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的,server是用新的线程来响应每一个请求的, 每一个请求相应的线程的事务是全然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,则新线程中的方法能够从事务上下文中获取原线程中的数据连接!