spring事务管理是指在业务代码在出现异常之后,对之前的操作进行回滚,保证数据库数据的一致性
-
分为编程式事务管理,配置式,注解式
- 编程式
TransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(def) try{ businessLogic(); transactionManager.commit(status); } catch(Exception ex) { transactionManager. rollback(status); throw ex; }
- xml配置式
<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="create*" propagation="REQUIRED" timeout="300" rollback-for="java.lang.Exception" /> <tx:method name="delete*" propagation="REQUIRED" timeout="300" rollback-for="java.lang.Exception" /> <tx:method name="update*" propagation="REQUIRED" timeout="300" rollback-for="java.lang.Exception" /> <tx:method name="get*" propagation="REQUIRED" read-only="true" timeout="300" /> <tx:method name="*" propagation="REQUIRED" read-only="true" timeout="300" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* com.mico.emptyspring.service.*ServiceA.*(..))" /> <aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice" /> </aop:config>
- 注解式,配置文件上添加配置
<tx:annotation-driven transaction-manager="txManager"/>
,接着在service类或方法上添加@Transactional注解
-
先偷个鸡,事务注解可以放在控制器层吗?
- 可以,但是在spring-framework-reference.pdf文档中有这样一段话:
<tx:annotation-driven/> only looks for @Transactional on beans in the same application context it is defined in. This means that, if you put <tx:annotation-driven/> in a WebApplicationContext for a DispatcherServlet, it only checks for @Transactional beans in your controllers, and not your services.这句话的意思是,<tx:annoation-driven/>只会查找和它在相同的应用上下文文件[同一配置文件]中定义的bean上面的@Transactional注解,如果你把它放在Dispatcher的应用上下文中,它只检查控制器上的@Transactional注解,而不是你services上的@Transactional注解。所以org.springframework.web.servlet.DispatcherServlet
的contextConfigLocation
属性配置的xml中配置<tx:annotation-driven/>则spring只检查控制器上的@Transactional注解,而不是你services上的@Transactional注解。但是,不推荐这么做,嘻嘻
- 可以,但是在spring-framework-reference.pdf文档中有这样一段话:
@Transactional配置项
属性 | 类型 | 描述 |
---|---|---|
value | String | 可选的限定描述符,指定使用的事务管理器 |
propagation | enum: Propagation | 可选的事务传播行为设置 |
isolation | enum: Isolation | 可选的事务隔离级别设置 |
readOnly | boolean | 读写或只读事务,默认读写 |
timeout | int (in seconds granularity) | 事务超时时间设置 |
rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 |
rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
value主要用来指定不同的事务管理器;主要用来满足在同一个系统中,存在不同的事务管理器。比如在Spring中,声明了两种事务管理器txManager1, txManager2.然后,用户可以根据这个参数来根据需要指定特定的txManager.在一个系统中,需要访问多个数据源或者多个数据库,则必然会配置多个事务管理器的
-
脏读,不可重复读和幻象读
- Dirty reads--读脏数据。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。
- non-repeatable reads--数据不可重复读。比如事务A中两处读取数据-total-的值。在第一读的时候,total是100,然后事务B就把total的数据改成 200,事务A再读一次,结果就发现,total竟然就变成200了,造成事务A数据混乱。
- phantom reads--幻象读数据,这个和non-repeatable reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据集被改变了(比如total的数据),但是phantom reads所要读的数据的不一致却不是他所要读的数据集改变,而是他的条件数据集改变。比如Select account.id where account.name="ppgogo*",第一次读去了6个符合条件的id,第二次读取的时候,由于事务b把一个帐号的名字由"dd"改成"ppgogo1",结果取出来了7个数据。
-
事务的隔离级别:是指若干个并发的事务之间的隔离程度
- @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用。
- @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
- @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读),保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
- @Transactional(isolation = Isolation.SERIALIZABLE):最严格的级别,事务串行执行,资源消耗最大;
事务隔离级别和脏读,幻象读,不可重复读
Isolation | Dirty reads | Non-repeatable reads | Phantom reads |
---|---|---|---|
Serializable | 不会 | 不会 | 不会 |
REPEATABLE READ | 不会 | 不会 | 会 |
READ COMMITTED | 不会 | 会 | 会 |
Read Uncommitted | 会 | 会 | 会 |
-
事务传播属性:如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为,即事务方法的嵌套调用会产生事务传播
- 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。
- REQUIRED_NEW和NESTED两种不同的传播机制的区别:
- REQUIRED_NEW:内部的事务独立运行,在各自的作用域中,可以独立的回滚或者提交;而外部的事务将不受内部事务的回滚状态影响.启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
- NESTED:的事务,基于单一的事务来管理,提供了多个保存点。这种多个保存点的机制允许内部事务的变更触发外部事务的回滚。如果外部事务 commit, 嵌套事务也会被 commit;如果外部事务 roll back, 嵌套事务也会被 roll back 。开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交
-
@Transactional 注意事项
- 用在接口实现类或接口实现方法上,而不是接口类中,Spring 建议不要在接口或者接口方法上使用@Transactional注解,因为这只有在使用基于接口的代理时它才会生效
- @Transactional 注解应该只被应用到 public 方法上,
这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。 - 将@Transactional放置在需要进行事务管理的方法上,而不是不假思索的放置在接口实现类上( 接口中所有方法都需要进行事务管理,但其实并不需要,如只读的接口就不需要事务管理,但是 由于配置了@Transactional就需要AOP拦截及事务的处理,影响系统性能)
- 方法上注解属性会覆盖类注解上的相同属性,当接口与接口中方法上同时带有@Transactional注解时,方法上的事务注解会覆盖类上的注解
- 用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException(“注释”);)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception(“注释”);)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚:@Transactional(notRollbackFor=RunTimeException.class)
- 默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰,如下
- 前置条件:同一个类中方法,A方法无@Transactional 注解,B使用了
- 同一个类中A 调用 B ;则外部调用A之后,B的事务是不会起作用的
- 若是有上层(按照 Controller层、Service层、DAO层的顺序)由Controller 调用 Service 直接调用B方法,发生异常会发生回滚;若间接调用,Action 调用 Service 中 的A 方法,A无@Transactional 注解,B有,A调用B,B的注解无效
- 前置条件:同一个类中方法,A方法无@Transactional 注解,B使用了
- 父类的声明的@Transactional会对子类的所有方法进行事务增强;子类覆盖重写父类方式可覆盖其@Transactional中的声明配置
-
多线程下事务管理
- 因为线程不属于spring托管,故线程不能够默认使用spring的事务,也不能获取spring注入的bean
- 在被spring声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制
- 单线程的情况下,一个事务会在层级式调用的Spring组件之间传播。但是在@Transactional注解的服务方法会产生一个新的线程的情况下,事务是不会从调用者线程传播到新建线程的。结果会是被调用者线程会说缺少事务,