项目中使用了Spring的@Transactional注解,有时候会出现一些奇怪的问题,例如:
1)明明抛了异常却不回滚?
2)嵌套事务执行报错?
很多的问题都是没有全面了解@Transactional的正确使用而导致的,下面一段代码就可以让你完全明白@Transactional到底该怎么用。
直接上代码,请细细品味
@Service
public class SysConfigService {
@Autowired
private SysConfigRepository sysConfigRepository;
public SysConfigEntity getSysConfig(String keyName) {
SysConfigEntity entity = sysConfigRepository.findOne(keyName);
return entity;
}
public SysConfigEntity saveSysConfig(SysConfigEntity entity) {
if(entity.getCreateTime()==null){
entity.setCreateTime(new Date());
}
return sysConfigRepository.save(entity);
}
@Transactional
public void testSysConfig(SysConfigEntity entity) throws Exception {
//不会回滚
this.saveSysConfig(entity);
throw new Exception("sysconfig error");
}
@Transactional(rollbackFor = Exception.class)
public void testSysConfig1(SysConfigEntity entity) throws Exception {
//会回滚
this.saveSysConfig(entity);
throw new Exception("sysconfig error");
}
@Transactional
public void testSysConfig2(SysConfigEntity entity) throws Exception {
//会回滚
this.saveSysConfig(entity);
throw new RuntimeException("sysconfig error");
}
@Transactional
public void testSysConfig3(SysConfigEntity entity) throws Exception {
//事务仍然会被提交
this.testSysConfig4(entity);
throw new Exception("sysconfig error");
}
@Transactional(rollbackFor = Exception.class)
public void testSysConfig4(SysConfigEntity entity) throws Exception {
this.saveSysConfig(entity);
}
}
总结如下:
/**
* @Transactional事务使用总结:
*
* 1、异常在A方法内抛出,则A方法就得加注解
* 2、多个方法嵌套调用,如果都有 @Transactional 注解,则产生事务传递,默认 Propagation.REQUIRED
* 3、如果注解上只写 @Transactional 默认只对 RuntimeException 回滚,而非 Exception 进行回滚
* 如果要对 checked Exceptions 进行回滚,则需要 @Transactional(rollbackFor = Exception.class)
*
* org.springframework.orm.jpa.JpaTransactionManager
*
* org.springframework.jdbc.datasource.DataSourceTransactionManager
*
* org.springframework.transaction.jta.JtaTransactionManager
*
*
*
*/
事务隔离级别
事务隔离级别,就是为了解决上面几种问题而诞生的。为什么要有事务隔离级别,因为事务隔离级别越高,在并发下会产生的问题就越少,但同时付出的性能消耗也将越大,因此很多时候必须在并发性和性能之间做一个权衡。所以设立了几种事务隔离级别,以便让不同的项目可以根据自己项目的并发情况选择合适的事务隔离级别,对于在事务隔离级别之外会产生的并发问题,在代码中做补偿。
事务隔离级别有4种,但是像Spring会提供给用户5种,来看一下:
1、DEFAULT
默认隔离级别,每种数据库支持的事务隔离级别不一样,如果Spring配置事务时将isolation设置为这个值的话,那么将使用底层数据库的默认事务隔离级别。顺便说一句,如果使用的MySQL,可以使用"select @@tx_isolation"来查看默认的事务隔离级别
2、READ_UNCOMMITTED
读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用
3、READ_COMMITED
读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读
4、REPEATABLE_READ
重复读取,即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决
5、SERLALIZABLE
串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了
网上专门有图用表格的形式列出了事务隔离级别解决的并发问题:
再必须强调一遍,不是事务隔离级别设置得越高越好,事务隔离级别设置得越高,意味着势必要花手段去加锁用以保证事务的正确性,那么效率就要降低,因此实际开发中往往要在效率和并发正确性之间做一个取舍,一般情况下会设置为READ_COMMITED,此时避免了脏读,并发性也还不错,之后再通过一些别的手段去解决不可重复读和幻读的问题就好了。
事务的7种传播行为
事务传播行为是Spring框架独有的事务增强特性。
7种:(required / supports / mandatory / requires_new / not supported / never / nested)
(required需要,没有新建,有加入)
PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。(supports支持,有则加入,没有就不管了,非事务运行)
PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。(mandatory强制性,有则加入,没有异常)
PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。(requires_new需要新的,不管有没有,直接创建新事务)
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。(not supported不支持事务,存在就挂起)
PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。(never不支持事务,存在就异常)
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。(nested存在就在嵌套的执行,没有就找是否存在外面的事务,有则加入,没有则新建)
对事务的要求程度可以从大到小排序:mandatory / supports / required / requires_new / nested / not supported / never