事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。
1. 事务的定义
事务: 是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元)。
2. 事务的四大特性
原子性
事务中的操作为一个整体,要么都做,要么都不做,即一旦事务出错,就回滚事务
一致性
执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。
如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
隔离性
一个事务的执行不能被其他事务干扰。即一个事物内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事物之间不能互相干扰。
持久性
指一个事物一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
3. 事务管理方式
Spring支持 编程式事务管理 和 声明式事务管理 两种方式。
编程式事务Spring推荐使用TransactionTemplate。
声明式事务管理建立在AOP之上 的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
声明式事务唯一不足地方是只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
4. @Transactional注解
注解参数如下
- propagation
事务传播类型。默认为 Propagation.REQUIRED
可选值 | 描述 |
Propagation.REQUIRED | 支持当前事务,如果不存在则创建一个新事务。这是事务注释的默认设置 |
SUPPORTS | 支持当前事务,如果不存在则以非事务方式执行。 |
MANDATORY | 支持当前事务,如果不存在则抛出异常。 |
REQUIRES_NEW | 创建一个新事务,如果存在则暂停当前事务 |
NOT_SUPPORTED | 以非事务方式执行,如果存在则暂停当前事务 |
NEVER | 以非事务方式执行,如果存在事务则抛出异常 |
NESTED | 如果当前事务存在,则在嵌套事务中执行,否则行为类似于 REQUIRED。 |
- timeout
事务的超时时间(以秒为单位)。默认为底层事务系统的默认超时(30秒)。
- timeoutString
同timeout,此事务的超时时间(以秒为单位)。默认为底层事务系统的默认超时。
- isolation
事务隔离级别。默认为 Isolation.DEFAULT
脏读 : 一个事务读取到另一事务未提交的更新数据
不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说,
后续读取可以读到另一事务已提交的更新数据. 相反, "可重复读"在同一事务中多次
读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
幻读 : 一个事务读到另一个事务已提交的insert数据
可选值 | 描述 |
Isolation.DEFAULT | 使用底层数据存储的默认隔离级别。所有其他级别对应于 JDBC 隔离级别。 |
Isolation.READ_UNCOMMITTED | 读取未提交数据(会出现脏读、不可重复读、幻读) 基本不使用 |
Isolation.READ_COMMITTED | 读取已提交数据(会出现不可重复读和幻读) |
Isolation.REPEATABLE_READ | 可重复读(会出现幻读) |
Isolation.SERIALIZABLE最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读 |
MYSQL: 默认为REPEATABLE_READ级别
SQLSERVER: 默认为READ_COMMITTED
- readOnly
设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。
- rollbackFor
设置需要进行回滚的异常类,当方法中抛出指定异常,则进行事务回滚。可以是1个异常活多个异常类,必须是Throwable的子类,默认情况下,事务将在 RuntimeException 和 Error 上回滚。
指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
- rollbackForClassName
同上边rollbackFor, 只不过可以是完全限定类名的子字符串,目前不支持通配符。例如,“ServletException”的值将匹配 javax.servlet.ServletException 及其子类。
指定单一异常类名称:@Transactional(rollbackForClassName=“RuntimeException”)
指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,“Exception”})
- noRollbackFor
设置哪些异常类型 不能 导致事务回滚,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。匹配异常类及其子类。
指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})
- noRollbackForClassName
指示哪些异常类型不得导致事务回滚。可以是完全限定类名的子字符串
指定单一异常类名称:@Transactional(noRollbackForClassName=“RuntimeException”)
指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,“Exception”})
5. @Transactional不生效的场景
用在非public方法
@Transactional是基于动态代理的,Spring的代理工厂在启动时会扫描所有的类和方法,并检查方法的修饰符是否为public,非public时不会获取@Transactional的属性信息,这时@Transactional的动态代理对象为空。
同一个类中,非@Transactional方法调用@Transactional方法
还是动态代理的原因,类内部方法的调用是通过this调用的,不会使用动态代理对象,事务不会回滚。
异常被捕获
Spring是根据抛出的异常来回滚的,如果异常被捕获了没有抛出的话,事务就不会回滚。
rollbackFor属性设置不对
Spring默认抛出RuntimeException 异常或Error时才会回滚事务,要想其他类型异常也回滚则需要设置rollbackFor属性的值。
当前类没有被Spring管理
没有被Spring管理成为IOC容器中的一个bean,更别说被事务切面代理到了
数据库引擎不支持事务
6. 代码实践
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
默认情况下,只有 来自外部的方法调用 才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。
6.1 声明式事务
@Transactional(readOnly = true) public class UserServiceImpl implements UserService { public void insertAll(List<User> list) { // do something } //方法上注解属性会覆盖类注解上的相同属性 @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) public void updateUser(User user) { // do something } }
6.2 编程式事务
@Autowired private TransactionTemplate transactionTemplate; @Autowired private DataSourceTransactionManager dataSourceTransactionManager; public void insert(){ // 开启一个只读事务 transactionTemplate.setReadOnly(true); TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionTemplate); try{ //do something //提交 dataSourceTransactionManager.commit(transactionStatus); }catch(Exception ex){ //回滚 dataSourceTransactionManager.rollback(transactionStatus); } } public void insert2(){ transactionTemplate.execute(status -> { //数据库新增 dao.insert(2); dao.insert(3); return new Object(); }); }