关于事务管理的概念这里就不多介绍了,在我的博客“JDBC事务之理论篇”中也有介绍。
关于Spring的事务管理,主要是通过事务管理器来进行的。这里看个Spring事务管理的接口图:(来自博客https://www.cnblogs.com/yixianyixian/p/8372832.html)
大概就是TransactionDefinition接口的实现类对对事务的一些配置设置进行定义;PlatformTransaction接口的实现类,是对事务进行管理的,像commit啊rollback等操作;然后TransactionStatus接口的实现类,是获得事务的一些状态的。
然后,对于事务管理,Spring提供了声明式和编程式的方法。 所谓编程式,就是在业务逻辑代码中精确地定位事务管理,而声明式事务管理,是基于AOP的,也就是说可以将事务管理代码和业务逻辑代码解耦。
在SpringBoot中使用Spring的事务管理非常简单,相当于Spring声明式事务管理,而且是注释版的。
我们只需要用两个注解:
@EnableTransactionManagement
@Transactional
@EnableTransactionManagement是要加载SpringBoot启动类上的,相当于是配置文件中的:
<!-- 声明式事务管理 配置事物的注解方式注入--> <tx:annotation-driven transaction-manager="transactionManager"/>
而这个@Transaction是放在你要进行事务管理的类或者是方法上的。
在@Transaction注解中有一些属性可以设置:
@Transactional注解中常用参数说明
参数名称 |
功能描述 |
readOnly |
该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) |
rollbackFor |
该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如: 指定单一异常类:@Transactional(rollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
rollbackForClassName |
该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如: 指定单一异常类名称:@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"}) |
propagation |
该属性用于设置事务的传播行为,具体取值可参考表6-7。 例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation |
该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 |
timeout |
该属性用于设置事务的超时秒数,默认值为-1表示永不超时 |
这里要讲的也就设置的比较多的两个:
首先是propagation。翻译过来是传播行为。什么意思呢?
我们一般都是将事务设置在Service层 那么当我们调用Service层的一个方法的时候它能够保证我们的这个方法中执行的所有的对数据库的更新操作保持在一个事务中,在事务层里面调用的这些方法要么全部成功,要么全部失败。那么事务的传播特性也是从这里说起的。
如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法,那么在调用其他的Service方法的时候,这个事务是怎么规定的呢,我必须保证我在我方法里掉用的这个方法与我本身的方法处在同一个事务中,否则如果保证事物的一致性。事务的传播特性就是解决这个问题的,“事务是会传播的”在Spring中有针对传播特性的多种配置我们大多数情况下只用其中的一种:PROPGATION_REQUIRED:这个配置项的意思是说当我调用service层的方法的时候开启一个事务(具体调用那一层的方法开始创建事务,要看你的aop的配置),那么在调用这个service层里面的其他的方法的时候,如果当前方法产生了事务就用当前方法产生的事务,否则就创建一个新的事务。这个工作使由Spring来帮助我们完成的。
这里做一个各个Propagation的汇总:
1: PROPAGATION_REQUIRED 加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务 比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候, ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA 的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。 这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被 提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚 2: PROPAGATION_SUPPORTS 如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行 这就跟平常用的普通非事务的代码只有一点点区别了。不理这个,因为我也没有觉得有什么区别 3: PROPAGATION_MANDATORY 必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常。 4: PROPAGATION_REQUIRES_NEW 这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW, 那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后, 他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在 两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚, 如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。 5: PROPAGATION_NOT_SUPPORTED 当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED , 那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。 6: PROPAGATION_NEVER 不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER , 那么ServiceB.methodB就要抛出异常了。 7: PROPAGATION_NESTED 理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立, 而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。 而Nested事务的好处是他有一个savepoint。
一般用的较多就这个PROPAGATION_REQUIRED
然后还要讲的要给属性是: Isolation,就是确认隔离机制。
这个就和JDBC中的隔离机制基本一致,是为了防止drity reads、non-repeatable reads、phantom reads而有的机制,隔离级别常量这里也不细讲了,和我的博客中记录的“JDBC事务管理理论篇”中的基本一样。
好的下面上代码来看例子:
在我们的service层中的一个类中新建一个测试方法:
先在接口中添加声明:
public interface UserService { Map<String, Object> selectList(Map<String, Object> map); Map<String, Object> insert(User user); Map<String, Object> update(User user); Map<String, Object> delete(String id); void transactionTest(User user1, User user2); }
再看实现类:(其他方法的实现省略了)
@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public void transactionTest(User user1, User user2) { // TODO Auto-generated method stub userMapper.insert(user1); System.out.println(1/0); userMapper.insert(user2); } }
然后在我们service的新建方法中,我们两次调用userMapper的insert方法,也就是说进行了两次数据库的插入操作。并且在中间人为制造了一个异常。
先来看没进行事务管理的效果:
测试类:
@RunWith(SpringRunner.class) // SpringJUnit支持,由此引入Spring-Test框架支持! @SpringBootTest //提供spring环境 public class TransactionTest { @Autowired private UserService userService; @Test public void testTransaction() { User user1 = new User(); User user2 = new User(); user1.setId("111111"); user1.setUserAccount("tt"); user1.setUserName("user1"); user1.setUserPassword("12312312"); user2.setId("222222"); user2.setUserAccount("bb"); user2.setUserName("user2"); user2.setUserPassword("12312312"); userService.transactionTest(user1, user2); } }
结果截图:
运行前的数据:
运行后:
可见,执行了一个insert之后,因为异常,后面那个insert就没执行了。
好的现在我们加入事务管理:
加入启动事务管理注解:
@SpringBootApplication(scanBasePackages={"com.stuPayment.*"}) @EnableTransactionManagement @MapperScan("com.stuPayment.dao") public class StuPaymentApplication { public static void main(String[] args) { SpringApplication.run(StuPaymentApplication.class, args); } }
加入Transactional注解:
可以加在类上,这样的话,就类中的所有方法都事务管理,这里我们加在方法上:
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor=Exception.class) @Override public void transactionTest(User user1, User user2) { // TODO Auto-generated method stub userMapper.insert(user1); System.out.println(1/0); userMapper.insert(user2); }
再运行看效果:
因为这个时候是REQUIRED的传播级别,也就是说两个方法都是一个事务的,只要有错误,就会回滚。
最后再说几点注意的地方:
1、用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)
2、@Transactional 注解应该只被应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。
3、Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。
参考过的博客:
这两个是介绍Spring的事务管理的综述的
https://www.cnblogs.com/yixianyixian/p/8372832.html
https://blog.csdn.net/donggua3694857/article/details/69858827
这两个是介绍传播行为的概念的,第一个讲概念可以,第二个的例子挺清晰:
https://blog.csdn.net/wwh578867817/article/details/51736723
https://blog.csdn.net/hsgao_water/article/details/52860380
SpringBoot中的事务管理:
https://blog.csdn.net/wohaqiyi/article/details/72895983
@Transactional注解介绍:
https://www.cnblogs.com/caoyc/p/5632963.html