Spring声明式事务

来源:互联网 发布:淘宝真实收货地址大全 编辑:程序博客网 时间:2024/05/17 21:57
Spring框架对事务管理提供了两种方式支持
1> 编程式事务:TransactionTemplate类(推荐使用),基于PlatformTransactionManager类来实现事务管理;
2> 声明式事务:基于AOP实现(动态代理方式织入事务、提交/回滚事务),只需要要配置文件中配置好事务规则(tx及aop配置或使用@Transactional注解),注解方式开发简洁推荐使用。

编程式事务需要手工处理事务的开启、提交、回滚,加大开发工作量,事务控制力度更细,可以是方法中的代码块级(声明式事务也可以通过将代码块独立成方法实现相同的功能)。
声明式事务是非侵入式的(普通的java类加上注解即可获取事务支持),事务控制最细力度是方法级别;

事务实现机制
在beginTransaction阶段(hiberante框架是beginTransaction(),spring框架是doBegin()),spring底层实现是会将Connection对象的autoCommit设置为false,取消自动提交,然后在endTransaction阶段来提交/回滚。

事务隔离级别(与数据库四个级别一致)
1> TransactionDefinition.ISOLATION_DEFAULT    # 这是默认值,表示使用底层数据库的默认隔离级别。大部分数据库通常默认是:READ_COMMITTED读已提交;
2> TransactionDefinition.ISOLATION_READ_UNCOMMITTED    # 一个事务可以读取另一个事务修改但还没有提交的数据。
     存在问题:该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
3> TransactionDefinition.ISOLATION_READ_COMMITTED        # 一个事务只能读取另一个事务已经提交的数据。可以防止脏读,这也是大多数情况下的推荐值。
4> TransactionDefinition.ISOLATION_REPEATABLE_READ      # 一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。可以防止脏读和不可重复读。
5> TransactionDefinition.ISOLATION_SERIALIZABLE    # 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,可以防止脏读、不可重复读以及幻读。
     但是这将严重影响程序的性能。通常情况下也不会用到该级别。

脏读(Dirty read):脏读发生在一个事务读取了被另一个事务改写但还未提交的数据时。如果这些改变在稍后被回滚,那么之前的事务读取的到数据就是无效的。
不可重复读(Nonrepeatable read):不可重复读发生在一个事务执行相同的查询两次或两次以上,但每一次的查询结果不同时。这通常是由于另一个并发的事务在两次查询之间更新了数据。
幻读(Phantom read):幻读是一个事务读取几行记录后,另一个事务插入了一些记录,幻读就发生了。在后来的查询中第一个事务就会发现有一些原来没有的额外的记录。

事务传播行为(TransactionDefinition
1> PROPAGATION_MANDATORY    该方法必须运行在一个事务中。如果当前事务不存在则抛出异常。
2> PROPAGATION_NESTED    # 如果当前存在一个事务,则该方法运行在一个嵌套的事务中。被嵌套的事务可以从当前事务中单独的提交和回滚。如果当前不存在事务,则开始一个新的事务。各厂商对这种传播行为的支持参差不齐,使用时需注意。 
3> PROPAGATION_NEVER      # 当前方法不应该运行在一个事务中。如果当前存在一个事务,则抛出异常。
4> PROPAGATION_NOT_SUPPORTED    # 当前方法不应该运行在一个事务中。如果一个事务正在运行,它将在该方法的运行期间挂起。
5> PROPAGATION_REQUIRED    # 该方法必须运行在一个事务中。如果一个事务正在运行,该方法将运行在这个事务中。否则,就开始一个新的事务。
6> PROPAGATION_REQUIRES_NEW    # 该方法必须运行在自己的事务中。它将启动一个新的事务。如果一个现有的事务正在运行,将在这个方法的运行期间挂起。
7> PROPAGATION_SUPPORTS    # 当前方法不需要事务处理环境,但如果一个事务已经在运行的话,这个方法也可以在这个事务里运行。

@Transactional异常回滚策略
Spring默认情况下会对运行时异常(RunTimeException)进行事务回滚(unchecked),如果遇到checked意外就不回滚。这个默认规则是可以调整的,如下:
1> @Transactional(rollbackFor=Exception.class)                            # checked及unchecked异常都回滚
2> @Transactional(notRollbackFor=RunTimeException.class)       # 让unchecked例外不回滚(checked、unchecked都不回滚)

Spring事务与线程安全
spring管理的transaction是ThreadLocal的,服务器是用新的线程来响应每个请求的, 每个请求对应的线程的事务是完全ThreadLocal的, 也就是事务是完全和线程想关的, 一个线程的事务回滚 是不影响其他线程的。数据库连接Connection在不同线程中是不能共享的,事务管理器为不同的事务线程利用ThreadLocal类提供独立的Connection副本。
关键源码(类和方法):
org.springframework.transaction.interceptor.TransactionInterceptor.invoke
org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary       # transactionInfoHolder类型为ThreadLocal<TransactionInfo>
org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction
结论
1> Spring中DAO和Service都是以单实例的bean形式存在,Spring通过ThreadLocal类将有状态的变量(例如数据库连接Connection)本地线程化,从而做到多线程状况下的安全。在一次请求响应的处理线程中, 该线程贯通展示、服务、数据持久化三层,通过ThreadLocal使得所有关联的对象引用到的都是同一个变量。
2> 在事务属性为REQUIRED时,在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果互相嵌套调用的事务方法工作在不同线程中,则不同线程下的事务方法工作在独立的事务中。
3> 程序只要使用SpringDAO模板,例如JdbcTemplate进行数据访问,一定没有数据库连接泄露问题!如果程序中显式的获取了数据连接Connection,则需要手工关闭它,否则就会泄露!
4> 当Spring事务方法运行时,就产生一个事务上下文,它在本事务执行线程中对同一个数据源绑定了一个唯一的数据连接,所有被该事务上下文传播的方法都共享这个连接。要获取这个连接,如要使用Spirng的资源获取工具类DataSourceUtils。
5> 事务管理上下文就好比一个盒子,所有的事务都放在里面。如果在某个事务方法中开启一个新线程,新线程中执行另一个事务方法,则由上面第二条可知这两个方法运行于两个独立的事务中,但是:如果使用DataSourcesUtils,则新线程中的方法可以从事务上下文中获取原线程中的数据连接!
0 0
原创粉丝点击