Spring中的声明式事务和编程式事务(@Transactional无效的八种原因及解决方案)

1、注解方式实现(声明式事务)

在方法或者类前面加上下面注解

@Transactional(value="transactionManager", rollbackFor = Exception.class)

这种情况经常会出现事务不生效的情况,下面对不生效的几种情况做一个总结

  1. 如果你@Transactional标注的方法不是public的,事务将无效
  2. 如果你使用@Transactional的时候贪图简单没有写里面的rollbackFor = Exception.class,那只有当发生unchecked异常的时候才能事务回滚,checked的异常事务无效。
  3. 如果你使用的是Mysql,那么你要注意你的表的存储引擎是否是InnoDB。如果不是InnoDb将导致事务无效。关于如何查看和修改Mysql表的存储引擎
  4. 异常被你cache住了而没有抛出将导致事务无效
  5. 如果你是在a方法中调用了同一个类的b方法,然后你的@Transactional是加在b方法上,那么事务将无效。你需要把@Transactional加在a方法上或者单独把b方法写到一个新的类里面去然后在a方法里面通过spring注入的方式调用(而不是B b = new B() b.b()这种方式)
  6. 你是否在配置文件中开启了对注解的解析。
  7. 你是否在spring的配置文件中扫描到了你这个包。注意是spring的配置文件,而不是springmvc的配置文件,笔者一直以为只需要在springmvc中配置context:component-scan。而事实是你需要在spring中也配置context:component-scan,否则将产生很多很奇怪的现象比如事务无效,但是很奇怪的是,即使你不在spring中配置context:component-scan,那么你依然可以使用诸如@Autowired等spring中的注解而不会有任何异常,只是事务没法使用。总而言之,你需要在spring的配置文件中配置。记得把后面的com.bxoon换成你自己的包目录。(具体原因后续分析)
  8. 多数据源的情况下你需要配置具体使用的哪一个事务管理器,而不能简单的写成@Transactional(rollbackFor = Exception.class),如果写成这样,事务将不生效

2、手动提交方式实现(编程式事务)

/**
 * JpaTransactionManager事务管理 .
 */
@Resource(name = "transactionManager")
JpaTransactionManager tm;

//事务开始
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);  
def.setTimeout(30);
//事务状态
TransactionStatus status = tm.getTransaction(def); 
try {
	//此处写持久层逻辑
	tm.commit(status);
} catch (Exception e) {
	logger.error("出现异常,事务回滚", e)
	if(!status.isCompleted()){
		tm.rollback(status);
	}
	throw new BusinessException("[制卡动作]更新卡状态为制卡审批通过失败。");
}

3、关于上诉不生效原因中的第7个原因的深入分析

2019.6.29更新

在上诉第七条中我们讲到

如果你的是配置在springmvc.xml中而非配置在spring的配置文件(多数情况下为application.xml)中,那么将造成事务无效。但是即使你不在spring的配置文件中配置上述代码,你依然可以正常使用诸如@Autowired等注解。那么本节,就来对造成这种想象的原因深入分析。

本质原因
其本质原因是因为:在spring和spring mvc中,都分别有一个bean容器,其中spring mvc的容器是spring容器的子容器,子容器可以获取到父容器的 Bean的,但反之则不行。但controller本身是存在于spring mvc容器中的,这也是很多教程中会采用如下配置方式的原因。

<!-- Spring希望管理所有的业务逻辑组件,不包含controller -->
<context:component-scan base-package="com.atguigu.mybatis">
	<context:exclude-filter type="annotation"
		expression="org.springframework.stereotype.Controller" />
</context:component-scan>

即过滤到controller.因为Controller默认由spring mvc管理,是不需要spring管理的。

事务为什么会无效
搞清楚了上诉spring和spring mvc的关系之后,我们再来分析事务为什么无效。
当我们把context:component-scan配置到spring mvc中的时候会造成什么影响呢?会把扫描到的bean放入spring mvc的bean容器中而非spring的容器中,而我们知道,oop事务是spring的内容而非spring mvc的内容,那么也就导致事务无效。

@Autower为什么会生效
@Autower会生效的原因是因为,@Autower注解会去自己的容器中找对应的类,在本处,因为controller和service都存在于spring mvc的容器中,所以也确实能找到,所以是能够加载的。

context:component-scan配置在spring mvc中有可能造成的其他问题
理论上只要是spring的功能不是spring mvc的功能都没法使用,例如oop.下面这个问题就是这样
https://mp.weixin.qq.com/s?__biz=MjM5NzMyMjAwMA==&mid=2651482269&idx=1&sn=3864454746096db6830cd1a67edb7cce&chksm=bd2504e28a528df447564a655799c752f07ff7b71c26c40e5f4ea1cb8b0e3c1bbfe9ce0cd652&mpshare=1&scene=1&srcid=#rd

2020.2.26更新

关于spring手动回滚的实现
当我们需要在事务控制的service层类中使用try catch 去捕获异常后,就会使事务控制失效,因为该类的异常并没有抛出,就不是触发事务管理机制。怎样才能即使用try catch去捕获异常,而又让出现异常后spring回滚呢?
有人说那就再把异常抛出,但其实即使抛出异常也不一定会事务回滚,因为回滚条件必须抛出的是运行时异常才会生效!
那么有没有更好的办法呢?可以在异常中加上这样一句

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

完整代码如下

//假设这是一个service类的片段

try{ 
    //出现异常
} catch (Exception e) {
    e.printStackTrace();
    //设置手动回滚
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
//此时return语句能够执行
return  xxx;

你可能感兴趣的:(Spring)