Spring事务

spring事务基本配置

参见:http://www.cnblogs.com/leiOOlei/p/3725911.html

spring事务传播机制

参见:http://www.cnblogs.com/aurawing/articles/1887030.html

简单说一下new和nested的区别。
使用new的时候,外层事务的提交或回滚,与new的事务没有关系。而使用nested时,内层事务最终是提交还是回滚,需要依赖于外层事务。参见下表。

事务传播配置 外层事务(set a=1) 内层事务(set b=2) 最终结果
new 提交 提交 a=1 && b=2
new 提交 回滚 a=1
new 回滚 提交 b=2
new 回滚 回滚 什么都不变
nested 提交 提交 a=1 && b=2
nested 提交 回滚 a=1(这种情况需要增加一个配置:<property name="globalRollbackOnParticipationFailure" value="false" />
nested 回滚 提交 什么都不变
nested 回滚 回滚 什么都不变

spring事务隔离机制

参见:Isolation Level(事务隔离等级):

  1. Serializable:最严格的级别,事务串行执行,资源消耗最大;
  2. REPEATABLE READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
  3. READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
  4. Read Uncommitted:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。

我们知道并行可以提高数据库的吞吐量和效率,但是并不是所有的并发事务都可以并发运行,这需要查看数据库教材的可串行化条件判断了。

这里就不阐述了。
我们首先说并发中可能发生的3中不讨人喜欢的事情

  1. Dirty reads--读脏数据。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。
  2. non-repeatable reads--数据不可重复读。比如事务A中两处读取数据-total-的值。在第一读的时候,total是100,然后事务B就把total的数据改成200,事务A再读一次,结果就发现,total竟然就变成200了,造成事务A数据混乱。
  3. phantom reads--幻象读数据,这个和non-repeatable reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据集被改变了(比如total的数据),但是phantom reads所要读的数据的不一致却不是他所要读的数据集改变,而是他的条件数据集改变。比如Select account.id where account.name="ppgogo*",第一次读去了6个符合条件的id,第二次读取的时候,由于事务b把一个帐号的名字由"dd"改成"ppgogo1",结果取出来了7个数据。
-- Dirty reads non-repeatable reads phantom reads
Serializable  不会 不会 不会
REPEATABLE READ 不会 不会
READ COMMITTED 不会
Read Uncommitted
DEFALT(使用底层数据库的默认隔离级别)

spring事务管理其他配置

readOnly

标记事务只读。只读事务会被优化;但是“只读”事务中其实可以写数据。

timeout

事务超时时间

rollbackFor/rollbackForClassName

标记哪些异常会引发回滚。默认情况下,所有RuntimeException都会引发回滚;所有其它异常(checked-exception)都不引发回滚。如果需要针对某种checked-exception进行回滚,则需要为事务配置rollbackFor或者rollbackForClassName

noRollbackFor/noRollbackForClassName

标记哪些异常不会引发回滚。默认情况下,所有RuntimeException都会引发回滚;所有其它异常(checked-exception)都不引发回滚。如果需要使得某种RuntimeException不进行回滚,则需要为事务配置noRollbackFor/noRollbackForClassName。

spring事务机制基本原理

aop

无论配置方式,还是注解方式,spring都是基于spring aop来进行事务管理。
即,在事务切入点处,生成一个动态代理。在代理中管理事务(开启、传播、提交或回滚等)。可参见下面两张图:

Spring事务

Spring事务

两张图都是调用isLocked()方法时的线程栈。可以看到,第二章图是通过动态代理来调用isLocked()方法的,而第一张图则不是。
动态代理的方式简化了代码的开发;但是也引入了一些小问题。后面会提。

线程上下文

spring会将事务相关的有状态数据(数据库连接、hibernate的session等)放在线程上下文中(ThreadLocal)。因此,事务间的传播关系、事务开启和关闭的时机,与线程中的方法调用栈很相似。
另外,由于事务与线程相关,因此目前spring的事务管理无法让多个子线程在同一事务内运行。

如何测试

首先来看看如何确定运行时是否使用了事务、事务是如何传播的。
首先在log4j中加入以下配置。严格说只要记录了org.springframework.transaction.support.AbstractPlatformTransactionManager的日志即可。另一个logger是为了记录SQL的,也可以将它替换成其它logger。

<Logger    name="org.springframework.transaction.support.AbstractPlatformTransactionManager"    level="DEBUG" additivity="false">   
        <AppenderRef ref="Console" />    
        <AppenderRef ref="AuditAsyncAppender" />
</Logger>
<Logger name="org.hibernate.SQL" level="TRACE" additivity="false">
        <AppenderRef ref="Console" /> 
       <AppenderRef ref="AuditAsyncAppender" />
</Logger>

然后运行事务相关代码:

@Testpublic void test() {   
        // 这个方法使用默认的传播方式(REQUIRED)    
        this.lockServiceByDB.tryLock(LockName.EVENT_LOAN, "23424");    
        // 这个方法使用REQUIRES_NEW的传播方式    
        this.lockServiceByDB.isLocked(LockName.EVENT_LOAN, "23424");
}

可以找到如下日志:

2016-07-06 18:01:10,969 DEBUG AbstractPlatformTransactionManager.getTransaction Creating new transaction with name[cn.youcredit.thread.bizaccount.service.impl.LockServiceByDBTestBySpring.test]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '',-cn.youcredit.thread.common.exception.ServiceException
【中略】
2016-07-06 18:01:11,297 DEBUG AbstractPlatformTransactionManager.handleExistingTransaction Participating in existing transaction
2016-07-06 18:01:11,309 DEBUG SqlStatementLogger.logStatement
select count(1) as col_00 from dblocks dblock0 where dblock0.lockName=? and dblock0.uniKey=?
2016-07-06 18:01:11,325 DEBUG SqlStatementLogger.logStatement
insert into db_locks (lockName, lockTimeAsLong, uniKey) values (?, ?, ?)
【中略】
2016-07-06 18:01:11,331 DEBUG AbstractPlatformTransactionManager.handleExistingTransaction Suspending current transaction, creating new transaction with name [cn.youcredit.thread.bizaccount.service.impl.LockServiceByDB.isLocked]
【中略】
2016-07-06 18:01:11,335 DEBUG SqlStatementLogger.logStatement
select count(1) as col_00 from dblocks dblock0 where dblock0.lockName=? and dblock0.uniKey=?
2016-07-06 18:01:11,337 DEBUG AbstractPlatformTransactionManager.processCommit Initiating transaction commit
2016-07-06 18:01:11,337 DEBUG HibernateTransactionManager.doCommit Committing Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList@1a9ea4d9 updates=org.hibernate.engine.spi.ExecutableList@59ca0db6 deletions=org.hibernate.engine.spi.ExecutableList@33293dac orphanRemovals=org.hibernate.engine.spi.ExecutableList@384841e3 collectionCreations=org.hibernate.engine.spi.ExecutableList@571f925f collectionRemovals=org.hibernate.engine.spi.ExecutableList@5eb192b7 collectionUpdates=org.hibernate.engine.spi.ExecutableList@248f1090 collectionQueuedOps=org.hibernate.engine.spi.ExecutableList@5eb20abb unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
【中略】
2016-07-06 18:01:11,339 DEBUG AbstractPlatformTransactionManager.cleanupAfterCompletion Resuming suspended transaction after completion of inner transaction
2016-07-06 18:01:11,340 DEBUG AbstractPlatformTransactionManager.proce***ollback Initiating transaction rollback
2016-07-06 18:01:11,340 DEBUG HibernateTransactionManager.doRollback Rolling back Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[EntityKey[cn.youcredit.thread.common.model.auth.UserInfo#system], EntityKey[cn.youcredit.thread.bizaccount.bean.DBLock#139], EntityKey[cn.youcredit.thread.common.model.auth.UserGroupInfo#500]],collectionKeys=[]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList@2f90ad92 updates=org.hibernate.engine.spi.ExecutableList@4710332d deletions=org.hibernate.engine.spi.ExecutableList@7930de44 orphanRemovals=org.hibernate.engine.spi.ExecutableList@52891a77 collectionCreations=org.hibernate.engine.spi.ExecutableList@785fd189 collectionRemovals=org.hibernate.engine.spi.ExecutableList@3e900cf4 collectionUpdates=org.hibernate.engine.spi.ExecutableList@412d379c collectionQueuedOps=org.hibernate.engine.spi.ExecutableList@5b6dc76c unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]

从日志中可以清楚的看到事务操作、传播的过程:

  1. 首先,运行LockServiceByDBTestBySpring.test()方法时,创建了一个新事务。这是SpringJUnit4Cla***unner的事务逻辑,即每一个单元测试都会启动一个事务,并且默认情况下,该事务会回滚。
  2. 然后,在执行到this.lockServiceByDB.tryLock()方法时,由于传播方式是REQUIRED,因此会加入当当前已有的事务中,并执行两条sql。
  3. 接着执行this.lockServiceByDB.isLocked()方法。由于这个方法的事务传播方式是REQUIRES_NEW,因此会挂起当前事务,并创建一个新的事务,在新的事务中执行一条SQL。
  4. 新事务执行并提交之后,恢复被挂起的上层事务。并继续执行。由于后面没有其它逻辑、代码,因此开始回滚外层事务。

常见问题

事务标记在protcted或private方法上,导致事务失效

spring的事务注解只对public方法生效,对protcted、friendly(默认)、private方法无效。如果在后面这些方法上标记事务注解,其效果等于没有标记。

注解继承不当,导致事务失效

Java注解的基本继承关系如下。spring的Transactional注解上有Inherited的元注解。

- 未写@Inherited: 写了@Inherited的
子类的类上能否继承到父类的类上的注解?
子类方法,实现了父类上的抽象方法,这个方法能否继承到注解?
子类方法,继承了父类上的方法,这个方法能否继承到注解?
子类方法,覆盖了父类上的方法,这个方法能否继承到注解?

例如下面的代码中,虽然Son在类级别上声明了事务,但是它的tryLocked()方法并不会启动新的事务。因为它在父类中没有声明事务。

public class Father{
    public void tryLocked(){}
}

@Transactional(REQUIRES_NEW)
public class Son extends Father{
}

使用this方式调用,导致事务失效

对于spring aop的动态代理来说,被代理实例和方法是一个“黑盒”。只有在“黑盒”之外才能进行事务管理。而this调用是黑盒内部的调用逻辑,代理无法感知。
因此,像下文这样的代码中,isLocked()方法并不会启动新的事务。

@Transactional(REQUIRES_NEW)
    public void isLocked(){
        ……
    } 
    @Transactional()
    public void tryLock(){ 
        this.isLocked(); 
        ……
    }

事务与查询

虽然查询操作并不更新数据,但是查询也需要事务。尤其对hibernate来说。

hibernate操作数据库时,需要获取到一个hibernate的session。而这个session也由HibernateTransactionManager来管理。管理的方式与其它有状态数据一样,都是放在ThreadLocal中。如果线程上没有事务管理器,那么就拿不到session。hibernate做查询时就会报错:

org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:134) ~[spring-orm-4.2.2.RELEASE.jar:4.2.2.RELEASE]

有时候明明没有写事务注解,也能执行查询,那么多半是在某个地方默认、或者“偷偷”开了事务。比如继承SpringTestCase的单元测试会默认开启事务。或者web环境下,线程池中的线程上遗留了以前绑定的事务管理器。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要介绍了spring的事务传播属性REQUIRED_NESTED的原理介绍,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。传统事务中回滚点的使...
今天小编给大家分享的是一文解析spring中事务的传播机制,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获...
这篇文章主要介绍了SpringCloudAlibaba和SpringCloud有什么区别,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。Spring Cloud Netfli...
本篇文章和大家了解一下SpringCloud整合XXL-Job的几个步骤。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。第一步:整合pom文件,在S...
本篇文章和大家了解一下Spring延迟初始化会遇到什么问题。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。List 坑列表 = new ArrayList(2);...
这篇文章主要介绍了怎么使用Spring提供的不同缓存注解实现缓存的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇...
本篇内容主要讲解“Spring中的@Autowired和@Resource注解怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学...
今天小编给大家分享一下SpringSecurity怎么定义多个过滤器链的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家
这篇文章主要介绍“Spring的@Conditional注解怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring的@Con...
这篇文章主要介绍了SpringCloudGateway的熔断限流怎么配置的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇SpringCloud&nb...
今天小编给大家分享一下怎么使用Spring解决循环依赖问题的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考
这篇文章主要介绍“Spring事务及传播机制的原理及应用方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Sp...
这篇“SpringCloudAlibaba框架实例应用分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价
本篇内容主要讲解“SpringBoot中怎么使用SpringMVC”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习...
这篇文章主要介绍“SpringMVC适配器模式作用范围是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“SpringMVC
这篇“导入SpringCloud依赖失败如何解决”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家...
这篇文章主要讲解了“SpringMVC核心DispatcherServlet处理流程是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来
今天小编给大家分享一下SpringMVCHttpMessageConverter消息转换器怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以...
这篇文章主要介绍“Spring框架实现依赖注入的原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Spring框架...
本篇内容介绍了“Spring单元测试控制Bean注入的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下