Spring对事务的管理底层实现方式是基于AOP实现的。采用AOP的方式进行了封装。所以Spring专门针对事务开发了一套API,API的核心接口如下:
PlatformTransactionManager接口:spring事务管理器的核心接口。在Spring6中它有两个实现:
以银行账户转账为例学习事务。两个账户act-001和act-002。act-001账户向act-002账户转账10000,必须同时成功,或者同时失败。(一个减成功,一个加成功, 这两条update语句必须同时成功,或同时失败。)
连接数据库的技术采用Spring框架的JdbcTemplate。
采用三层架构搭建:
4.0.0 org.example spring6-010-tx-bank 1.0-SNAPSHOT jar org.springframework spring-context 6.0.2 org.springframework spring-jdbc 6.0.2 mysql mysql-connector-java 8.0.30 com.alibaba druid 1.2.13 jakarta.annotation jakarta.annotation-api 2.1.1 junit junit 4.13.2 test 17 17
com.powernode.bank.pojo
com.powernode.bank.service
com.powernode.bank.service.impl
com.powernode.bank.dao
com.powernode.bank.dao.impl
package com.powernode.bank.pojo;/*** 银行账户类*/
public class Account {private String actno;private Double balance;// 省略构造方法,get、set方法,toString方法
package com.powernode.bank.dao;import com.powernode.bank.pojo.Account;/*** 专门负责账户信息的CRUD操作* DAO中只执行SQL语句,没有任何业务逻辑*/
public interface AccountDao {/*** 根据账号查询账户信息* @param actno* @return*/Account selectByActno(String actno);/*** 更新账户信息* @param act* @return*/int update(Account act);
}
package com.powernode.bank.dao.impl;import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {@Resource(name = "jdbcTemplate")private JdbcTemplate jdbcTemplate;@Overridepublic Account selectByActno(String actno) {String sql = "select actno, balance from t_act where actno = ?";Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);return account;}@Overridepublic int update(Account act) {String sql = "update t_act set balance = ? where actno = ?";int count = jdbcTemplate.update(sql, act.getBalance(), act.getActno());return count;}
}
package com.powernode.bank.service;/*** 业务接口* 事务是在这个接口下控制的*/
public interface AccountService {/*** 转账业务方法* @param fromActno 转出账户* @param toActno 转入账户* @param money 转账金额*/void transfer(String fromActno, String toActno, double money);
}
在service类上或方法上添加@Transactional注解
在类上添加该注解,该类中所有的方法都有事务。在某个方法上添加该注解,表示只有这个方法使用事务。
package com.powernode.bank.service.impl;import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {@Resource(name = "accountDao")private AccountDao accountDao;@Override// 控制事务//@Transactionalpublic void transfer(String fromActno, String toActno, double money) {// 查询转出账户的余额是否充足Account fromAct = accountDao.selectByActno(fromActno);if (fromAct.getBalance() < money) {throw new RuntimeException("余额不足");}// 余额充足Account toAct = accountDao.selectByActno(toActno);// 将内存中两个对象的余额修改fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);// 数据库更新int count = accountDao.update(fromAct);/*// 模拟异常String s = null;s.toString();*/count += accountDao.update(toAct);if (count != 2) {throw new RuntimeException("转账失败");}}
}
在spring配置文件中配置事务管理器。
在spring配置文件中引入tx命名空间。
在spring配置文件中配置“事务注解驱动器”,开始注解的方式控制事务。
package com.powernode.bank.test;import com.powernode.bank.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringTXTest {@Testpublic void testSpringTX(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");AccountService accountService = applicationContext.getBean("accountService", AccountService.class);try{accountService.transfer("act-001","act-002",10000);System.out.println("转账成功");}catch (Exception e){e.printStackTrace();}}
}
数据变化:
模拟异常
事务中的重点属性:
什么是事务的传播行为?
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
事务传播行为在spring框架中被定义为枚举类型:
public enum Propagation {REQUIRED(0),SUPPORTS(1),MANDATORY(2),REQUIRES_NEW(3),NOT_SUPPORTED(4),NEVER(5),NESTED(6);
一共有七种传播行为:
@Transactional(propagation = Propagation.REQUIRED)
事务隔离级别类似于教室A和教室B之间的那道墙,隔离级别越高表示墙体越厚。隔音效果越好。
数据库中读取数据存在的三大问题:(三大读问题)
事务隔离级别包括四个级别:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 有 | 有 | 有 |
读提交 | 无 | 有 | 有 |
可重复读 | 无 | 无 | 有 |
序列化 | 无 | 无 | 无 |
在Spring代码中如何设置隔离级别?
隔离级别在spring中以枚举类型存在:
public enum Isolation {DEFAULT(-1),READ_UNCOMMITTED(1),READ_COMMITTED(2),REPEATABLE_READ(4),SERIALIZABLE(8);
@Transactional(isolation = Isolation.READ_COMMITTED)
@Transactional(timeout = 10)
以上代码表示设置事务的超时时间为10秒。
表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话,最终结果会选择回滚。
默认值-1,表示没有时间限制。
这里有个坑,事务的超时时间指的是哪段时间?
在当前事务当中,最后一条DML语句执行之前的时间。如果最后一条DML语句后面很有很多业务逻辑,这些业务代码执行的时间不被计入超时时间。
以下代码的超时不会被计入超时时间
@Transactional(timeout = 10) // 设置事务超时时间为10秒。
public void save(Account act) {accountDao.insert(act);// 睡眠一会try {Thread.sleep(1000 * 15);} catch (InterruptedException e) {e.printStackTrace();}
}
以下代码超时时间会被计入超时时间
@Transactional(timeout = 10) // 设置事务超时时间为10秒。
public void save(Account act) {// 睡眠一会try {Thread.sleep(1000 * 15);} catch (InterruptedException e) {e.printStackTrace();}accountDao.insert(act);
}
如果想让整个方法的所有代码都计入超时时间的话,可以在方法最后一行添加一行无关紧要的DML语句。
@Transactional(readOnly = true)
将当前事务设置为只读事务,在该事务执行过程中只允许select语句执行,delete insert update均不可执行。
该特性的作用是:启动spring的优化策略。提高select语句执行效率。
如果该事务中确实没有增删改操作,建议设置为只读事务。
@Transactional(rollbackFor = RuntimeException.class)
表示只有发生RuntimeException异常或该异常的子类异常才回滚。
@Transactional(noRollbackFor = NullPointerException.class)
表示发生NullPointerException或该异常的子类异常不回滚,其他异常则回滚。
编写一个类来代替配置文件
package com.powernode.bank;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;// 代替spring.xml文件 在这个类中完成配置
@Configuration
// 组件扫描
@ComponentScan("com.powernode.bank")
// 开启事务注解
@EnableTransactionManagement
public class Spring6Config {// Spring框架,看到这个@Bean注解后,会调用这个被标注的方法,这个方法的返回值是一个java对象,这个java对象会自动纳入IoC容器管理。// 返回的对象就是Spring容器当中的一个Bean了。// 并且这个bean的名字是:dataSource@Bean(name = "dataSource")public DruidDataSource getDataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");dataSource.setUsername("root");dataSource.setPassword("root");return dataSource;}@Bean(name = "jdbcTemplate")//Spring在调用这个方法的时候会自动给我们传递过来一个dataSource对象。public JdbcTemplate getJdbcTemplate(DataSource dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}@Bean(name = "txManager")public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){DataSourceTransactionManager txManager = new DataSourceTransactionManager();txManager.setDataSource(dataSource);return txManager;}
}
测试程序
@Test
public void testNoXML(){ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);AccountService accountService = applicationContext.getBean("accountService", AccountService.class);try{accountService.transfer("act-001","act-002",10000);System.out.println("转账成功");}catch (Exception e){e.printStackTrace();}
}
添加aspectj的依赖
org.springframework spring-aspects 6.0.2
Spring配置文件
上一篇:4-3 Linux启动流程