一、事务简介
事务的ACID(原子性,一致性,隔离性,持久性)
隔离性是有4种隔离级别(针对脏读、可重复读,幻读)
除此之外,还有七种传播机制
一提到事务,我们可能最先想到的就是数据库中的事务。如果MySQL中一个事务中某个操作失败了,那么整个事务都会回滚,变成原来的样子。
而Spring事务和数据库中的事务其实是一样的,它也是调用数据库的事物操作,回滚之类的。
因此,如果数据库没有事物,Spring事物也是无效的
Spring事务一般是针对Service的。
二、Spring事物应用场景举例
在一个service方法中,需要插入两张表A、B,两次插入插入操作必须一起成功,或者一起失败。
如下
//insertA(),insertB()必须一起成功或失败
@Override
public void insertService(T record1, T record2) {
//插入A表
insertA(record1);
//插入B表
insertB(record2);
}
实现的方式如下。
三、Spring事物实现的几种可能
1、在整个方法上加@Transactional(不必在单个方法中加):
@Transactional
public void insertService(T record1, T record2) {
//插入A表
insertA(record1);
//插入B表
insertB(record2);
}
2、在单个方法上加@Transactional:
//注意,这两个方法一定和insertService()不在同一个类中!
@Transactional(propagation=PROPAGATION_MANDATORY)
public Long insertA(T record);
@Transactional(propagation=PROPAGATION_MANDATORY)
public Long insertB(T record);
如果是REQUIRED级别就无效,必须是MANDATORY级别
3、在整个方法及单个方法上加@Transactional(解释事务传播最经典的案例):
class X{
@Transactional
public void insertService(T record1, T record2) {
//插入A表
insertA(record1);
//插入B表
insertB(record2);
}
}
class Y{
//注意,这两个方法一定和insertService()不在同一个类中!
@Transactional
public Long insertA(T record);
@Transactional
public Long insertB(T record);
}
这三种方法可能有点让人难以理解,下面是对此的说明。
四、Spring事物的传播机制
这两种方法工作的原理就涉及到事物的传播机制。
Spring默认的传播机制是:REQUIRED。
它的规则是:这个方法必须运行在事务中,如果事务存在,则会运行在当前事务中,否则会新开一个事务。
有点晦涩难懂,但其实也很好理解,结合上面两种方法解释就是:
- 对整个方法加事物——程序调用insertService()方法时,发现它有事物注解,便会创建一个事物,将这个方法加入到事物之中。
- 对单个插入加事物——程序运行到insertA(),发现这个方法是有MANDATORY级别的事物注解,继续运行发现insertB()也有MANDATORY级别的事物注解,便会认为,两个事务必然是同时成功或失败。
- 对整个及单个加事物——程序运行到insertService()方法,发现这个方法是有事物注解,便会创建一个事物,把它加入进入。继续运行发现insertA()也有事务,但在事务之中,就不会再创建事务了,insertB()也是同理。
对于对三种,也就是insertService 与 insertA、insertB 都加了事务,如果insertA 和insertB 加了try catch 捕捉了异常,不抛出异常给insertService 会导致报错。
想要了解其他几种事物传播机制的话:
传送门
五、Spring事务失效的场景
场景是这样,同一个类中,一个方法调用另一个事务注释的方法。
class{
public void insertService(T record1, T record2) {
//插入A表
insertA(record1);
//插入B表
insertB(record2);
}
//注意,这两个方法和insertService()在同一个类中
@Transactional
public Long insertA(T record);
@Transactional
public Long insertB(T record);
}
这时候, insertA() 和 insertB() 的事物是不生效的。
原因是:
Spring采用动态代理机制来实现事务控制。在扫描bean时,会对@Transactional 注解的bean生成一个代理子类,实际上调用insertA() 或 insertB() 时,会自动调用代理对象中实现的方法,代理里的方法才是有事务的!而对象内部的方法相互调用,是不会走代理的,默认是this.insertA() 也就是本对象的。
六、Spring事务的xml配置方式
<!-- 配置spring的声明式事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务注解驱动,标注@Transactional的类和方法将具有事务性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="find*" propagation="SUPPORTS"/>
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!--支持基于注解的aspectj -->
<aop:aspectj-autoproxy />
<aop:config>
<aop:pointcut expression="execution(* com.fengyuan.service.*.*(..))" id="mypoint"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypoint"/>
</aop:config>
七、Spring事物的回滚
手动回滚操作如下:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
注意事项:
1、配置spring和springMVC扫描包的时候,一定要明确,springMVC只负责扫描controller层,不要扫描需要配置事物回滚的Service层,这是因为在加载配置时springMVC的配置先于spring,这样的话当spring扫描包的时候发现已经存在(被springMVC先扫描创建了),那么就不会创建对象,因为是由springMVC扫描创建的,自然也就不会使用spring的事物代理,就不能进行回滚。
2、spring事物回滚默认只有在程序出现RuntimeException的时候才进行回滚;
3、如果需要其他异常回滚,那么可以使用如下方式:
//使用rollbackFor(回滚指定的异常)
@Transactional(rollbackFor={Exception.class})
参考:
传送门1
传送门3