Chapter One . Spring 事务配置


1.事务(Transaction)是并发控制的单位,是用户定义的一个操作序列。

这些操作要么都做,要么都不做,是一个不可分割的工作单位。通过事务,SQLServer能将逻辑相关的一组操作绑定在一起,以便服务器保持数据的完整性。

事务通常是以Begin,Transaction 开始,以Commit或Rollback结束。

Commit:表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据库的更新写回到磁盘上的物理数据库中去,事务正常结束。

Rollback:表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有以完成的操作全部撤消,滚回到事务开始的状态。


2.事务的特性(ACID特性)

A:原子性(Atomicity)事务是数据库的逻辑工作单位,事务中包括的诸操作要么全做,要么全不做。

C:一致性(Consistency)事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

I:隔离性(Isolation)一个事务的执行不能被其他事务干扰。

D:持续性/永久性(Durability)一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。


3.分为编程式事务管理和声明式事务管理两种方式。

编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。

spring事务特性

spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口


其中TransactionDefinition接口定义以下特性:

事务隔离级别

隔离级别是指若干个并发的事务之间的隔离程度。

TransactionDefinition 接口中定义了五个表示隔离级别的常量:

TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。

TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。

TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。

TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。

TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

事务传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

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。

事务超时:所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。

事务只读属性:只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。

默认为读写事务。



“只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。

但是你非要在“只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像“读写事务”那样保险而已。

因此,“只读事务”仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可


spring事务回滚规则


指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。



myBatis为例 基于注解的声明式事务管理配置@Transactional

spring.xml

<!--mybatisconfig--><beanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean"><propertyname="dataSource"ref="dataSource"/><propertyname="configLocation"><value>classpath:mybatis-config.xml</value></property></bean><!--mybatismappers,scannedautomatically--><beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer"><propertyname="basePackage"><value>com.baobao.persistence.test</value></property><propertyname="sqlSessionFactory"ref="sqlSessionFactory"/></bean><!--配置spring的PlatformTransactionManager,名字为默认值--><beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><propertyname="dataSource"ref="dataSource"/></bean><!--开启事务控制的注解支持--><tx:annotation-driventransaction-manager="transactionManager"/>

添加tx名字空间

xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd"


MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。


@Transactional注解


@Transactional属性

属性类型描述valueString可选的限定描述符,指定使用的事务管理器propagationenum: Propagation可选的事务传播行为设置isolationenum: Isolation可选的事务隔离级别设置readOnlyboolean读写或只读事务,默认读写timeoutint (in seconds granularity)事务超时时间设置rollbackForClass对象数组,必须继承自Throwable导致事务回滚的异常类数组rollbackForClassName类名数组,必须继承自Throwable导致事务回滚的异常类名字数组noRollbackForClass对象数组,必须继承自Throwable不会导致事务回滚的异常类数组noRollbackForClassName类名数组,必须继承自Throwable不会导致事务回滚的异常类名字数组

用法

@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。

@AutowiredprivateMyBatisDaodao;@Transactional@Overridepublicvoidinsert(Testtest){dao.insert(test);thrownewRuntimeException("test");//抛出unchecked异常,触发事物,回滚}


noRollbackFor类,当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性

@Transactional(noRollbackFor=RuntimeException.class)@Overridepublicvoidinsert(Testtest){dao.insert(test);//抛出unchecked异常,触发事物,noRollbackFor=RuntimeException.class,不回滚thrownewRuntimeException("test");}@TransactionalpublicclassMyBatisServiceImplimplementsMyBatisService{@AutowiredprivateMyBatisDaodao;@Overridepublicvoidinsert(Testtest){dao.insert(test);//抛出unchecked异常,触发事物,回滚thrownewRuntimeException("test");}


propagation=Propagation.NOT_SUPPORTED

@Transactional(propagation=Propagation.NOT_SUPPORTED)@Overridepublicvoidinsert(Testtest){//事物传播行为是PROPAGATION_NOT_SUPPORTED,以非事务方式运行,不会存入数据库dao.insert(test);}

myBatis为例 基于注解的声明式事务管理配置,xml配置

主要为aop切面配置,只看xml就可以了

<!--事物切面配置--><tx:adviceid="advice"transaction-manager="transactionManager"><tx:attributes><tx:methodname="update*"propagation="REQUIRED"read-only="false"rollback-for="java.lang.Exception"/><tx:methodname="insert"propagation="REQUIRED"read-only="false"/></tx:attributes></tx:advice><aop:config><aop:pointcutid="testService"expression="execution(*com.baobao.service.MyBatisService.*(..))"/><aop:advisoradvice-ref="advice"pointcut-ref="testService"/></aop:config>



Chapter Two Spring事务配置的方式




根据代理机制的不同,总结了五种Spring事务的配置方式,配置文件如下:

第一种方式:每个Bean都有一个代理

<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsd"><beanid="sessionFactory"class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"><propertyname="configLocation"value="classpath:hibernate.cfg.xml"/><propertyname="configurationClass"value="org.hibernate.cfg.AnnotationConfiguration"/></bean><!--定义事务管理器(声明式的事务)--><beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager"><propertyname="sessionFactory"ref="sessionFactory"/></bean><!--配置DAO--><beanid="userDaoTarget"class="com.bluesky.spring.dao.UserDaoImpl"><propertyname="sessionFactory"ref="sessionFactory"/></bean><beanid="userDao"class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"><!--配置事务管理器--><propertyname="transactionManager"ref="transactionManager"/><propertyname="target"ref="userDaoTarget"/><propertyname="proxyInterfaces"value="com.bluesky.spring.dao.GeneratorDao"/><!--配置事务属性--><propertyname="transactionAttributes"><props><propkey="*">PROPAGATION_REQUIRED</prop></props></property></bean></beans>

第二种方式:所有Bean共享一个代理基类

<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsd"><beanid="sessionFactory"class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"><propertyname="configLocation"value="classpath:hibernate.cfg.xml"/><propertyname="configurationClass"value="org.hibernate.cfg.AnnotationConfiguration"/></bean><!--定义事务管理器(声明式的事务)--><beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager"><propertyname="sessionFactory"ref="sessionFactory"/></bean><beanid="transactionBase"class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"lazy-init="true"abstract="true"><!--配置事务管理器--><propertyname="transactionManager"ref="transactionManager"/><!--配置事务属性--><propertyname="transactionAttributes"><props><propkey="*">PROPAGATION_REQUIRED</prop></props></property></bean><!--配置DAO--><beanid="userDaoTarget"class="com.bluesky.spring.dao.UserDaoImpl"><propertyname="sessionFactory"ref="sessionFactory"/></bean><beanid="userDao"parent="transactionBase"><propertyname="target"ref="userDaoTarget"/></bean></beans>

第三种方式:使用拦截器

<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsd"><beanid="sessionFactory"class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"><propertyname="configLocation"value="classpath:hibernate.cfg.xml"/><propertyname="configurationClass"value="org.hibernate.cfg.AnnotationConfiguration"/></bean><!--定义事务管理器(声明式的事务)--><beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager"><propertyname="sessionFactory"ref="sessionFactory"/></bean><beanid="transactionInterceptor"class="org.springframework.transaction.interceptor.TransactionInterceptor"><propertyname="transactionManager"ref="transactionManager"/><!--配置事务属性--><propertyname="transactionAttributes"><props><propkey="*">PROPAGATION_REQUIRED</prop></props></property></bean><beanclass="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"><propertyname="beanNames"><list><value>*Dao</value></list></property><propertyname="interceptorNames"><list><value>transactionInterceptor</value></list></property></bean><!--配置DAO--><beanid="userDao"class="com.bluesky.spring.dao.UserDaoImpl"><propertyname="sessionFactory"ref="sessionFactory"/></bean></beans>

第四种方式:使用tx标签配置的拦截器

<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.5.xsd"><context:annotation-config/><context:component-scanbase-package="com.bluesky"/><beanid="sessionFactory"class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"><propertyname="configLocation"value="classpath:hibernate.cfg.xml"/><propertyname="configurationClass"value="org.hibernate.cfg.AnnotationConfiguration"/></bean><!--定义事务管理器(声明式的事务)--><beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager"><propertyname="sessionFactory"ref="sessionFactory"/></bean><tx:adviceid="txAdvice"transaction-manager="transactionManager"><tx:attributes><tx:methodname="*"propagation="REQUIRED"/></tx:attributes></tx:advice><aop:config><aop:pointcutid="interceptorPointCuts"expression="execution(*com.bluesky.spring.dao.*.*(..))"/><aop:advisoradvice-ref="txAdvice"pointcut-ref="interceptorPointCuts"/></aop:config></beans>

第五种方式:全注解

<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.5.xsd"><context:annotation-config/><context:component-scanbase-package="com.bluesky"/><tx:annotation-driventransaction-manager="transactionManager"/><beanid="sessionFactory"class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"><propertyname="configLocation"value="classpath:hibernate.cfg.xml"/><propertyname="configurationClass"value="org.hibernate.cfg.AnnotationConfiguration"/></bean><!--定义事务管理器(声明式的事务)--><beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager"><propertyname="sessionFactory"ref="sessionFactory"/></bean></beans>

此时在DAO上需加上@Transactional注解,如下:packagecom.bluesky.spring.dao;importjava.util.List;importorg.hibernate.SessionFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.orm.hibernate3.support.HibernateDaoSupport;importorg.springframework.stereotype.Component;importcom.bluesky.spring.domain.User;@Transactional@Component("userDao")publicclassUserDaoImplextendsHibernateDaoSupportimplementsUserDao{publicList<User>listUsers(){returnthis.getSession().createQuery("fromUser").list();}}