Spring笔记——事务

Spring的事务管理不需要与任何特定的事务API耦合。对不同的持久层访问技术,编程式事务提供一致的事务编程风格,通过模板化的操作一致性的管理事务。声明式事务基于Spring AOP实现,却并不需要程序开发者成为AOP专家,亦可轻易使用Spring的声明式事务管理。

1.Spring支持的事务策略

Java EE应用的传统事务有两种策略:全局事务和局部事务,全局事务由应用服务器管理,需要底层服务器的JTA支持。局部事务和底层所采用的持久化技术有关,当采用JDBC持久化技术时,需要使用Connection对象来操作事务;而采用Hibernate持久化技术时,需要使用Session对象来操作事务。

全局事务可以跨多个事务性的资源(典型的例子是关系数据库和消息队列);使用局部事务,应用服务器不需要参与事务管理,因此不能保证跨多个事务性资源的事务的正确性。当然,实际上大部分应用都使用单一事务性的资源。

Spring事务策略是通过PlatformTransactionManager接口体现的,该接口是Spring事务策略的核心。该接口的源代码如下:

public interface PlatformTransactionManager
{
    // 平台无关的获得事务的方法
    public abstract TransactionStatus getTransaction(TransactionDefinition transactiondefinition)
        throws TransactionException;
    // 平台无关的事务提交方法
    public abstract void commit(TransactionStatus transactionstatus)
        throws TransactionException;
    // 平台无关的事务回滚方法
    public abstract void rollback(TransactionStatus transactionstatus)
        throws TransactionException;
}

PlatformTransactionManager是一个与任何事务策略分离的接口,随着底层不同事务策略的切换,应用必须采用不同的实现类。PlatformTransactionManager接口没有与任何事务资源捆绑在一起,它可以适用于任何的事务策略,结合Spring的IoC容器,可以向PlatformTransactionManager注入相关的平台特性。

PlatformTransactionManager接口有许多不同的实现类,应用程序面向与平台无关的接口编程,当底层采用不同的持久层技术时,系统只需使用不同的PlatformTransactionManager实现类即可——而这种切换通常由Spring容器负责管理,应用程序既无须与具体的事务API耦合,也无须与特定实现类耦合,从而将应用和持久化技术、事务API彻底分离开来。

Spring的事务管理机制是一种典型的策略模式,PlatformTransactionManager代表事务管理接口,但它并不知道底层如何管理事务,它只要求事务管理需要提供开始事务、提交事务和回滚事务三个方法,但具体如何实现则交给其实现类来完成。

在PlatformTransactionManager接口内,包含一个getTransaction(TransactionDefinition transactiondefinition)方法,该方法根据一个TransactionDefinition参数,返回一个TransactionStatus对象。TransactionStatus 对象表示一个事务,TransactionStatus 被关联在当前执行的线程上。

getTransaction(TransactionDefinition transactiondefinition)返回的TransactionStatus对象,可能是一个新的事务,也可能是一个已经存在的事务对象。如果当前执行的线程已经处于事务管理下,则返回当前线程的事务对象;否则,系统将新建一个事务对象后返回。

TransactionDefinition 接口定义了一个事务规则,该接口必须指定如下几个属性值:
☞ 事务隔离当前事务和其他事务的隔离程度。例如,这个事务能否看到其他事务未提交的数据等。
☞ 事务传播通常,在事务中执行的代码都会在当前事务中运行。但是,如果一个事务上下文已经存在,有几个选项可指定该事务性方法的执行行为。例如,大多数情况下,简单地在现有的事务上下文中运行;或者挂起现有事务,创建一个新的事务。
☞ 事务超时事务在超时前能运行多久,也就是事务的最长持续时间。如果事务一直没有被提交或回滚,将在超出该时间后,系统自动回滚事务。
☞ 只读状态只读事务不修改任何数据。在某些情况下(例如使用Hibernate时),只读事务是非常有用的优化。

TransactionStatus代表事务本身,它提供了简单的控制事务执行和查询事务状态的方法,这些方法在所有的事务API中都是相同的。TransactionStatus接口的源代码如下:

public interface TransactionStatus
{
    // 判断事务是否为新建的事务
    boolean isNewTransaction();
    // 设置事务回滚
    public abstract void setRollbackOnly();
    // 查询事务是否已有回滚标志
    public abstract boolean isRollbackOnly();
}

Spring具体的事务管理由PlatformTransactionManager的不同实现类完成。在Spring容器中配置PlatformTransactionManager Bean时,必须针对不同环境提供不同的实现类。

下面提供了不同的持久层访问环境,及其对应的PlatformTransactionManager实现类的配置。

JDBC数据源的局部事务策略的配置文件如下:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    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/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close">
        
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        
        <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1/javaee" />
        
        <property name="user" value="root" />
        
        <property name="password" value="123456" />
        
        <property name="maxPoolSize" value="40" />
        
        <property name="minPoolSize" value="1" />
        
        <property name="initialPoolSize" value="1" />
        
        <property name="maxIdleTime" value="20" />
    bean>

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

容器管理的JTA全局事务的配置文件如下:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    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/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        
        <property name="jndiName" value="jdbc/jpetstore" />
    bean>

    
    
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
beans>

从上面的配置文件来看,当配置JtaTransactionManager全局事务管理策略时,只需指定事务管理器实现类即可,无须传入额外的事务资源。这是因为全局事务的JTA资源由Java EE服务器提供,而Spring容器能自行从Java EE服务器中获取该事务资源,所以无须使用依赖注入来配置。

当采用Hibernate持久层访问策略时,局部事务策略的配置文件如下:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    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/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close">
        
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        
        <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1/javaee" />
        
        <property name="user" value="root" />
        
        <property name="password" value="123456" />
        
        <property name="maxPoolSize" value="40" />
        
        <property name="minPoolSize" value="1" />
        
        <property name="initialPoolSize" value="1" />
        
        <property name="maxIdleTime" value="20" />
    bean>

    
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        
        <property name="dataSource" ref="dataSource" />
        
        <property name="mappingResources">
            <list>
                
                <value>lee/MyTest.hbm.xmlvalue>
            list>
        property>
        
        <property name="hibernateProperties">
            <props>
                
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialectprop>
                
                <prop key="hibernate.hbm2ddl.auto">updateprop>
            props>
        property>
    bean>

    
    
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        
        <property name="sessionFactory" ref="sessionFactory" />
    bean>
beans>

如果底层采用Hibernate持久层技术,但事务采用JTA全局事务,则Spring配置文件如下:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    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/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        
        <property name="jndiName" value="jdbc/jpetstore" />
    bean>

    
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        
        <property name="dataSource" ref="dataSource" />
        
        <property name="mappingResources">
            <list>
                
                <value>lee/MyTest.hbm.xmlvalue>
            list>
        property>
        
        <property name="hibernateProperties">
            <props>
                
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialectprop>
                
                <prop key="hibernate.hbm2ddl.auto">updateprop>
            props>
        property>
    bean>

    
    
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
beans>

从上面的配置文件可以看出,不论采用哪种持久层访问技术,只要使用JTA全局事务,Spring事务管理的配置完全一样,因为他们采用的都是全局事务管理策略。

从上面的配置文件可以看出,当采用Spring事务管理策略时,应用程序无须与具体的事务策略耦合。Spring提供了如下两种事务管理方式:
☞ 编程式事务管理:即使利用Spring编程式事务时,程序也可直接获取容器中的transactionManager Bean,该Bean总是PlatformTransactionManager的实例,所以可以通过该接口所提供的3个方法来开始事务、提交事务和回滚事务。
☞声明式事务管理:无须在Java程序中书写任何的事务操作代码,而是通过在XML文件中为业务组件配置管理事务代理(AOP代理的一种),AOP为事务代理所织入的增强处理也有Spring提供:在目标方法执行之前,织入开始事务;在目标方法执行之后,织入结束事务。

2.使用TransactionProxyFactoryBean创建事务代理

Spring同时支持编程式事务策略和声明式事务策略,大部分时候,我们都推荐采用声明式事务策略。使用声明式事务策略的优势十分明显:
☞ 声明式事务能大大降低开发者的代码书写量,而且声明式事务几乎不影响应用的代码。因此,无论底层的事务策略如何变化,应用程序都无须任何改变。
☞ 应用程序代码无须任何事务处理代码,可以更专注于业务逻辑的实现。
☞ Spring则可对任何POJO的方法提供事务管理,而且Spring的声明式事务管理无须容器的支持,可在任何环境下使用。
☞ EJB的CMT无法提供声明式回滚规则:而通过配置文件,Spring可指定事务在遇到特定异常时自动回滚。Spring不仅可在代码中使用setRollbackOnly回滚事务,也可在配置文件中配置回滚规则。
☞ 由于Spring采用AOP的方式管理事务,因此,可以在事务回滚动作中插入用户自己的动作,而不仅仅是执行系统默认的回滚。


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    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/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close">
        
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        
        <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1/javaee" />
        
        <property name="user" value="root" />
        
        <property name="password" value="123456" />
        
        <property name="maxPoolSize" value="40" />
        
        <property name="minPoolSize" value="1" />
        
        <property name="initialPoolSize" value="1" />
        
        <property name="maxIdleTime" value="20" />
    bean>

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

    
    <bean id="newsDao" class="org.crazyit.app.dao.impl.NewsDaoImpl">
        <property name="ds" ref="dataSource"/>
    bean>

    
    <bean id="newsDaoTrans" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        
        <property name="transactionManager" ref="transactionManager"/>
        <property name="target" ref="newsDao"/>
        
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIREDprop>
            props>
        property>
    bean>
beans>

上面的配置文件中第一段粗体字代码配置了一个事务管理器,该事务管理器是针对JDBC局部事务的特定实现类。程序第二段粗体字代码为test Bean配置了事务代理。

配置事务代理时需要传入一个事务管理器、一个目标Bean,并指定该事物代理的事务属性,事务属性由transactionManager属性指定。上面事务属性只有一条事务传播规则,该规则指定对于所有方法都使用PROPAGATION_REQUIRED的传播规则。Spring支持的事务传播规则如下:
☞ PROPAGATION_MANDATORY:要求调用该方法的线程必须处于事务环境中,否则抛出异常
☞ PROPAGATION_NESTED:如果执行该方法的线程已处于事务环境下,依然启动新的事务,方法在嵌套的事务里执行。如果执行该方法的线程并未处于事务中,也启动新的事务,然后执行该方法,此时与PROPAGATION_REQUIRED相同
☞ PROPAGATION_NEVER:不允许调用该方法的线程处于事务环境系下,如果调用该方法的线程处于事务环境下,则抛出异常
☞ PROPAGATION_NOT_SUPPORTED:如果调用该方法的线程处于事务中,则先暂停当前事务,然后执行该方法
☞ PROPAGATION_REQUIRED:要求在事务环境中执行该方法,如果当前执行线程已处于事务中,则直接调用;如果当前执行线程不处于事务中,则启动新的事务后执行该方法
☞ PROPAGATION_REQUIRED_NEW:该方法要求在新的事务环境中执行,如果当前执行线程已处于事务中,则先暂停当前事务,启动新事务后执行该方法;如果当前调用线程不处于事务中,则启动新的事务后执行方法
☞ PROPAGATION_SUPPORTS:如果当前执行线程处于事务中,则使用当前事务,否则不使用事务

事实上,Spring不仅支持对接口的代理,整合CGLIB后,Spring甚至可以对具体类生成代理,只要设置proxy-target-class属性为true就可以。如果目标Bean没有实现任何接口,proxy-target-class属性默认被设为true,此时Spring会对具体类生成代理。


<aop:aspectj-autoproxy proxy-target-class="true" />

3.Spring2.X的事务配置策略

虽然前面介绍的TransactionProxyFactoryBean配置策略简单易懂,但配置起来极为繁琐:每个目标Bean都需要额外配置一个TransactionProxyFactoryBean代理,这种方式将导致配置文件急剧增加。
Spring2.X的XML Schema方式提供了更简洁的事务配置策略,Spring2.X提供了tx命名空间来配置事务管理,tx命名空间下提供了元素来配置事务增强处理,一旦使用该元素配置了事务增强处理,就可以直接使用为容器中一批Bean配置自动事务代理。


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    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/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-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">

    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close">
        
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        
        <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1/javaee" />
        
        <property name="user" value="root" />
        
        <property name="password" value="123456" />
        
        <property name="maxPoolSize" value="40" />
        
        <property name="minPoolSize" value="1" />
        
        <property name="initialPoolSize" value="1" />
        
        <property name="maxIdleTime" value="20" />
    bean>

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

     
    <bean id="newsDao" class="org.crazyit.app.dao.impl.NewsDaoImpl">
        <property name="ds" ref="dataSource"/>
    bean>

    
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        
        <tx:attributes>
            
            <tx:method name="get*" read-only="true"/>
            
            <tx:method name="*"/>
        tx:attributes>
    tx:advice>

    
    <aop:config>
        
        <aop:pointcut id="myPointcut" expression="execution(* org.crazyit.app.dao.iml.*Impl.*(..))"/>
        
        <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
    aop:config>
beans>

上面的配置文件的第一段粗体字代码使用XML Schema启用了Spring配置文件的tx、aop两个命名空间,配置文件的第二段粗体字代码配置了一个事务增强处理,配置元素时只需指定一个transaction-manager属性,该属性的默认值是‘transactionManager’

配置文件中最后一段粗体字是定义,它确保由txAdvice切面定义事务增强处理能在合适的点被织入。首先我们定义了一个切入点,它匹配org.crazyit.app.dao.iml包下所有以Impl结尾的类所包含的所有方法,我们把该切入点叫做myPointcut。然后用一个Advisor把这个切入点与txAdvice绑定在一起,表示当myPointcut执行时,txAdvice定义的增强处理将被织入。

当我们使用这种配置策略时,无须为每个业务Bean专门配置事务代理,Spring AOP会为业务组件自动生成代理,程序可以直接请求容器中test Bean,该Bean的方法已经具有了事务性——因为该Bean的实现类位于org.crazyit.app.dao.iml包下,且以Impl结尾,和myPointcut切入点匹配。

配置子元素可以指定如下几个属性:
☞ name:必选属性,与该事务语义关联的方法名。该属性支持视同通配符
☞ propagation:指定事务传播行为,该属性值可为Propagation枚举类的任一枚举值。该属性的默认值为Propagation.REQUIRED
☞ isolation:指定事务隔离级别,该属性值可为Isolation枚举类的任一枚举值,该属性的默认值为Isolation.DEFAULT
☞ timeout:指定事务超时的时间(以秒为单位),指定-1意味着不超时,该属性的默认值是-1
☞ read-only:指定事务是否只读。该属性的默认值是false
☞ rollback-for:指定出发事务回滚的异常类(应使用全限定类名),该属性可指定多个异常类,多个异常类之间以英文逗号隔开
☞ no-rollback-for:指定不触发事务回滚的异常类(应使用全限定类名),该属性可指定多个异常类,多个异常类之间以英文逗号隔开

提示:在默认情况下,只有当抛出运行时异常和unChecked异常时,Spring事务框架才会自动回滚事务。也就是说,只有当抛出一个RuntimeException或其子类实例,以及Error对象,Spring才会自动回滚事务。如果事务方法中抛出Checked异常,则事务不会自动回滚。

通过使用rollback-for属性可强制Spring遇到特定Checked异常时自动回滚事务,下面的XML配置片段示范了这种用法。

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        
        <tx:method name="get*" read-only="true" rollback-for="exception.NoItemException"/>
        <tx:method name="*"/>
    tx:attributes>
tx:advice>

如果想让事务方法抛出指定异常时强制不回滚事务,则可通过no-rollback-for属性来指定,如下面的配置片段所示。

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        
        <tx:method name="get*" read-only="true" no-rollback-for="exception.NoItemException"/>
        <tx:method name="*"/>
    tx:attributes>
tx:advice>

4.使用@Transactional

Spring还允许将事务配置在设置在Java类定义中,这需要借助于@Transactional,该注解既可用于修饰Spring Bean类,也可用于修饰Bean类中的某个方法。
如果使用@Transactional修饰Bean类,表明这些事务设置对整个Bean类起作用;如果使用@Transactional修饰Bean类的某个方法,表明这些事务设置只对该方法有效。

使用@Transactional时可指定如下方法。
☞ isolation:用于指定事务隔离的级别。默认为底层事务的隔离级别
☞ noRollbackFor:指定遇到指定异常时强制不回滚事务
☞ noRollbackForClassName:指定遇到指定多个异常时强制不回滚事务。该属性值可以指定多个异常类名
☞ propagation:指定事务传播属性
☞ readOnly:指定事务是否只读
☞ rollbackFor:指定遇到指定异常时强制回滚事务
☞ rollbackForClassName:指定遇到指定多个异常时强制回滚事务。该属性值可以指定多个异常类名
☞ timeout:指定事务的超时时长

根据上面的解释不难看出,其实该Annotation所指定的属性与元素中能指定的事务属性基本上是对应的,他们的意义也基本相似。
下面使用@Transactional修饰需要添加事务的方法

public class NewsDaoImpl implements NewsDao {

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void insert(String title, String content) {
        .........
    }
}

上面的Bean类中insert()方法使用了@Transactional修饰,表明该方法就会具有事务性。仅使用这个Annotation修饰还不够,还需要让Spring根据Annotation来配置事务代理。所以还需要在Spring配置文件中增加如下配置片段:



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


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

你可能感兴趣的:(Spring)