本文最后更新于:2024年9月8日 晚上
Spring 事务
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aop</artifactId > <version > 5.3.13</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 5.3.13</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 5.3.13</version > </dependency >
Spring 事务属性
Spring事务属性对应TransactionDefinition
类里面的各个方法,TransactionDefinition
类方法如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface TransactionDefinition { int getPropagationBehavior ( ) ; int getIsolationLevel ( ) ; int getTimeout ( ) ; boolean isReadOnly ( ) ; @Nullable String getName ( ) ; }
事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上,事务属性包含了5个方面:传播行为,隔离规则,回滚规则,事务超时,是否只读。
事务传播特性
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播,例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行,Spring定义了七种传播行为:
传播行为
含义
TransactionDefinition.PROPAGATION_REQUIRED
如果当前没有事务,就新建一个事务,如果已经存在一个事务,则加入到这个事务中
TransactionDefinition.PROPAGATION_SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行,
TransactionDefinition.PROPAGATION_MANDATORY
表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常
TransactionDefinition.PROPAGATION_REQUIRED_NEW
表示当前方法必须运行在它自己的事务中,一个新的事务将被启动,如果存在当前事务,在该方法执行期间,当前事务会被挂起,
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
表示该方法不应该运行在事务中,如果当前存在事务,就把当前事务挂起,
TransactionDefinition.PROPAGATION_NEVER
表示当前方法不应该运行在事务上下文中,如果当前正有一个事务在运行,则会抛出异常
TransactionDefinition.PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作,
Spring 默认的事务传播行为是PROPAGATION_REQUIRED
,它适合于绝大多数的情况。
隔离级别
隔离级别定义了一个事务可能受其他并发事务影响的程度。
并发状态下可能产生: 脏读,不可重复读,幻读的情况,因此我们需要将事务与事务之间隔离,根据隔离的方式来避免事务并发状态下脏读,不可重复读,幻读的产生,Spring中定义了五种隔离规则:
隔离级别
含义
脏读
不可重复读
幻读
TransactionDefinition.ISOLATION_DEFAULT
使用后端数据库默认的隔离级别
TransactionDefinition.ISOLATION_READ_UNCOMMITTED
允许读取尚未提交的数据变更(最低的隔离级别)
是
是
是
TransactionDefinition.ISOLATION_READ_COMMITTED
允许读取并发事务已经提交的数据
否
是
是
TransactionDefinition.ISOLATION_REPEATABLE_READ
对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改
否
否
是
TransactionDefinition.ISOLATION_SERIALIZABLE
最高的隔离级别,完全服从ACID的隔离级别,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的
否
否
否
注意 :ISOLATION_SERIALIZABLE
隔离规则类型在开发中很少用到,如果使用了ISOLATION_SERIALIZABLE
规则,A,B两个事务操作同一个数据表并发过来了,A先执行,A事务这个时候会把表给锁住,B事务执行的时候直接报错。
事务隔离级别与锁的关系。
事务隔离级别为ISOLATION_READ_UNCOMMITTED
时,写数据只会锁住相应的行。
事务隔离级别为可ISOLATION_REPEATABLE_READ
时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key锁,如果检索条件没有索引,更新数据时会锁住整张表,一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
事务隔离级别为ISOLATION_SERIALIZABLE
时,读写数据都会锁住整张表。
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也就越大。
回滚规则
事务回滚规则定义了哪些异常会导致事务回滚而哪些不会。
默认情况下,只有RuntimeException和Error类型的异常会导致事务回滚,而在遇到CheckedException异常时不会回滚。
可以声明事务在遇到特定的CheckedException时像遇到RuntimeException那样回滚,也还可以声明事务遇到特定的异常不回滚,即使这些异常是RuntimeException
事务超时
为了使应用程序很好地运行,事务不能运行太长的时间,因为事务可能涉及对后端数据库的锁定,也会占用数据库资源,事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
是否只读
如果在一个事务中所有关于数据库的操作都是只读的,也就是说,这些操作只读取数据库中的数据,而并不更新数据, 这个时候我们应该给该事务设置只读属性,这样可以帮助数据库引擎优化事务,提升效率。
Spring 事务管理
Spring 为事务管理提供了丰富的功能支持,Spring 事务管理分为编码式和声明式的两种方式:
编程式事务:允许用户在代码中精确定义事务的边界,编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager,对于编程式事务管理,spring推荐使用TransactionTemplate
声明式事务:基于AOP,有助于用户将操作与事务规则进行解耦,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务,声明式事务管理也有两种常用x方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional注解的方式。
注意 :显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式,声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持,和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别,但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
使用配置文件实现声明式事务
将事务管理作为横切关注点,通过aop方法模块化,Spring中通过Spring AOP框架支持声明式事务管理。
事务管理器
无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
事务管理器就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。
配置事务管理器。
1 2 3 4 5 <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" /> </bean >
配置好事务管理器后我们需要去配置事务的通知。
1 2 3 4 5 6 7 8 9 10 11 12 <tx:advice id ="txAdvice" transaction-manager ="transactionManager" > <tx:attributes > <tx:method name ="add" propagation ="REQUIRED" /> <tx:method name ="delete" propagation ="REQUIRED" /> <tx:method name ="update" propagation ="REQUIRED" /> <tx:method name ="search*" propagation ="REQUIRED" /> <tx:method name ="get" read-only ="true" /> <tx:method name ="*" propagation ="REQUIRED" /> </tx:attributes > </tx:advice >
配置AOP
1 2 3 4 5 <aop:config > <aop:pointcut id ="txPointcut" expression ="execution(* com.test.mapper.*.*(..))" /> <aop:advisor advice-ref ="txAdvice" pointcut-ref ="txPointcut" /> </aop:config >
测试。
1 2 3 4 5 6 7 @Test public void test2 () { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml" ); UserMapper mapper = (UserMapper) context.getBean("userMapper" ); List<User> user = mapper.selectUser(); System.out.println(user); }
使用注解实现声明式事务
@Transactional属性
@Transactional注解里面的各个属性和Spring的事务属性里面是一一对应的,用来设置事务的传播行为,隔离规则,回滚规则,事务超时,是否只读。
value/transactionManager
大多数项目只需要一个事务管理器,然而,有些项目为了提高效率,或者有多个完全不同又不相干的数据源,从而使用了多个事务管理器,当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。
首先定义多个transactional manager,并为qualifier属性指定不同的值,然后在需要使用@Transactional注解的时候指定TransactionManager的qualifier属性值或者直接使用bean名称。
1 2 3 4 5 6 7 8 9 10 11 <tx:annotation-driven /> <bean id ="transactionManager1" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="datasource1" > </property > <qualifier value ="datasource1Tx" /> </bean > <bean id ="transactionManager2" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="datasource2" > </property > <qualifier value ="datasource2Tx" /> </bean >
1 2 3 4 5 6 7 8 9 public class TransactionalService { @Transactional("datasource1Tx") public void setSomethingInDatasource1 () { ... } @Transactional("datasource2Tx") public void doSomethingInDatasource2 () { ... } }
propagation
propagation用于指定事务的传播行为,默认值为 REQUIRED
1 @Transactional(propagation = REQUIRED)
isolation
isolation用于指定事务的隔离规则,默认值为DEFAULT
1 @Transactional(isolation = READ_COMMITTED)
timeout
1 @Transactional(timeout = 10000)
readOnly
1 @Transactional(readOnly = true)
rollbackFor,rollbackForClassName,noRollbackFor,noRollbackForClassName
rollbackFor,rollbackForClassName用于设置那些异常需要回滚。
noRollbackFor,noRollbackForClassName用于设置那些异常不需要回滚,他们就是在设置事务的回滚规则。
1 @Transactional(rollbackFor = IOException.class, norollbackFor = NullPointerException.class)
@Transactional的使用
@Transactional注解可以作用于接口,接口方法,类以及类方法上,当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,也可以在方法级别使用该标注来覆盖类级别的定义。
虽然@Transactional 注解可以作用于接口,接口方法,类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。
@Transactional注解应该只被应用到 public 方法上,这是由Spring AOP的本质决定的,如果你在 protected,private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。
要明确事务的作用范围,有@Transactional的函数调用有@Transactional的函数的时候,注意区分进入第二个函数的时候是新的事务,还是沿用之前的事务,稍不注意就会抛UnexpectedRollbackException
异常。
同一个类中函数相互调用
同一个类AClass中,有两个函数aFunction,aInnerFunction,aFunction调用aInnerFunction,而且aFunction函数会被外部调用。
情况1: aFunction添加了@Transactional注解,aInnerFunction函数没有添加,aInnerFunction抛异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class AClass { @Transactional(rollbackFor = Exception.class) public void aFunction () { aInnerFunction(); } private void aInnerFunction () { throw new RuntimeException("函数执行有异常!" ); } }
情况2:两个函数都添加了@Transactional注解,aInnerFunction抛异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class AClass { @Transactional(rollbackFor = Exception.class) public void aFunction () { aInnerFunction(); } @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) private void aInnerFunction () { throw new RuntimeException("函数执行有异常!" ); } }
结果:同第一种情况一样,两个函数对数据库操作都会回滚,因为同一个类中函数相互调用的时候,内部函数添加@Transactional注解无效,@Transactional注解只有外部调用才有效。
情况3: aFunction不添加注解,aInnerFunction添加注解,aInnerFunction抛异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class AClass { public void aFunction () { aInnerFunction(); } @Transactional(rollbackFor = Exception.class) protected void aInnerFunction () { throw new RuntimeException("函数执行有异常!" ); } }
结果:两个函数对数据库的操作都不会回滚,因为内部函数@Transactional注解添加和没添加一样。
情况4:aFunction添加了@Transactional注解,aInnerFunction函数没有添加,aInnerFunction抛异常,不过在aFunction里面把异常抓出来了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class AClass { @Transactional(rollbackFor = Exception.class) public void aFunction () { try { aInnerFunction(); } catch (Exception e) { e.printStackTrace(); } } private void aInnerFunction () { throw new RuntimeException("函数执行有异常!" ); } }
结果:两个函数里面的数据库操作都成功,事务回滚的动作发生在当有@Transactional注解函数有对应异常抛出时才会回滚。
不同类中函数相互调用
两个类AClass,BClass,AClass类有aFunction,BClass类有bFunction,AClass类aFunction调用BClass类bFunction,最终会在外部调用AClass类的aFunction
情况1:aFunction添加注解,bFunction不添加注解,bFunction抛异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Service() public class AClass { private BClass bClass; @Autowired public void setbClass (BClass bClass) { this .bClass = bClass; } @Transactional(rollbackFor = Exception.class) public void aFunction () { bClass.bFunction(); } }@Service() public class BClass { public void bFunction () { throw new RuntimeException("函数执行有异常!" ); } }
情况2:aFunction,bFunction两个函数都添加注解,bFunction抛异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Service() public class AClass { private BClass bClass; @Autowired public void setbClass (BClass bClass) { this .bClass = bClass; } @Transactional(rollbackFor = Exception.class) public void aFunction () { bClass.bFunction(); } }@Service() public class BClass { @Transactional(rollbackFor = Exception.class) public void bFunction () { throw new RuntimeException("函数执行有异常!" ); } }
结果:两个函数对数据库的操作都回滚了,两个函数里面用的还是同一个事务,这种情况下,你可以认为事务rollback了两次,两个函数都有异常。
情况3:aFunction,bFunction两个函数都添加注解,bFunction抛异常,aFunction抓出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Service() public class AClass { private BClass bClass; @Autowired public void setbClass (BClass bClass) { this .bClass = bClass; } @Transactional(rollbackFor = Exception.class) public void aFunction () { try { bClass.bFunction(); } catch (Exception e) { e.printStackTrace(); } } }@Service() public class BClass { @Transactional(rollbackFor = Exception.class) public void bFunction () { throw new RuntimeException("函数执行有异常!" ); } }
结果:两个函数数据库操作都没成功,而且还抛出异常:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
可以这么理解,两个函数用的是同一个事务,bFunction函数抛了异常,调了事务的rollback函数,事务被标记了只能rollback了,程序继续执行,aFunction函数里面把异常给抓出来了,这个时候aFunction函数没有抛出异常,既然你没有异常那事务就需要提交,会调事务的commit函数,而之前已经标记了事务只能rollback-only(以为是同一个事务),直接就抛异常了,不让调了。
情况4:aFunction,bFunction两个函数都添加注解,bFunction抛异常,aFunction抓出异常,这里要注意bFunction函数@Transactional注解我们是有变化的,加了一个参数propagation = Propagation.REQUIRES_NEW,控制事务的传播行为,表明是一个新的事务,其实情况3就是来解决情况2的问题的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Service() public class AClass { private BClass bClass; @Autowired public void setbClass (BClass bClass) { this .bClass = bClass; } @Transactional(rollbackFor = Exception.class) public void aFunction () { try { bClass.bFunction(); } catch (Exception e) { e.printStackTrace(); } } }@Service() public class BClass { @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void bFunction () { throw new RuntimeException("函数执行有异常!" ); } }
结果:bFunction函数里面的操作回滚了,aFunction里面的操作成功了,有了前面情况3的理解,这种情况也很好解释,两个函数不是同一个事务了。