Spring 事务管理探究

本文基于Spring4.1.7所写,主要说明以下几个问题:
1)、 关于事务管理。
2)、 Spring如何提供事务管理。
3)、 Spring提供了哪些事务管理的方式方法。
4)、 Spring实现事务管理。
5)、 注意事项及常见问题。

1、关于事务管理

1.1、为什么需要事务

        为了使软件系统的数据库中的数据更可靠,更可信。

1.2、事务管理的分类

  • 全局事务管理
    这种事务管理方式通过Java Transaction API(JTA)实现,由容器管理,它可以管理多个资源(多数据源、多应用服务),通常需要以JNDI的方式提供数据源。通常集群的分布式数据库服务通常会使用这样的方式。

  • 本地事务管理
    本地事务管理也称局部事务管理,按其实现方式的不同可以分为编程式事务管理和声明式事务管理。

    编程式事务管理,它通过对资源的操作实现,比如联合JDBC连接实现事务管理,本地事务对于开发者来说更容易实现,但是也有重大的缺点:不能对多资源实现事务管理,因为它与JDBC的连接不是由应用服务器来进行管理的;另外一个不足是本地事务管理是一种代码入侵的模式。
    声明式事务管理是编程式事务管理的升级版,通过代理、切面等方式实现对业务代码的分离,声明式事务管理可以在不同的开发环境中使用统一的编程模式,因此其复用性较好,对系统的影响较小,因而受到广泛的应用。

2、Spring如何提供事务管理

        Spring提供了两种事务管理实现方式:编程式事务管理和声明式事务管理。

2.1、编程式事务管理

Spring中提供了一个核心的接口,PlatformTransactionManager接口:

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

该接口通过TransactionDefinition对象参数定义对事务的不同策略配置,通过TransactionStatus对象参数与事务执行的线程关联来知晓一个事务的存在与否及其状态,并根据TransactionDefinition 对象参数的策略配置来决定是否生创建新的事务、事务传播等一系列操作。TransactionDefinition的相关属性如下:

    事务隔离(Isolation):当前事务和其他运行的事务是相互隔离的。
 传播(Propagation):可以向已经存在的事务中添加事务。
 失效时间(Timeout):指明一个事务从开始执行后多少时间失效,等待这个时间段后无法完成提交的事务将自动回滚。
 只读状态(Read-only status):对只读去数据而不变更数据的操作可以将事务设置为只读,这样将提高系统的性能。

Spring中提供了两种编程式事务实现方式:

  • 使用TransactionTemplate模板;

  • 直接使用一个PlatformTransactionManager的实现。

使用模板实现编程式事务管理的基本步骤如下:

  1. 在需要使用事务管理的地方声明一个TransactionTemplate对象,其构造的参数是一个PlatformTransactionManager实例;

  2. 调用transactionTemplate对象的execute方法,该方法的参数为一个TransactionCallback实例,它作为一个回调传递给模板,TransactionCallback只是一个接口,其实现即是所要执行的业务操作。具体实现示例代码如下:

public Object someServiceMethod() {
    PlatformTransactionManager tm = new DataSourceTransactionManager();
    TransactionTemplate tt = new TransactionTemplate(tm);
    //通过模板设置事务策略
    tt.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
    tt.setTimeout(30); // 30 seconds
    return tt.execute(new TransactionCallback() {
            // 下面的这部分代码是执行在事务的上下文中的
            public Object doInTransaction(TransactionStatus status) {
            try{
                 updateOperation1(); //要执行的业务操作1
            catch(MyException e){
                status.setRollbackOnly();  //对关心的异常回滚事务
            }
            return resultOfUpdateOperation2();//要执行的业务操作2
            }
        });
    }

上面的示例中的回调是有返回值的,若不需要返回值可以使用TransactionCallbackWithoutResult作为回调。

直接使用PlatformTransactionManager实现事务管理

直接使用PlatformTransactionManager实现事务管理与使用模板相似,其步骤相同,只不过事务的执行、提交、回滚等操作需要自己实现,实现的示例代码如下:

public Object someServiceMethod() {
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    def.setName("SomeTxName");
    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    TransactionStatus status = txManager.getTransaction(def); 
    try {
         // 执行业务操作
    }catch (MyException ex) {
         txManager.rollback(status);
         throw ex;
    }
    txManager.commit(status);
}

2.2、声明式事务管理

        从上面对编程式事务管理的实现来看,其实现的事务管理比较精确,可以在同一个方法中实现不同的事务管理,这对于有相关方面需求的系统来说,使用编程式事务管理是可行的。但编程式事务管理也存在诸多弊端,如编码量大、侵入了业务代码、变更不灵活等,因此Spring提供了声明式的事务管理。
        Spring的声明式事务管理使用AOP的方式实现,通过动态代理,将对应的事务管理代码织入对应的切点,从而实现声明式的事务管理,但其事务管理的实际执行与编程式事务管理是相同的,其执行的流程如下图所示:

190012_kQc4_561986.png 

Spring声明式事务管理实现的相关基本配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!—配置Spring事务的传播特性 -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
     <!—以get开头的方法只进行读操作,可将其设置为只读事务,可提升系统性能 -->
            <tx:method name="get*" read-only="true"/>
            <!—其他方法默认 为 进行读写操作 -->
            <tx:method name="*" rollback-for="Exception"/>
        </tx:attributes>
    </tx:advice>
   <!--将事务传播特性与切面关联,使动态代理在合适的切点织入相应事务管理代码 -->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>
    <!-- 定义数据源 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>
    <!—定义PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

注:Spring中的事务管理器都是PlatformTransactionManager接口的实现,如DataSourceTransactionManager、HibernateTransactionManager、JMSTransactionManager等。

上述的配置是最基本的Spring声明式事务管理配置,其具体各项配置说明如下:

  • 首先需引入aop、tx标签的命名空间及对应的XSD文件;

  • 事务的管理实质上即是对数据源的控制,因此首先应该声明一个需要用于事务管理的数据源:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
       <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/> 
</bean>
  • Spring提供用于事务管理的接口为PlatformTransactionManager接口,它的实现是依赖于数据源(datasource)的,因此需要声明一个对应的事务管理器对象:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/> 
</bean>

对不同的数据源,使用的事务管理器是不同的,如Hibernate使用的是HibernateTransactionManager,其依赖的是sessionFactory,配置如下:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="mappingResources"> 
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
        </list> 
    </property> 
        <property name="hibernateProperties">
        <value>hibernate.dialect=${hibernate.dialect}</value> 
    </property> 
</bean>
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" /> 
</bean>
  • 由于Spring的声明式事务管理采用AOP的方式实现,因此Spring的声明式事务管理必须定义事务切入点,从而使事务管理代码能够正确织入适当的点,以实现事务管理。事务的切入点包含两部分信息:一个是切入的点(方法),一个是切入的点需要一个什么样的事务。这两部分的配置如下:

<!—配置Spring事务的传播特性 -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
             <!—以get开头的方法只进行读操作,可将其设置为只读事务,可提升系统性能 -->
            <tx:method name="get*" read-only="true"/>
            <!—其他方法默认 为 进行读写操作 -->
            <tx:method name="*" rollback-for="Exception"/>
        </tx:attributes>
    </tx:advice>
    <aop:config proxy-target-class="true">
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>

advice标签定义事务的传播特性,它表示要将transaction-manager指定的TransactionManager的事务管理代码织入指定点,其具体配置信息由attributes属性标签设置,attributes标签的详细信息如下:

属性是否必须默认描述
nameYes指切入点对应的方法的名称,可以使用通配符表示。
propagationNoREQUIRED

定义事务的传播行为:

  • PROPAGATION_REQUIRED --支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

  • PROPAGATION_SUPPORTS --支持当前事务,如果当前没有事务,就以非事务方式执行。

  • PROPAGATION_MANDATORY --支持当前事务,如果当前没有事务,就抛出异常。

  • PROPAGATION_REQUIRES_NEW --新建事务,如果当前存在事务,把当前事务挂起。

  • PROPAGATION_NOT_SUPPORTED --以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

  • PROPAGATION_NEVER --以非事务方式执行,如果当前存在事务,则抛出异常。

  • PROPAGATION_NESTED --如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

isolationNoDEFAULT

定义事务隔离级别:

  • ISOLATION_DEFAULT –Spring的默认事务隔离级别,它使用的是数据库的事务隔离级别,与使用的数据库有关。

  • ISOLATION_READ_UNCOMMITTED –该隔离级别允许所有事务读取当前事务未提交(或回滚)的事务,该隔离级别会导致“脏读”、“不可重复读”、“幻读”,在实际中很少使用。

  • ISOLATION_READ_COMMITTED –可以阻止 “脏读”,但可能会出现“不可重复读”、“幻读”。

  • ISOLATION_REPEATABLE_READ –可以阻止“脏读”、“不可重复读”,但可能会出现“幻读”。

  • ISOLATION_SERIALIZABLE –该级别的事务是顺序执行的,上述的三种现象都能阻止,但消耗系统性能最大。

名词解释:
脏读:一个事务读取了另一个未提交的事务修改的数据,这时前一个事务读取到的数据可能是不正确的数据。
不可重复读:指在一个事务中多次读取一条记录,在这个事务第一次读取和第二次读取之间另一个事务也访问了该条记录并修改,则前一个事务两次读取的数据不一致。
幻读:幻读与不可重复读有些类似,在同一个事务中以同一条件操作某一数据集两次(或多次),在其进行两个操作之间,另一个事务操作了另一个数据集,而这个数据集被操作后包含于前一个事务操作的数据集,因此引起了第一个事务中的两次操作的数据集是不一致的,因而出现了前一次操作不正确的幻觉。

timeoutNo-1指定一个事务的失效时间,在失效时限内如果一个事务未能执行完成,则被自动回滚,单位为秒。默认与数据库一致。
read-onlyNofalse定义一个事务为只读事务,可提高系统性能。
rollback-forNo对什么样的异常(及其子异常)进行事务回滚。
no-rollback-forNo对什么样的异常(及其子异常)不进行回滚,rollback-for 和no-rollback-for可同时使用。

注:
1) 使用XML based configuration的方式实现的声明式事务管理依然可以在具体的代码中通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()回滚事务;
2) 在配置中可以针对不同的模块、不同的类配置不同的切点和事务传播的特性。

2.3、通过注解驱动实现声明式事务管理

Spring同时还提供了通过注解实现事务管理的实现方式,这种方式的实现与XML based configuration的方式类同,即是在对应的实现类或方法中(或两者一起)添加事务属性的设置即可。
其具体实现方法如下:

  • 首先在XML配置文件中添加AOP和TX标签的命名空间及对应的.XSD文件,然后启用事务管理注解:<tx:annotation-driven transaction-manager="txManager"/>,同时需要指定一个事务管理器,事务管理器的配置与XML-base方式的配置相同,如果事务管理器的Bean id为transactionManager,则这里的transaction-manager可以省略,因为这是默认的名称。

  • 在目标实现类和目标方法上添加事务注解,实例代码如下:

@Transactional(readOnly = true)
public class SomeService implements ISomeService {
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) 
     public Object  someMethod(String param) {
        // do something
    }
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void someMethod 2(Object obj) {
        // do something
    }
}

3、注意事项

1、由于Spring AOP使用代理实现,finale修饰的类和static、finale修饰的将不能织入,因Spring AOP是运行时植入,这将导致运行时异常。
2、若数据库存在不支持事务的数据库引擎,应当切换到支持事务的引擎,否则事务管理将起不到应有的作用。如MySql数据库的MyISAM引擎不支持事务,应改为支持事务的引擎,如INNODB等。
3、使用Spring事务管理时,其动态代理模式默认为JDK动态代理,此时依赖注入都必须使用接口注入方式,不能使用类注入;使用CGLib动态代理模式可以使用类注入,使用CGLib代理的配置为:<aop:config proxy-target-class="true">,其默认值为false。

-- by  Alex  2015-10-30

转载于:https://my.oschina.net/u/561986/blog/524175

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值