事务之二:spring事务(事务管理方式,事务5隔离级别,7个事务传播行为,spring事务回滚条件)
www.cnblogs.com/duanxz/p/34…
blog.csdn.net/qqqqq1993qq…
事物管理对于企业应用来说是至关重要的,好使出现异常情况,它也可以保证数据的一致性。
spring支持编程式事务管理和声明式事务管理两种方式。
编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。
spring事务特性
spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口
其中TransactionDefinition接口定义以下特性:
事务隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
- TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。(注意这里,不要采坑)
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
PROPAGATION_REQUIRES_NEW与PROPAGATION_NESTED的区别:
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll back.
事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
事务只读属性
只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。
“只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。
但是你非要在“只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像“读写事务”那样保险而已。
因此,“只读事务”仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可。
spring事务回滚规则
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
示例:基于注解的声明式事务管理配置@Transactional
spring.xml
<bean id\="sqlSessionFactory" class\="org.mybatis.spring.SqlSessionFactoryBean"\>
<property name\="dataSource" ref\="dataSource" />
<property name\="configLocation"\>
<value\>classpath:mybatis-config.xml</value\>
</property\>
</bean\>
<!-- mybatis mappers, scanned automatically \-->
<bean class\="org.mybatis.spring.mapper.MapperScannerConfigurer"\>
<property name\="basePackage"\>
<value\> com.baobao.persistence.test </value\>
</property\>
<property name\="sqlSessionFactory" ref\="sqlSessionFactory" />
</bean\>
<!-- 配置spring的PlatformTransactionManager,名字为默认值 \-->
<bean id\="transactionManager" class\="org.springframework.jdbc.datasource.DataSourceTransactionManager"\>
<property name\="dataSource" ref\="dataSource" />
</bean\>
<!-- 开启事务控制的注解支持 \-->
<tx:annotation-driven transaction-manager\="transactionManager"/></span\></span\>
添加tx名字空间
xmlns:aop="www.springframework.org/schema/aop" xmlns:tx="www.springframework.org/schema/tx"
xsi:schemaLocation="www.springframework.org/schema/aop www.springframework.org/schema/aop/…
www.springframework.org/schema/tx www.springframework.org/schema/tx/s…"
MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
@Transactional注解
使用@Transactional时,可以指定如下属性:****
** a、isolation:用于指定事务的隔离级别。默认为底层事务的隔离级别。
b、noRollbackFor:指定遇到指定异常时强制不回滚事务。
c、noRollbackForClassName:指定遇到指定多个异常时强制不回滚事务。该属性可以指定多个异常类名。
d、propagation:指定事务的传播属性。
e、readOnly:指定事务是否只读。
f、rollbackFor:指定遇到指定异常时强制回滚事务。
g、rollbackForClassName:指定遇到指定多个异常时强制回滚事务。该属性可以指定多个异常类名。
h、timeout:指定事务的超时时长。**
**事务可回滚要注意点
**
1、被注解的必须是public
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
poxy-target-class="true"表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
2、rollbackFor和noRollbackFor
需我们指定方式来让事务回滚 :
要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常})
如果让unchecked例外不回滚:@Transactional(notRollbackFor=RunTimeException.class)
(关于rollbackFor配置的经历)
当的Transactional中配置rollbackFor = Exception.class时,抛出RuntimeException时是会回滚的。但是如果是Unchecked Exceptions则不会回滚。
于是查看Spring的Transactional的API文档,发现下面这段:
If no rules are relevant to the exception, it will be treated like DefaultTransactionAttribute (rolling back on runtime exceptions).
后面又试了下发现,如果不添加rollbackFor等属性,Spring碰到Unchecked Exceptions都会回滚,不仅是RuntimeException,也包括Error。
3、来自外部的方法调用才会被AOP代理捕获
默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。(我碰到后,只有重构代码再引入一层service解决)
示例一:(不要rollbackFor,unchecked异常,可以回滚)
@Autowired private MyBatisDao dao;
@Transactional
@Override public void insert(Test test) {
dao.insert(test); throw new RuntimeException("test");//抛出unchecked异常,触发事物,回滚
}
示例二:(noRollbackFor使用场景**)**
@Transactional(noRollbackFor=RuntimeException.class)
@Override public void insert(Test test) {
dao.insert(test); //抛出unchecked异常,触发事物,noRollbackFor=RuntimeException.class,不回滚
throw new RuntimeException("test");
}
示例三:当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性。
@Transactional public class MyBatisServiceImpl implements MyBatisService {
@Autowired private MyBatisDao dao;
@Override public void insert(Test test) {
dao.insert(test); //抛出unchecked异常,触发事物,回滚
throw new RuntimeException("test");
}
}
示例四:propagation=Propagation.NOT_SUPPORTED
@Transactional(propagation=Propagation.NOT_SUPPORTED)
@Override public void insert(Test test) { //事物传播行为是PROPAGATION_NOT_SUPPORTED,以非事务方式运行,不会存入数据库
dao.insert(test);
}
事务之六:spring 嵌套事务
一、基本概念
事务的隔离级别,事务传播行为见《事务之二:spring事务(事务管理方式,事务5隔离级别,7个事务传播行为,spring事务回滚条件) 》
二、 嵌套事务示例
2.1、Propagation.REQUIRED+Propagation.REQUIRES_NEW
package dxz.demo1;
@Service public class ServiceAImpl implements ServiceA {
@Autowired private ServiceB serviceB;
@Autowired private VcSettleMainMapper vcSettleMainMapper;
@Override
@Transactional(propagation \= Propagation.REQUIRED, readOnly = false) public void methodA() {
String id \= IdGenerator.generatePayId("A");
VcSettleMain vc \= buildModel(id);
vcSettleMainMapper.insertVcSettleMain(vc);
System.out.println("ServiceAImpl VcSettleMain111:" + vc);
serviceB.methodB();
VcSettleMain vc2 \= buildModel(id);
vcSettleMainMapper.insertVcSettleMain(vc2);
System.out.println("ServiceAImpl VcSettleMain22222:" + vc2);
} private VcSettleMain buildModel(String id) {
VcSettleMain vc \= new VcSettleMain();
vc.setBatchNo(id);
vc.setCreateBy("dxz");
vc.setCreateTime(LocalDateTime.now());
vc.setTotalCount(11L);
vc.setTotalMoney(BigDecimal.ZERO);
vc.setState("5"); return vc;
}
}
ServiceB
package dxz.demo1;
@Service public class ServiceBImpl implements ServiceB {
@Autowired private VcSettleMainMapper vcSettleMainMapper;
@Override
@Transactional(propagation \= Propagation.REQUIRES\_NEW, readOnly = false) public void methodB() {
String id \= IdGenerator.generatePayId("B");
VcSettleMain vc \= buildModel(id);
vcSettleMainMapper.insertVcSettleMain(vc);
System.out.println("---ServiceBImpl VcSettleMain:" + vc);
}
}
controller
package dxz.demo1;
@RestController @RequestMapping("/dxzdemo1") @Api(value = "Demo1", description="Demo1") public class Demo1 { @Autowired private ServiceA serviceA; /** * 嵌套事务测试 */ @PostMapping(value = "/test1") public String methodA() throws Exception { serviceA.methodA(); return "ok"; } }
结果:
看数据库表记录:
这种情况下, 因为 ServiceB#methodB 的事务属性为 PROPAGATION_REQUIRES_NEW,ServiceB是一个独立的事务,与外层事务没有任何关系。如果ServiceB执行失败(上面示例中让ServiceB的id为已经存在的值),ServiceA的调用出会抛出异常,导致ServiceA的事务回滚。
并且, 在 ServiceB#methodB 执行时 ServiceA#methodA 的事务已经挂起了 (关于事务挂起的内容已经超出了本文的讨论范围)。
2.2、Propagation.REQUIRED+Propagation.REQUIRED
//ServiceA //... @Override @Transactional(propagation = Propagation.REQUIRED, readOnly = false) public void methodA() { //ServiceB //... @Override @Transactional(propagation = Propagation.REQUIRED, readOnly = false) public void methodB(String id) { //...
--“1”可插入,“2”可插入,“3”不可插入:
结果是“1”,“2”,“3”都不能插入,“1”,“2”被回滚。
--“1”可插入,“2”不可插入,“3”可插入:
结果是“1”,“2”,“3”都不能插入,“1”,“2”被回滚。
2.3、Propagation.REQUIRED+无事务注解
//ServiceA //... @Override @Transactional(propagation = Propagation.REQUIRED, readOnly = false) public void methodA() { //ServiceB //... @Override //没有加事务注解 public void methodB(String id) { //...
--“1”可插入,“2”可插入,“3”不可插入:
结果是“1”,“2”,“3”都不能插入,“1”,“2”被回滚。
2.4、内层事务被try-catch:
2.4.1、trycatch+Propagation.REQUIRED+Propagation.REQUIRED
//ServiceA //... @Override @Transactional(propagation = Propagation.REQUIRED, readOnly = false) public void methodA() { try { serviceB.methodB(id); } catch (Exception e) { System.out.println("内层事务出错啦。"); } } //ServiceB //... @Override @Transactional(propagation = Propagation.REQUIRED, readOnly = false) public void methodB(String id) { //...
--“1”可插入,“2”不可插入,“3”可插入:
结果是“1”,“2”,“3”都不能插入,“1”被回滚。
事务设置为Propagation.REQUIRED时,如果内层方法抛出Exception,外层方法中捕获Exception但是并没有继续向外抛出,最后出现“Transaction rolled back because it has been marked as rollback-only”的错误。外层的方法也将会回滚。
其原因是:内层方法抛异常返回时,transacation被设置为rollback-only了,但是外层方法将异常消化掉,没有继续向外抛,那么外层方法正常结束时,transaction会执行commit操作,但是transaction已经被设置为rollback-only了。所以,出现“Transaction rolled back because it has been marked as rollback-only”错误。
2.4.2、trycatch+Propagation.REQUIRED+Propagation.NESTED
//ServiceA //... @Override @Transactional(propagation = Propagation.REQUIRED, readOnly = false) public void methodA() { try { serviceB.methodB(id); } catch (Exception e) { System.out.println("内层事务出错啦。"); } } //ServiceB //... @Override @Transactional(propagation = Propagation.NESTED, readOnly = false) public void methodB(String id) { //...
--“1”可插入,“2”不可插入,“3”可插入:
结果是“1”,“3"记录插入成功,“2”记录插入失败。
说明:
当内层配置成 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? 从 Juergen Hoeller 的原话中我们可以找到答案, ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint(注意, 这是本文中第一次提到它, 潜套事务中最核心的概念), 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:
1、内层失败,外层调用其它分支,代码如下
ServiceA { /** * 事务属性配置为 PROPAGATION_REQUIRED */
void methodA() { try {
ServiceB.methodB();
} catch (SomeException) { // 执行其他业务, 如 ServiceC.methodC();
}
}
}
这种方式也是潜套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。
2. 代码不做任何修改, 那么如果内部事务(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback。
三、嵌套事务总结
使用嵌套事务的场景有两点需求:
- 需要事务BC与事务AD一起commit,即:作为事务AD的子事务,事务BC只有在事务AD成功commit时(阶段3成功)才commit。这个需求简单称之为“联合成功”。这一点PROPAGATION_NESTED和PROPAGATION_REQUIRED可以做到。
- 需要事务BC的rollback不(无条件的)影响事务AD的commit。这个需求简单称之为“隔离失败”。这一点PROPAGATION_NESTED和PROPAGATION_REQUIRES_NEW可以做到。
分解下,可知PROPAGATION_NESTED的特殊性有:
1、使用PROPAGATION_REQUIRED满足需求1,但子事务BC的rollback会无条件地使父事务AD也rollback,不能满足需求2。即使对子事务进行了try-catch,父事务AD也不能commit。示例见2.4.1、trycatch+Propagation.REQUIRED+Propagation.REQUIRED
2、使用PROPAGATION_REQUIRES_NEW满足需求2,但子事务(这时不应该称之为子事务)BC是完全新的事务上下文,父事务(这时也不应该称之为父事务)AD的成功与否完全不影响BC的提交,不能满足需求1。
同时满足上述两条需求就要用到PROPAGATION_NESTED了。PROPAGATION_NESTED在事务AD执行到B点时,设置了savePoint(关键)。
当BC事务成功commit时,PROPAGATION_NESTED的行为与PROPAGATION_REQUIRED一样。只有当事务AD在D点成功commit时,事务BC才真正commit,如果阶段3执行异常,导致事务AD rollback,事务BC也将一起rollback ,从而满足了“联合成功”。
当阶段2执行异常,导致BC事务rollback时,因为设置了savePoint,AD事务可以选择与BC一起rollback或继续阶段3的执行并保留阶段1的执行结果,从而满足了“隔离失败”。
当然,要明确一点,事务传播策略的定义是在声明或事务管理范围内的(首先是在EJB CMT规范中定义,Spring事务框架补充了PROPAGATION_NESTED),编程式的事务管理不存在事务传播的问题。
四、PROPAGATION_NESTED的必要条件
上面大致讲述了潜套事务的使用场景, 下面我们来看如何在 spring 中使用 PROPAGATION_NESTED, 首先来看 AbstractPlatformTransactionManager
/** * Create a TransactionStatus for an existing transaction. */
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException {
... 省略 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { if (!isNestedTransactionAllowed()) { throw new NestedTransactionNotSupportedException( "Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
} if (debugEnabled) {
logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
} if (useSavepointForNestedTransaction()) { // Create savepoint within existing Spring-managed transaction, // through the SavepointManager API implemented by TransactionStatus. // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
DefaultTransactionStatus status = newTransactionStatus(definition, transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint(); return status;
} else { // Nested transaction through nested begin and commit/rollback calls. // Usually only for JTA: Spring synchronization might get activated here // in case of a pre-existing JTA transaction.
doBegin(transaction, definition); boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER); return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);
}
}
}
**1. 我们要设置 transactionManager 的 nestedTransactionAllowed 属性为 true, 注意, 此属性默认为 false!!! **
再看 AbstractTransactionStatus#createAndHoldSavepoint() 方法
/** * Create a savepoint and hold it for the transaction.
* @throws org.springframework.transaction.NestedTransactionNotSupportedException
* if the underlying transaction does not support savepoints */
public void createAndHoldSavepoint() throws TransactionException {
setSavepoint(getSavepointManager().createSavepoint());
}
可以看到 Savepoint 是 SavepointManager.createSavepoint 实现的, 再看 SavepointManager 的层次结构, 发现
其 Template 实现是 JdbcTransactionObjectSupport, 常用的 DatasourceTransactionManager, HibernateTransactionManager
中的 TransactonObject 都是它的子类 :
JdbcTransactionObjectSupport 告诉我们必须要满足两个条件才能 createSavepoint :
** 2. java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+ **
** 3. Connection.getMetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0 **
确保以上条件都满足后, 你就可以尝试使用 PROPAGATION_NESTED 了。