SpringMyBatis 事务管理

2016/3/4 posted in  数据库

框架简介

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架,具有两个重要模块:Spring 面向切面编程(AOP)和控制反转(IOC)容器。

  • 控制反转模式(也称作依赖性介入)的基本概念是:不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。

  • 容器(在Spring 框架中是 IOC 容器)负责将这些联系在一起。在典型的IOC场景中,容器创建了所有对象,并设置必要的属性将它们连接在一起,决定什么时间调用方法。

MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架,消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索,使用简单的XML或注解用于配置和原始映射,将接口和Java对象映射成数据库中的记录。

一、什么是事务

对于一个软件系统来说,需要相应的数据资源(比如,数据库,文件系统等)来保存系统状态,在对系统状态所依托的数据资源进行访问的时候,为了保证系统始终处于一个“正确”的状态,我们就必须对这些访问操作进行一些必要的限定,以此来保证系统状态的完整性。

事务就是以可控的方式对数据资源进行访问的一组操作,为了保证事务执行前后数据资源所承载的系统状态始终处于“正确”状态。

二、事务的特性

原子性(Atomicity),一致性(Consistency),隔离性(Isolation)以及持久性(Durability),也就是常说的事务的ACID属性。

1、事务的原子性(Atomicity)

要么同时成功,要么同时失败

原子性要求事务所包含的全部操作是一个不可分割的整体,这些操作要么全部提交成功,要么只要其中一个操作失败。

2、事务的一致性(Consistency)

一致性要求事务所包含的操作不能违反数据资源的一致性检查,数据资源在事务执行之前处于一个数据的一致性状态,那么,事务执行之后也需要依然保持数据间的一致性状态。

3、事务的隔离性(Isolation)

事务的隔离性主要规定了各个事务之间相互影响的程度。隔离性概念主要面向对数据资源的并发访问(Concurrency),并兼顾影响事务的一致性。当两个事务或者更多事务同时访问同一数据资源的时候, 不同的隔离级别决定了各个事务对该数据资源访问的不同行为。

不出意外的话,我们可以为事务指定四种类型的隔离级别,隔离程度按照从弱到强分别为Read UncommittedRead CommittedRepeatable ReadSerializable:

  • Read Uncommitted:最低的隔离级别,Read Uncommitted最直接的效果就是一个事务可以读取另一个事务并未提交的更新结果。

  • Read CommittedRead Committed通常是大部分数据库采用的默认隔离级别,它在Read Uncommitted隔离级别基础上所做的限定更进一步,在该隔离级别下,一个事务的更新操作结果只有在该事务提交之后,另一个事务才可能读取到同一笔数据更新后的结果。

  • Repeatable ReadRepeatable Read隔离级别可以保证在整个事务的过程中,对同一笔数据的读取结果是相同的,不管其他事务是否同时在对同一笔数据进行更新,也不管其他事务对同一笔数据的更新提交与否。Repeatable Read隔离级别避免了脏读和不可重复读取的问题,但无法避免幻读。

  • Serializable:最为严格的隔离级别,所有的事务操作都必须依次顺序执行,可以避免其他隔离级别遇到的所有问题,是最为安全的隔离级别,但同时也是性能最差的隔离级别。

4、事务的持久性(Durability)

事务的持久性是指一旦整个事务操作成功提交完成,对数据所做的变更将被记载并不可逆转。

三、Spring事务性

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

public interface PlatformTransactionManager 
{
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}

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

1、传播行为

传播行为定义关于客户端和被调用方法的事务边界。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

传播行为意义
PROPAGATION_MANDATORY该方法必须运行在一个事务中。如果当前没有事务正在发生,将抛出一个异常。
PROPAGATION_NESTED如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于封装事务进行提交或回滚。如果封装事务不存在,行为就像 PROPAGATION_REQUIRES一样。
PROPAGATION_NEVER当前的方法不应该在一个事务中运行。如果一个事务正在进行,则会抛出一个异常。
PROPAGATION_NOT_SUPPORTED该方法不应该在一个事务中运行。如果一个现有事务正在进行中,它将在该方法的运行期间被挂起。
PROPAGATION_SUPPORTS当前方法不需要事务性上下文,但是如果有一个事务已经在运行的话,它也可以在这个事务里运行。
PROPAGATION_REQUIRES_NEW当前方法必须在它自己的事务里运行。一个新的事务将被启动,而且如果有一个现有事务在运行的话,则将在这个方法运行期间被挂起。
PROPAGATION_REQUIRES当前方法必须在一个事务中运行。如果一个现有事务正在进行中,该方法将在那个事务中运行,否则就要开始一个新事务。

传播行为回答了这样一个问题,就是一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。

2、隔离级别

声明式事务的第二个方面是隔离级别。隔离级别定义一个事务可能受其他并发事务活动活动影响的程度。另一种考虑一个事务的隔离级别的方式,是把它想象为那个事务对于事物处理数据的自私程度。

在一个典型的应用程序中,多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。并发虽然是必需的,但是会导致一下问题:

  • 脏读(Dirty read)-- 脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。

  • 不可重复读(Nonrepeatable read)-- 不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。

  • 幻影读(Phantom reads)-- 幻影读和不可重复读相似。当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻影读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。

在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常牵扯到锁定在数据库中的记录(而且有时是锁定完整的数据表)。侵占性的锁定会阻碍并发,要求事务相互等待来完成工作。

考虑到完全隔离会影响性能,而且并不是所有应用程序都要求完全隔离,所以有时可以在事务隔离方面灵活处理。TransactionDefinition 接口中定义了五个表示隔离级别的常量:

隔离级别含义
ISOLATION_DEFAULT使用数据库默认的隔离级别。
ISOLATION_READ_UNCOMMITTED允许读取尚未提交的更改。可能导致脏读、幻影读或不可重复读。
ISOLATION_READ_COMMITTED允许从已经提交的并发事务读取。可防止脏读,但幻影读和不可重复读仍可能会发生。
ISOLATION_REPEATABLE_READ对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻影读仍可能发生。
ISOLATION_SERIALIZABLE完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。

3、超时

为了使一个应用程序很好地执行,它的事务不能运行太长时间。因此,声明式事务的下一个特性就是它的超时。

假设事务的运行时间变得格外的长,由于事务可能涉及对后端数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。

由于超时时钟在一个事务启动的时候开始的,因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEWPROPAGATION_REQUIREDROPAGATION_NESTED)的方法来说,声明事务超时才有意义。

4、只读

声明式事务的第四个特性是它是否是一个只读事务。如果一个事务只对后端数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化措施。通过把一个事务声明为只读,可以给后端数据库一个机会来应用那些它认为合适的优化措施。由于只读的优化措施是在一个事务启动时由后端数据库实施的, 因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEWPROPAGATION_REQUIREDROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。

5、回滚规则

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

默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出 checked 异常则不会导致事务回滚。

可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。

还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。

四、Spring 事务管理

Spring对事务管理提供了一致的抽象,其特点如下:

  • 为不同的事务API提供一致的编程模型,比如JTA(Java Transaction API), JDBC, Hibernate, JPA(Java Persistence APIJDO(Java Data Objects)

  • 支持声明式事务管理,特别是基于注解的声明式事务管理,简单易用

  • 提供比其他事务APIJTA更简单的编程式事务管理API

  • spring数据访问抽象的完美集成

1、事务管理方式

Spring支持编程式事务管理和声明式事务管理两种方式。

编程式事务管理使用TransactionTemplate或直接使用底层PlatformTransactionManagerSpring推荐使用 TransactionTemplate

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

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

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

2、自动提交(AutoCommit)

默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。

对于正常的事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式。不过,这个我们不用担心,Spring会将底层连接的自动提交特性设置为false

org/springframework/jdbc/datasource/DataSourceTransactionManager.java

// switch to manual commit if necessary. this is very expensive in some jdbc drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getautocommit()) {
    txobject.setmustrestoreautocommit(true);
    if (logger.isdebugenabled()) {
        logger.debug("switching jdbc connection [" + con + "] to manual commit");
    }
    con.setautocommit(false);
}

五、基于@Transactional注解的AOP事务管理

Spring 配置文件里需要:

<tx:annotation-driven transaction-manager="transactionManager"/>

<aop:aspectj-autoproxy/>

tx:annotation-driven是注解驱动的事务管理支持的核心。

标签属性:

a、transaction-manager:指定到现有的 PlatformTransactionManager bean的引用,通知会使用该引用,default = "transactionManager"

b、mode:指定Spring事务管理框架创建通知bean的方式。可用的值有proxyaspectj。前者是默认值,表示通知对象是个JDK代理;后者表示Spring AOP会使用AspectJ创建代理。

c、order:指定创建的切面的顺序。只要目标对象有多个通知就可以使用该属性。

d、proxy-target-class:该属性如果为true就表示你想要代理目标类而不是bean所实现的所有接口default="false"

1、@Transactional属性

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

2、用法

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

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

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

示例代码:

@Transactional(readOnly = true)
public class getInfoService implements BaseService 
{
  public Info getInfo(String name) 
  {
    // do something
  }
  // these settings have precedence for this method
  //方法上注解属性会覆盖类注解上的相同属性
  @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  public void updateInfo(Info info) 
  {
    // do something
  }
}

如果在每个方法上都定义注解,那么就会很麻烦。(可以使用XML AOP事务管理能更好的处理这种情况)

六、基于XMLAOP事务管理

1、基于XMLAOP事务管理配置文件如下:

<aop:config>
    <aop:pointcut id="allServiceMethods"  
                  expression="execution(* cn.hao24.mobauto.service.*.*(..))"/>  
    <aop:advisor advice-ref="defaultTransactionAdvice"
                    pointcut-ref="allServiceMethods"/>  
</aop:config>  
  
<tx:advice id="defaultTransactionAdvice" transaction-manager="transactionManager">  
    <tx:attributes>  
        <tx:method  
                name="*"  
                isolation="DEFAULT"  
                propagation="REQUIRED"  
                no-rollback-for="java.lang.RuntimeException"  
                timeout="100"/>  
        <tx:method  
                name="get*"  
                read-only="true"/>  
    </tx:attributes>  
</tx:advice>

2、tx:advice标签简介

该标签会创建一个事务处理通知。

id是该advice bean的标识,而transaction-manager则必须引用一个PlatformTransactionManager bean
还可以通过<tx:attributes>标签定制<tx:advice>标签所创建的通知的行为。

3、<tx:method/>标签的属性

name:方法名的匹配模式,通知根据该模式寻找匹配的方法。

其余属性(propagationisolationtimeoutread-onlyno-rollback-forrollback-for)不再赘述。

<tx:method>isolation(隔离)和propagation(传播)参数的含义:
getIsolationLevel:他对其他事务所看到的数据变化进行控制。

七、MyBatis-Spring 配置

使用MyBatis-Spring的主要原因是它允许MyBatis参与到 Spring 的事务管理中。而不是给MyBatis创建一个新的特定的事务管理器,MyBatis-Spring利用了存在于 Spring中的DataSourceTransactionManager

一旦SpringPlatformTransactionManager配置好了,你可以在Spring中以你通常的做法来配置事务。@Transactional注解和AOP XML的配置都是支持的。在事务处理期间,一个单独的SqlSession对象将会被创建和使用。当事务完成时,这个session会以合适的方式提交或回滚。

一旦事务创建之后,MyBatis-Spring将会透明的管理事务。配置代码如下:

<!-- 创建SqlSessionFactory,同时指定数据源 -->
<bean id="mySqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
    <property name="dataSource" ref="dataSource" />
    <!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
    <property name="mapperLocations" value="classpath:sqlmaps/**/*Mapper.xml" />
</bean>

<!-- Mapper接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
    <property name="basePackage" value="cn.hao24.mobauto.mapper" />
    <property name="sqlSessionFactoryBeanName" value="mySqlSessionFactory" />
</bean>

MyBatisSqlSession提供指定的方法来处理编程式的事务。 但是当使用MyBatis-Spring时, bean将会使用 Spring管理的SqlSession或映射器来注入。 那就是说Spring通常是处理事务的。

你不能在Spring管理的SqlSession上调用 SqlSession.commit()SqlSession.rollback()SqlSession.close()方法 。 如果这样做了 , 就 会 抛 出UnsupportedOperationException异常。注意在使用注入的映射器时不能访问那些方法。

无论JDBC连接是否设置为自动提交, SqlSession数据方法的执行或在Spring事务之外任意调用映射器方法都将会自动被提交。

Local vs. Global Transactions

Local transactions are specific to a single transactional resource like a JDBC connnection, whereas globa transactions can span multiple transactional resources like transaction in a distributed system.

事务类型

数据库事务类型有本地事务和分布式事务:

  • 本地事务:就是普通事务,能保证单台数据库上的操作的ACID,被限定在一台数据库上
  • 分布式事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的)

Java事务类型有JDBC事务和JTA事务:

  • JDBC事务:就是数据库事务类型中的本地事务,通过Connection对象的控制来管理事务
  • JTA事务:由应用程序服务器厂商提供实现

JavaEE事务类型有本地事务和全局事务:

  • 本地事务:使用JDBC变成实现事务
  • 全局事务:由应用程序服务器提供,使用JTA事务