Spring通过DataSourceTransactionManager为使用者提供事务管理,并提供以下参数使用户可以根据业务场景进行个性化的事务配置。
传播行为propagation:
Spring的事务传播行为分为7种。分别为PROPAGATION_REQUIRED(XML文件中为REQUIRED),PROPAGATION_SUPPORTS(XML文件中为SUPPORTS),PROPAGATION_MANDATORY(XML文件中为MANDATORY),PROPAGATION_NESTED(XML文件中为NESTED)
,PROPAGATION_NEVER(XML文件中为NEVER),PROPAGATION_REQUIRES_NEW(XML文件中为REQUIRES_NEW),PROPAGATION_NOT_SUPPORTED(XML文件中为NOT_SUPPORTED)。
这些传播行为分别是什么含义,可以从spring 事务传播行为实例分析这篇文章了解,非常详细且有例子说明。
事务的传播机制指当前事务部分对其外层事务部分的影响。在刚才spring 事务传播行为实例分析这篇文章中可以多次看到Transaction rolled back because it has been marked as rollback-only这个异常。即事务被回滚,因为他已经被标记为只可回滚。为什么会有这个异常呢,假设有两个方法methodA和methodB()成methodA{ methodB();}结构,methodB的传播行为为REQUIRED,methodA和methodB在同一事务中。在methodB出现异常被catch掉导致methodA无法感知到methodB的异常时就会出现marked as rollback-only这异常。处理流程:
Sring的事务管理依赖于AOP,由在切点处是否有符合条件的异常来判断是否回滚。而不是根据数据是否存储成功来判断是否回滚。当数据存储失败(单纯的数据保存,不存在传播行为)且异常在方法内部被catch掉使得AOP无法感知到异常时,事务依然会进行提交操作(而不是回滚)且不会产生异常。
隔离级别isolation:事务之间的隔离级别。数据更新时因为并发的问题会导致各种各样的问题:
1.更新丢失.多个事务对同一条数据操作。在事务A未提交时,事务B进行操作,此时后提交的事务就会覆盖掉先提交的事务所做的更新,造成了更新丢失。如银行账户.用户A账户中有10元钱。同时受到用户B转账的10元和用户C转账的20元。此时如果我们使用sql.1{UPDATE USERACCOUNT SET ACCOUNT = 20 WHERE USERNAME = A}与sql.2{UPDATE USERACCOUNT SET ACCOUNT = 30 WHERE USERNAME = A}.此时无论A先提交还是B先提交都会导致先提交的那个事务所做的更改被覆盖。事实上,很多数据库都会默认在底层加锁来避免这种现象发生。
2.脏读。一个事务查询到另一个事务未提交的数据,如果再查询之后另一个事务的更新操作被回滚就会出现脏读现象。如银行账户,用户A想买了价值1000元的商品。此时用户A余额为900元。于是就用户B给用户A转账100元。在转账事务未提交的时间,用户A看到自己账户以及变为1000元就去付款。此时B给A的转账事务被回滚。那么A付款时就会出现余额不足的清况。脏读是非常危险的。因为其读取到一个未提交的数据。一旦这个未提交的数据被回滚。那么以此查询为基础的之后任何操作在链式反应下全部都会得到期望之外的结果。
3.不可重复读。一个事务对同一条数据的两次查询返回了不同的结果。同样以账户为例。在一个事务T里面,用户A第一次查询账户余额为1000元。查询之后用户B转账给用户A100元。此时在事务T里面再次查询用户A的余额得到为1100元。在同一个事务里面对同一条数据的两次查询获得了不同的结果就产生了不可重复读。
4.幻读。在同一个事务中,条件相同的两次查询得到不同的结果。比如说在事务T中。查询所有姓A的客户的数量,获得结果为999位,此时又有一位A姓客户注册了,那么在事务T中再次查询姓A的客户的数量时获得结果为1000.此时便出现了幻读现象。
不可重复读和幻读都必须是在同一个事务中内的。不同的是不可重复读是对同一条数据的查询。幻读是相同条件下的一批数据的查询。
针对不同场景下对数据有不同的错误容忍度。spring的事务管理器提供了5种事务隔离级别。XML方式下通过isolation来设置:
- DEFAULT:默认级别。此种事务级别下spring的事务隔离级别将采用数据库的事务隔离策略。即数据数据库采用哪种隔离级别,spring的事务管理器也采用哪种事务管理级别。
- READ_UNCOMMITTED:读未提交。此种隔离级别下未提交的的数据可以被查询到。会出现脏读,不可重复读,幻读。
- READ_COMMITTED:读已提交。此种隔离级别下未提交的数据将不可被查询到。只有已提交的数据才可查询到。此种隔离级别不会出现脏读,但是会出现不可重复读和幻读。
- REPEATABLE_READ:可重复读。此种隔离级别下不会出现脏读和不可重复读,但是会出现幻读。
- SERIALIZABLE:序列化。最高的隔离级别。此种情况下脏读,不可重复读,幻读都不会出现。
隔离级别的提升势必会带来性能的下降。因为要花费更多的资源到事务隔离上。
回滚条件no-rollback-for与rollback-for:当AOP感知到何种异常时不执行回滚策略或者执行回滚策略。
XML方式通过rollback-for和no-rollback-for设置,value为字符串,填入异常名即可。eg:rollback-for="ClassNotFoundException".注解的方式提供noRollbackFor和rollbackFor可以通过异常类的Class来设置。同时依然可以通过rollbackForClassName和noRollbackForClassName来用String方式数据异常名来设置。
对于no-rollback-for,其覆盖范围为指定的Exception极其子类。而对于rollback-for则包括其子类以及RuntimeException及其子类.也就是说对于rollback-for,除了指定的Exception,RuntimeException是默认在覆盖范围的,用户无法通过rollback-for配置其它异常使得RuntimeException无法回滚。
示例:
事务超时时间timeout:事务的超时时间。即事务可允许的最大执行时间。
单位为秒。默认为-1.即无限制。事务执行超时时会抛出异常org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Oct 26 16:42:41 CST 2018。Fri Oct 26 16:42:41 CST 2018这个时间是事务的最后期限。即事务应该在这个时间之前提交。
只读read-only:此事务是否为已读事务。
默认为false。设置为true时并不代表着此事务将只能读不能写,他对读写操作没有任何限制。这只是一个标识,告诉事务管理器这个事务是已读的,让事务管理器可以尝试使用只读的策略来优化这个事务。
配置:Spring提供了两种方式(注解和XML)配置事务管理
XML方式通过Execution表达式对不同的包和文件设置不同的切入规则。
<aop:config proxy-target-class="true">
<aop:advisor pointcut="execution(* com.pck1.*.*(..))" advice-ref="txAdvice" />
<aop:advisor pointcut="execution(* com.pck2.*.*(..)) || execution(* com.pck3..*(..))" advice-ref="txAdvice2" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="find*" propagation="REQUIRED" />
<tx:method name="get*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<tx:advice id="txAdvice2" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*TxManagerUnit" propagation="REQUIRED"/>
<tx:method name="*PropagationNew" propagation="REQUIRES_NEW" />
<tx:method name="*PropagationNested" propagation="NESTED" />
</tx:attributes>
</tx:advice>
XML的方式通过Execution表达式来设置切点,可以设置多个不同的规则(一般为后缀,前缀,*(匹配所有)三种),如果一个method满足多个规则。那么后缀规则优先于前缀,前缀规则优先于"*"(all)。
注解的方式通过@Transactional注解来实现事务规则设置
@Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED)
因为spring事务管理是通过AOP来实现事务节点。线程池来执行任务并不会影响到AOP所以也就不会影响到事务管理。但是对于AOP无法环绕的地方(没有设置AOP或者使用this.method()来调用指定方法)是无法实现事务管理的,对应的事务设置不会起作用。