再spring中要使用事务,很简单,只需要在要加事物的方法上加上一个@Transtional注解就行,这个注解可以加载接口或者类上,如果加载类上或者接口上,那就表示这个类上或者接口上都加了事务管理。
spring中有事务传播机制,就是两个用来规定两个都加了事务的方法,再被调用时的规则,是新创建一个事务,还是加入调用者的事务,还是抛异常,就比如a(),b()方法上都加了@transtational注解,b()方法里调用a()方法注解,那么,a()方法里可以设置事务传播机制,选择加入b()方法的事务,还是自己重新开一个事务,还是抛异常等等。。。。
事务的传播机制有几种,但我们主要记住REQUIRE:加入调用者的事务(默认的),REQUIRES_NEW:无论调用者有没有事务都自己创建一个事务。
像这里,delete()方法,他调用了deleteByDeptId()方法,就会有用到事务传播机制,用到的默认的require
如果要设置事务传播机制,可以在propagation中设置。
下面说一下事务失效的场景:
1.同一个类中的方法自调用:
因为事务的注解@transactional注解要代理对象才能检测到,比如再同一个类中b()方法调用了a()方法,b()方法是代理对象执行的,但是a()方法是普通对象执行的,所以检测不要a()方法上的@transactional注解,因为a()方法事务会失效,要解决这种问题,1.可以把a()方法写到另一个类中,然后再把这个类的对象注入进来,再用那个类代理对象调用a()方法。2.可以自己注入自己,再用自己这个类的代理对象调用a()方法。
第2中方法:
第二种失效的情况:就是这个方法不是public或者有final修饰,比如a()方法是private,因为这种情况,这个方法是不会被代理对象重写的,代理对象自己就没有这个方法,他没有额外的逻辑去执行a()方法,他不会去开启事务(setAutocommit=0),不会提交事务,回滚啥的,就是直接执行a方法里的sql,执行完sql就提交了,所以事务就失效了。
第三种失效情况是:方法内,有另一个线程执行了sql:,虽然在a()方法上加了@transactional注解,但是因为方法内sql在一个新线程内,所以这个sql会再这个线程内检测有没有开启事务,有没有建立数据库连接,如果没有,他就会自己建立一个新的数据库连接,他自己的新建的数据库连接里面的autocommit=1,也就是自动提交,所以sql一执行完就会提交,不管后面抛步抛异常。
第四种失效的情况就是:配置类失效了没有加@Configurtion注解,这种咋spring中常见,再boot中比较少:
再配置类里面,配置了jdbcTemplate,和platformTransactionManager,这两个bean都需要一个DataSource对象,如果我们加了@Configtration注解,那么这个appconfig就会生成代理对象,执行代理逻辑,再代理逻辑里,会给jdbcTemplate和platformTransactionManager,提供同一个数据源DataSource
这样我们在执行a()方法时,上面的@transational注解生效时,会生成一个DataSource数据源,和connection数据库连接,并且把这个DataSource作为key,connection作为值存进map里面,把map存进TrealLocal里面,还会把autocommit设置为0,也就是false关闭自动提交。所以当我们的jdbcTemplate在执行时,他会先去ThreadLocal里面找有没有他的DataSource的对应的connection,
如果我们在配置类上加了@configurtion注解,那么jdbc和transactional两个对象的DataSource应该是一样的,因此,jdbc能用DataSource在ThreadLocal里面的map里面找到transactional创建的connection,也就是transactional和jdbc用同一个connection,他的autocommit已经设置为了false,所以不会自动提交,因为事务不会失效。
如果我们没有在配置类appconfig上家@configuration注解,那么jdbc和transactional两个的DataSource就不一样,不一样,那jdbc就不能再ThreadLocal的map李找的transactional创建的connection,所以jdbc他会自己创建一个connection,jdbc创建的connection没有设置autocommit=false,所以sql一执行完,就会提交,因此事务失效了。
第五种情况就是:方法内出现的异常不是RuntimeException或者error,因为transactional默认是只能检测到运行时异常和error才回滚,如果是编译时异常,那么就检测不到也就不会回滚:
所以有两种方法:
1.设置所以异常有检测到,都回滚:
2:把编译时异常try--catch,再catch里面,抛出一个运行时异常