原文出处: 张开涛html
事务首先是一系列操做组成的工做单元,该工做单元内的操做是不可分割的,即要么全部操做都作,要么全部操做都不作,这就是事务。java
事务必需知足ACID(原子性、一致性、隔离性和持久性)特性,缺一不可:程序员
在实际项目开发中数据库操做通常都是并发执行的,即有多个事务并发执行,并发执行就可能遇到问题,目前常见的问题以下:面试
为了解决这些并发问题,须要经过数据库隔离级别来解决,在标准SQL规范中定义了四种隔离级别:spring
隔离级别越高,数据库事务并发执行性能越差,能处理的操做越少。所以在实际项目开发中为了考虑并发性能通常使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但能够在可能出现的场合使用悲观锁或乐观锁来解决这些问题。sql
数据库事务类型有本地事务和分布式事务:数据库
Java事务类型有JDBC事务和JTA事务:express
Java EE事务类型有本地事务和全局事务:编程
按是否经过编程实现事务有声明式事务和编程式事务;后端
Spring框架最核心功能之一就是事务管理,并且提供一致的事务管理抽象,这能帮助咱们:
Spring支持声明式事务和编程式事务事务类型。
spring事务特性
spring全部的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口
其中TransactionDefinition接口定义如下特性:
事务隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
事务传播行为
所谓事务的传播行为是指,若是在开始当前事务以前,一个事务上下文已经存在,此时有若干选项能够指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了以下几个表示传播行为的常量:
事务超时
所谓事务超时,就是指一个事务所容许执行的最长时间,若是超过该时间限制但事务尚未完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,若是底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
事务只读属性
只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,好比使用Hibernate的时候。
默认为读写事务。
Spring框架支持事务管理的核心是事务管理器抽象,对于不一样的数据访问框架(如Hibernate)经过实现策略接口PlatformTransactionManager,从而能支持各类数据访问框架的事务管理,PlatformTransactionManager接口定义以下:
java代码:
public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
TransactionDefinition接口定义以下:
java代码:
public interface TransactionDefinition { int getPropagationBehavior(); int getIsolationLevel(); int getTimeout(); boolean isReadOnly(); String getName(); }
TransactionStatus接口定义以下:
java代码:
public interface TransactionStatus extends SavepointManager { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); void flush(); boolean isCompleted(); }
Spring提供了许多内置事务管理器实现:
Spring不只提供这些事务管理器,还提供对如JMS事务管理的管理器等,Spring提供一致的事务抽象如图9-1所示。
图9-1 Spring事务管理器
接下来让咱们学习一下如何在Spring配置文件中定义事务管理器:
1、声明对本地事务的支持:
a)JDBC及iBATIS、MyBatis框架事务管理器
java代码:
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
经过dataSource属性指定须要事务管理的单个javax.sql.DataSource对象。
b)Jdo事务管理器
java代码:
<bean id="txManager" class="org.springframework.orm.jdo.JdoTransactionManager"> <property name="persistenceManagerFactory" ref="persistenceManagerFactory"/> </bean>
经过persistenceManagerFactory属性指定须要事务管理的javax.jdo.PersistenceManagerFactory对象。
c)Jpa事务管理器
java代码:
bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean>
经过entityManagerFactory属性指定须要事务管理的javax.persistence.EntityManagerFactory对象。
还须要为entityManagerFactory对象指定jpaDialect属性,该属性所对应的对象指定了如何获取链接对象、开启事务、关闭事务等事务管理相关的行为。
java代码:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> …… <property name="jpaDialect" ref="jpaDialect"/> </bean> <bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
d)Hibernate事务管理器
java代码:
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean>
经过entityManagerFactory属性指定须要事务管理的org.hibernate.SessionFactory对象。
从上节编程式实现事务管理能够深入体会到编程式事务的痛苦,即便经过代理配置方式也是不小的工做量。
本节将介绍声明式事务支持,使用该方式后最大的获益是简单,事务管理再也不是使人痛苦的,并且此方式属于无侵入式,对业务逻辑实现无影响。
接下来先来看看声明式事务如何实现吧。
一、定义业务逻辑实现,此处使用ConfigUserServiceImpl和ConfigAddressServiceImpl:
二、定义配置文件(chapter9/service/ applicationContext-service-declare.xml):
2.一、XML命名空间定义,定义用于事务支持的tx命名空间和AOP支持的aop命名空间:
java代码: <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
2.二、业务实现配置,很是简单,使用之前定义的非侵入式业务实现:
java代码: <bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl"> <property name="userDao" ref="userDao"/> <property name="addressService" ref="addressService"/> </bean> <bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl"> <property name="addressDao" ref="addressDao"/> </bean>
2.三、事务相关配置:
java代码: <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED" read-only="true"/> </tx:attributes> </tx:advice>
java代码:
<aop:config> <aop:pointcut id="serviceMethod" expression="execution(* cn..chapter9.service..*.*(..))"/> <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/> </aop:config> <tx:advice>:事务通知定义,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并经过< tx:attributes >指定具体须要拦截的方法; <tx:method name=”save*”>:表示将拦截以save开头的方法,被拦截的方法将应用配置的事务属性:propagation=”REQUIRED”表示传播行为是Required,isolation=”READ_COMMITTED”表示隔离级别是提交读; <tx:method name=”*”>:表示将拦截其余全部方法,被拦截的方法将应用配置的事务属性:propagation=”REQUIRED”表示传播行为是Required,isolation=”READ_COMMITTED”表示隔离级别是提交读,read-only=”true”表示事务只读; <aop:config>:AOP相关配置: <aop:pointcut/>:切入点定义,定义名为”serviceMethod”的aspectj切入点,切入点表达式为”execution(* cn..chapter9.service..*.*(..))”表示拦截cn包及子包下的chapter9. service包及子包下的任何类的任何方法; <aop:advisor>:Advisor定义,其中切入点为serviceMethod,通知为txAdvice。 从配置中能够看出,将对cn包及子包下的chapter9. service包及子包下的任何类的任何方法应用“txAdvice”通知指定的事务属性。
三、修改测试方法并测试该配置方式是否好用:
将TransactionTest 类的testServiceTransaction测试方法拷贝一份命名为testDeclareTransaction:
并在testDeclareTransaction测试方法内将:
四、执行测试,测试正常经过,说明该方式能正常工做,当调用save方法时将匹配到事务通知中定义的“<tx:method name=”save”>”中指定的事务属性,而调用countAll方法时将匹配到事务通知中定义的“<tx:method name=””>”中指定的事务属性。
声明式事务是如何实现事务管理的呢?还记不记得TransactionProxyFactoryBean实现配置式事务管理,配置式事务管理是经过代理方式实现,而声明式事务管理一样是经过AOP代理方式实现。
声明式事务经过AOP代理方式实现事务管理,利用环绕通知TransactionInterceptor实现事务的开启及关闭,而TransactionProxyFactoryBean内部也是经过该环绕通知实现的,所以能够认为是<tx:tags/>帮你定义了TransactionProxyFactoryBean,从而简化事务管理。
了解了实现方式后,接下来详细学习一下配置吧:
9.4.4 <tx:advice/>配置详解
声明式事务管理经过配置<tx:advice/>来定义事务属性,配置方式以下所示:
java代码: <tx:advice id="……" transaction-manager="……"> <tx:attributes> <tx:method name="……" propagation=" REQUIRED" isolation="READ_COMMITTED" timeout="-1" read-only="false" no-rollback-for="" rollback-for=""/> …… </tx:attributes> </tx:advice> <tx:advice>:id用于指定此通知的名字, transaction-manager用于指定事务管理器,默认的事务管理器名字为“transactionManager”; <tx:method>:用于定义事务属性即相关联的方法名;
name:定义与事务属性相关联的方法名,将对匹配的方法应用定义的事务属性,可使用“”通配符来匹配一组或全部方法,如“save”将匹配以save开头的方法,而“*”将匹配全部方法;
propagation:事务传播行为定义,默认为“REQUIRED”,表示Required,其值能够经过TransactionDefinition的静态传播行为变量的“PROPAGATION_”后边部分指定,如“TransactionDefinition.PROPAGATION_REQUIRED”可使用“REQUIRED”指定;
isolation:事务隔离级别定义;默认为“DEFAULT”,其值能够经过TransactionDefinition的静态隔离级别变量的“ISOLATION_”后边部分指定,如“TransactionDefinition. ISOLATION_DEFAULT”可使用“DEFAULT”指定:
timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
read-only:事务只读设置,默认为false,表示不是只读;
rollback-for:须要触发回滚的异常定义,以“,”分割,默认任何RuntimeException 将致使事务回滚,而任何Checked Exception 将不致使事务回滚;异常名字定义和TransactionProxyFactoryBean中含义同样
no-rollback-for:不被触发进行回滚的 Exception(s);以“,”分割;异常名字定义和TransactionProxyFactoryBean中含义同样;
记不记得在配置方式中为了解决“自我调用”而致使的不能设置正确的事务属性问题,使用“((IUserService)AopContext.currentProxy()).otherTransactionMethod()”方式解决,在声明式事务要获得支持须要使用<aop:config expose-proxy=”true”>来开启。
9.4.5 多事务语义配置及最佳实践
什么是多事务语义?说白了就是为不一样的Bean配置不一样的事务属性,由于咱们项目中不可能就几个Bean,而可能不少,这可能须要为Bean分组,为不一样组的Bean配置不一样的事务语义。在Spring中,能够经过配置多切入点和多事务通知并经过不一样方式组合使用便可。
一、首先看下声明式事务配置的最佳实践吧: <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="create*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="merge*" propagation="REQUIRED" /> <tx:method name="del*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="put*" propagation="REQUIRED" /> <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> <tx:method name="count*" propagation="SUPPORTS" read-only="true" /> <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> <tx:method name="list*" propagation="SUPPORTS" read-only="true" /> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* cn.javass..service.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" /> </aop:config>
该声明式事务配置能够应付常见的CRUD接口定义,并实现事务管理,咱们只需修改切入点表达式来拦截咱们的业务实现从而对其应用事务属性就能够了,若是还有更复杂的事务属性直接添加便可,即
若是咱们有一个batchSaveOrUpdate方法须要“REQUIRES_NEW”事务传播行为,则直接添加以下配置便可:
java代码:
1
<tx:method name="batchSaveOrUpdate" propagation="REQUIRES_NEW" />
二、接下来看一下多事务语义配置吧,声明式事务最佳实践中已经配置了通用事务属性,所以能够针对须要其余事务属性的业务方法进行特例化配置:
java代码: <tx:advice id="noTxAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="*" propagation="NEVER" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="noTxPointcut" expression="execution(* cn.javass..util.*.*())" /> <aop:advisor advice-ref="noTxPointcut" pointcut-ref="noTxAdvice" /> </aop:config>
该声明将对切入点匹配的方法所在事务应用“Never”传播行为。
多事务语义配置时,切入点必定不要叠加,不然将应用两次事务属性,形成没必要要的错误及麻烦。
对声明式事务管理,Spring提供基于@Transactional注解方式来实现,但须要Java 5+。
注解方式是最简单的事务配置方式,能够直接在Java源代码中声明事务属性,且对于每个业务类或方法若是须要事务都必须使用此注解。
接下来学习一下注解事务的使用吧:
一、定义业务逻辑实现:
package cn.javass.spring.chapter9.service.impl; //省略import public class AnnotationUserServiceImpl implements IUserService { private IUserDao userDao; private IAddressService addressService; public void setUserDao(IUserDao userDao) { this.userDao = userDao; } public void setAddressService(IAddressService addressService) { this.addressService = addressService; } @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED) @Override public void save(final UserModel user) { userDao.save(user); user.getAddress().setUserId(user.getId()); addressService.save(user.getAddress()); } @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, readOnly=true) @Override public int countAll() { return userDao.countAll(); } }
二、定义配置文件(chapter9/service/ applicationContext-service-annotation.xml):
2.一、XML命名空间定义,定义用于事务支持的tx命名空间和AOP支持的aop命名空间:
java代码:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
2.二、业务实现配置,很是简单,使用之前定义的非侵入式业务实现:
java代码: <bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl"> <property name="userDao" ref="userDao"/> <property name="addressService" ref="addressService"/> </bean> <bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl"> <property name="addressDao" ref="addressDao"/> </bean>
2.三、事务相关配置:
java代码: 1 <tx:annotation-driven transaction-manager="txManager"/> 使用如上配置已支持声明式事务。 三、修改测试方法并测试该配置方式是否好用: 将TransactionTest 类的testServiceTransaction测试方法拷贝一份命名为testAnntationTransactionTest: classpath:chapter9/service/applicationContext-service-annotation.xml" userService.save(user); try { userService.save(user); Assert.fail(); } catch (RuntimeException e) { } Assert.assertEquals(0, userService.countAll()); Assert.assertEquals(0, addressService.countAll());
四、执行测试,测试正常经过,说明该方式能正常工做,由于在AnnotationAddressServiceImpl类的save方法中抛出异常,所以事务须要回滚,因此两个countAll操做都返回0。
9.4.7 @Transactional配置详解
Spring提供的<tx:annotation-driven/>用于开启对注解事务管理的支持,从而能识别Bean类上的@Transactional注解元数据,其具备如下属性:
transaction-manager:指定事务管理器名字,默认为transactionManager,当使用其余名字时须要明确指定;
proxy-target-class:表示将使用的代码机制,默认false表示使用JDK代理,若是为true将使用CGLIB代理
order:定义事务通知顺序,默认Ordered.LOWEST_PRECEDENCE,表示将顺序决定权交给AOP来处理。
Spring使用@Transactional 来指定事务属性,能够在接口、类或方法上指定,若是类和方法上都指定了@Transactional ,则方法上的事务属性被优先使用,具体属性以下:
value:指定事务管理器名字,默认使用<tx:annotation-driven/>指定的事务管理器,用于支持多事务管理器环境;
propagation:指定事务传播行为,默认为Required,使用Propagation.REQUIRED指定;
isolation:指定事务隔离级别,默认为“DEFAULT”,使用Isolation.DEFAULT指定;
readOnly:指定事务是否只读,默认false表示事务非只读;
timeout:指定事务超时时间,以秒为单位,默认-1表示事务超时将依赖于底层事务系统;
rollbackFor:指定一组异常类,遇到该类异常将回滚事务;
rollbackForClassname:指定一组异常类名字,其含义与<tx:method>中的rollback-for属性语义彻底同样;
noRollbackFor:指定一组异常类,即便遇到该类异常也将提交事务,即不回滚事务;
noRollbackForClassname:指定一组异常类名字,其含义与<tx:method>中的no-rollback-for属性语义彻底同样;
Spring提供的@Transactional 注解事务管理内部一样利用环绕通知TransactionInterceptor实现事务的开启及关闭。
使用@Transactional注解事务管理须要特别注意如下几点:
若是在接口、实现类或方法上都指定了@Transactional 注解,则优先级顺序为方法>实现类>接口;
建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是由于若是使用JDK代理机制是没问题,由于其使用基于接口的代理;而使用使用CGLIB代理机制时就会遇到问题,由于其使用基于类的代理而不是接口,这是由于接口上的@Transactional注解是“不能继承的”;
具体请参考基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。
在Spring代理机制下(不论是JDK动态代理仍是CGLIB代理),“自我调用”一样不会应用相应的事务属性,其语义和<tx:tags>中同样;
默认只对RuntimeException异常回滚;
在使用Spring代理时,默认只有在public可见度的方法的@Transactional 注解才是有效的,其它可见度(protected、private、包可见)的方法上即便有@Transactional 注解也不会应用这些事务属性的,Spring也不会报错,若是你非要使用非公共方法注解事务管理的话,可考虑使用AspectJ。
微信公众号【黄小斜】做者是蚂蚁金服 JAVA 工程师,专一于 JAVA 后端技术栈:SpringBoot、SSM全家桶、MySQL、分布式、中间件、微服务,同时也懂点投资理财,坚持学习和写做,相信终身学习的力量!关注公众号后回复”架构师“便可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送做者原创的Java学习指南、Java程序员面试指南等干货资源