Spring的事务管理

    技术2022-05-11  32

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

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

    public interface PlatformTransactionManager

    {

        //平台无关的获得事务的方法

        TransactionStatus getTransaction(TransactionDefinition definition)

            throws TransactionException;

        //平台无关的事务提交方法

        void commit(TransactionStatus status) throws TransactionException;

        //平台无关的事务回滚方法

        void rollback(TransactionStatus status) throws TransactionException;

    }

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

    PlatformTransactionManager接口有许多不同的实现类,应用程序面向与平台无关的接口编程,对不同平台的底层支持,由 PlatformTransactionManager接口的实现类完成。从而,应用程序无须与具体的事务API耦合。因此,使用 PlatformTransactionManager接口,可将代码从具体的事务API中解耦出来。

    即使使用特定容器管理的JTA,代码依然无须执行JNDI查找,无须与特定的JTA资源耦合在一起。通过配置文件,JTA资源传给PlatformTransactionManager的实现类。因此,程序的代码可在JTA事务管理和非JTA事务管理之间轻松切换。

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

    getTransaction(TransactionDefinition definition)返回的TransactionStatus对象,可能是一个新的事务,也可能是一个已经存在的事务对象。如果当前执行的线程已经处 于事务管理下,返回当前线程的事务对象,否则,返回当前线程的调用堆栈已有的事务对象。

    TransactionDefinition接口定义了一个事务规则,该接口必须指定如下几个属性值:

      ● 事务隔离,当前事务和其他事务的隔离程度。例如,这个事务能否看到其他事务未提交的数据等。

      ● 事务传播,通常,在事务中执行的代码都会在当前事务中运行。但是,如果一个事务上下文已经存在,有几个选项可指定该事务性方法的执行行为。例如,大多数情 况下,简单地在现有的事务上下文中运行;或者挂起现有事务,创建一个新的事务。Spring提供EJB CMT(Contain Manager Transaction,容器管理事务)中所有的事务传播选项。

      ● 事务超时,事务在超时前能运行多久。事务的最长持续时间。如果事务一直没有被提交或回滚,将在超出该时间后,系统自动回滚事务。

      ● 只读状态,只读事务不修改任何数据。在某些情况下(例如使用Hibernate时),只读事务是非常有用的优化。

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

    public interface TransactionStatus

    {

        //判断事务是否是新建的事务

        boolean isNewTransaction();

        //设置事务回滚

        void setRollbackOnly();

        //查询事务是否已有回滚标志

        boolean isRollbackOnly();

    }

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

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

    JDBC数据源的局部事务策略:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans "

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "

           xsi:schemaLocation="http://www.springframework.org/schema/beans

           http://www.springframework.org/schema/beans/spring-beans.xsd ">

    <!-- 定义数据源Bean,使用C3P0数据源实现 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">

        <!-- 指定连接数据库的驱动 -->

        <property name="driverClass" value="com.mysql.jdbc.Driver"/>

        <!-- 指定连接数据库的URL -->

        <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

        <!-- 指定连接数据库的用户名 -->

        <property name="user" value="root"/>

        <!-- 指定连接数据库的密码 -->

        <property name="password" value="32147"/>

        <!-- 指定连接数据库连接池的最大连接数 -->

        <property name="maxPoolSize" value="40"/>

        <!-- 指定连接数据库连接池的最小连接数 -->

        <property name="minPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的初始化连接数 -->

        <property name="initialPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的连接最大空闲时间 -->

        <property name="maxIdleTime" value="20"/>

    </bean>

    <!-- 配置JDBC数据源的局部事务管理器 -->

    <!-- 使用DataSourceTransactionManager 类,该类实现PlatformTransactionManager接口 -->

    <!-- 针对采用数据源连接的特定实现 -->

    <bean id="transactionManager"

            class="org.springframework.jdbc.datasource.         DataSourceTransactionManager">

            <!-- DataSourceTransactionManager  bean需要依赖注入一个DataSource         bean的引用 -->

             <property name="dataSource" ref="dataSource"/>

        </bean>

    </beans>

    对于容器管理JTA数据源,全局事务策略的配置文件如下:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans "

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "

           xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd ">

        <!-- 配置JNDI数据源Bean -->

        <bean id="dataSource" class="org.springframework.jndi.     JndiObjectFactoryBean">

        <!--  容器管理数据源的JNDI -->

             <property name="jndiName" value="jdbc/jpetstore"/>

        </bean>

        <!-- 使用JtaTransactionManager类,该类实现PlatformTransactionManager接     口 -->

        <!-- 针对采用全局事务管理的特定实现 -->

        <!-- JtaTransactionManager不需要知道数据源,或任何其他特定资源 -->

        <!-- 因为它使用容器的全局事务管理 -->

        <bean id="transactionManager"

            class="org.springframework.transaction.jta.         JtaTransactionManager" />

    </beans>

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

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans "

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "

           xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd ">

        <!-- 定义数据源Bean,使用C3P0数据源实现 -->

        <bean id="dataSource" class="com.mchange.v2.c3p0.     ComboPooledDataSource" destroy-method="close">

            <!-- 指定连接数据库的驱动 -->

            <property name="driverClass" value="com.mysql.jdbc.Driver"/>

            <!-- 指定连接数据库的URL -->

            <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

            <!-- 指定连接数据库的用户名 -->

            <property name="user" value="root"/>

            <!-- 指定连接数据库的密码 -->

            <property name="password" value="32147"/>

            <!-- 指定连接数据库连接池的最大连接数 -->

            <property name="maxPoolSize" value="40"/>

            <!-- 指定连接数据库连接池的最小连接数 -->

            <property name="minPoolSize" value="1"/>

            <!-- 指定连接数据库连接池的初始化连接数 -->

            <property name="initialPoolSize" value="1"/>

            <!-- 指定连接数据库连接池的连接最大空闲时间 -->

            <property name="maxIdleTime" value="20"/>

        </bean>

        <!-- 定义Hibernate的SessionFactory -->

        <bean id="sessionFactory" class="org.springframework.orm.hibernate3.     LocalSessionFactoryBean">

            <!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource -->

            <property name="dataSource" ref="dataSource"/>

            <!-- mappingResources属性用来列出全部映射文件 -->

            <property name="mappingResources">

                  <list>

                      <!-- 以下用来列出所有的PO映射文件 -->

                    <value>lee/MyTest.hbm.xml</value>

                  </list>

            </property>

              <!-- 定义Hibernate的SessionFactory的属性 -->

            <property name="hibernateProperties">

                 <props>

                    <!-- 指定Hibernate的连接方言 -->

                    <prop key="hibernate.dialect">org.hibernate.dialect.                 MySQLDialect</prop>

                    <!-- 是否根据Hibernate映射创建数据表时,选择create、update、                 create-drop -->

                      <prop key="hibernate.hbm2ddl.auto">update</prop>

                 </props>

            </property>

        </bean>

        <!-- 配置Hibernate的局部事务管理器 -->

        <!-- 使用HibernateTransactionManager类,该类是PlatformTransactionManager     接口,针对采用Hibernate持久化连接的特定实现 -->

        <bean id="transactionManager"

          class="org.springframework.orm.hibernate3.       HibernateTransactionManager">

                <!-- HibernateTransactionManager  Bean需要依赖注入一个             SessionFactorybean的引用 -->

             <property name="sessionFactory" ref="sessionFactory"/>

         </bean>

    </beans>

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

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans "

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "

           xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd ">

        <!-- 配置JNDI数据源Bean -->

        <bean id="dataSource" class="org.springframework.jndi.     JndiObjectFactoryBean">

            <!--  容器管理数据源的JNDI -->

             <property name="jndiName" value="jdbc/jpetstore"/>

        </bean>

        <!--定义Hibernate的SessionFactory -->

        <bean id="sessionFactory" class="org.springframework.orm.hibernate3.     LocalSessionFactoryBean">

            <!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource Bean -->

            <property name="dataSource" ref="dataSource"/>

            <!-- mappingResources属性用来列出全部映射文件 -->

            <property name="mappingResources">

                  <list>

                      <!-- 以下用来列出所有的PO映射文件 -->

                    <value>lee/MyTest.hbm.xml</value>

                  </list>

            </property>

              <!-- 定义Hibernate的SessionFactory的属性 -->

            <property name="hibernateProperties">

                 <props>

                    <!-- 指定Hibernate的连接方言 -->

                    <prop key="hibernate.dialect">org.hibernate.dialect.                 MySQLDialect</prop>

                    <!-- 是否根据Hiberante映射创建数据表时,选择create、update、                 create-drop -->

                      <prop key="hibernate.hbm2ddl.auto">update</prop>

                 </props>

             </property>

        </bean>

        <!-- 使用JtaTransactionManager类,该类是PlatformTransactionManager接口,             针对采用数据源连接的特定实现 -->

        <!--  JtaTransactionManager不需要知道数据源,或任何其他特定资源,

                因为使用容器的全局事务管理 -->

        <bean id="transactionManager"

               class="org.springframework.transaction.jta.            JtaTransactionManager" />

    </beans>

    不论采用哪种持久层访问技术,只要使用JTA数据源,Spring事务管理器的配置都是一样的,因为它们都采用的是全局事务管理。

    可以看到,仅仅通过配置文件的修改,就可以在不同的事务管理策略间切换,即使从局部事务到全局事务的切换。

    提示:Spring所支持的事务策略非常灵活,Spring的事务策略允许应用程序在不同事务策略之间自由切换,即使需要在局部事务策略和全局事务 策略之间切换,只需要修改配置文件,而应用程序的代码无须任何改变。这种灵活的设计,又何尝不是因为面向接口编程带来的优势,可见面向接口编程给应用程序 更好的适应性。

    6.3.2  Spring事务策略的优势 虽然在上面的配置片段中,仅仅配置了JDBC局部事务管理器、Hibernate局部事务管理器、JDBC全局事务管理器等。但Spring支持大部分持久化策略的事务管理器。

    不论采用何种持久化策略,Spring都提供了一致的事务抽象,因此,应用开发者能在任何环境下,使用一致的编程模型。无须更改代码,应用就可在不同的事务管理策略中切换。Spring同时支持声明式事务管理和编程式事务管理。

    使用编程式事务管理,开发者使用的是Spring事务抽象,而无须使用任何具体的底层事务API。Spring的事务管理将代码从底层具体的事务API中抽象出来,该抽象可以使用在任何底层事务基础之上。

    使用声明式策略,开发者通常书写很少的事务管理代码,因此,不依赖Spring或任何其他事务API。Spring的声明式事务无须任何额外的容器 支持,Spring容器本身管理声明式事务。使用声明事务策略,无须在业务代码中书写任何事务代码,可以让开发者更好地专注于业务逻辑的实现。 Spring管理的事务支持多个事务资源的跨越,但无法支持跨越远程调用的事务上下文传播。

    6.3.3  使用TransactionProxyFactoryBean创建事务代理 Spring同时支持编程式事务策略和声明式事务策略,大部分时候,都推荐采用声明式事务策略,使用声明事务策略的优势十分明显:

      ● 声明式事务能大大降低开发者的代码书写量。而且声明式事务几乎不需要影响应用的代码。因此,无论底层事务策略如何变化,应用程序无须任何改变。

      ● 应用程序代码无须任何事务处理代码,可以更专注于业务逻辑的实现。

      ● Spring则可对任何POJO的方法提供事务管理,而且Spring的声明式事务管理无须容器的支持,可在任何环境下使用。

      ● EJB的CMT无法提供声明式回滚规则。而通过配置文件,Spring可指定事务在遇到特定异常时自动回滚。Spring不仅可在代码中使用setRollbackOnly回滚事务,也可在配置文件中配置回滚规则。

      ● 由于Spring采用AOP的方式管理事务,因此,可以在事务回滚动作中插入用户自己的动作,而不仅仅是执行系统默认的回滚。

    提示:本节不打算全面介绍Spring的各种事务策略,因此本节不会介绍编程式事务。如果读者需要更全面了解Spring事务的相关方面,请参阅笔者所著的《Spring2.0宝典》     一书。

    对于采用声明式事务策略,可以使用TransactionProxyFactoryBean来配置事务代理Bean。正如它的类名所暗示的,它是一个工厂Bean,工厂Bean用于生成一系列的Bean实例,这一系列的Bean实例都是Proxy。

    可能读者已经想到了,既然TransactionProxyFactoryBean产生的是代理Bean,可见这种事务代理正是基于Spring AOP组件的。配置TransactionProxyFactoryBean时,一样需要指定目标Bean。

    每个TransactionProxyFactoryBean为一个目标Bean生成事务代理,事务代理的方法改写了目标Bean的方法,就是在目标Bean的方法执行之前加入开始事务,在目标Bean的方法正常结束之前提交事务,如果遇到特定异常则回滚事务。

    TransactionProxyFactoryBean创建事务代理时,需要了解当前事务所处的环境,该环境属性通过 PlatformTransactionManager实例传入,而相关事务传入规则在TransactionProxy- FactoryBean的定义中给出。

    下面给出声明式事务配置文件的完整代码:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans "

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "

           xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd ">

        <!-- 定义数据源Bean,使用C3P0数据源实现 -->

        <bean id="dataSource" class="com.mchange.v2.c3p0.     ComboPooledDataSource" destroy-method="close">

            <!-- 指定连接数据库的驱动 -->

            <property name="driverClass" value="com.mysql.jdbc.Driver"/>

            <!-- 指定连接数据库的URL -->

            <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

            <!-- 指定连接数据库的用户名 -->

            <property name="user" value="root"/>

            <!-- 指定连接数据库的密码 -->

            <property name="password" value="32147"/>

            <!-- 指定连接数据库连接池的最大连接数 -->

            <property name="maxPoolSize" value="40"/>

            <!-- 指定连接数据库连接池的最小连接数 -->

            <property name="minPoolSize" value="1"/>

            <!-- 指定连接数据库连接池的初始化连接数 -->

            <property name="initialPoolSize" value="1"/>

            <!-- 指定连接数据库连接池的连接最大空闲时间 -->

            <property name="maxIdleTime" value="20"/>

        </bean>

        <!-- 定义Hibernate的SessionFactory -->

        <bean id="sessionFactory" class="org.springframework.orm.hibernate3.     LocalSessionFactoryBean">

            <!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource -->

            <property name="dataSource" <ref="dataSource"/>

            <!-- mappingResources属性用来列出全部映射文件 -->

            <property name="mappingResources">

                  <list>

                      <!-- 以下用来列出所有的PO映射文件 -->

                    <value>lee/Person.hbm.xml</value>

                  </list>

            </property>

              <!-- 定义Hibernate的SessionFactory属性 -->

            <property name="hibernateProperties">

                 <props>

                    <!-- 指定Hibernate的连接方言 -->

                    <prop key="hibernate.dialect">org.hibernate.dialect.                 MySQLDialect</prop>

                    <!-- 是否根据Hiberante映射创建数据表时,选择create、update、                 create-drop -->

                      <prop key="hibernate.hbm2ddl.auto">update</prop>

                 </props>

            </property>

        </bean>

        <!-- 配置DAO Bean,该Bean将作为目标Bean使用 -->

        <bean id="personDAOTarget" class="lee.PersonDaoImpl">

            <!-- 采用依赖注入来传入SessionFactory的引用 -->

            <property name="sessionFactory" ref="sessionFactory"/>

        </bean>

        <!-- 配置Hibernate的事务管理器 -->

        <!-- 使用HibernateTransactionManager类,该类实现PlatformTransactionManager     接口,针对采用Hibernate持久化连接的特定实现 -->

        <bean id="transactionManager"

            class="org.springframework.orm.hibernate3.         HibernateTransactionManager">

            <!-- HibernateTransactionManager Bean,它需要依赖注入一个SessionFactory         Bean的引用 -->

            <property name="sessionFactory" ref="sessionFactory"/>

        </bean>

        <!--  配置personDAOTarget Bean的事务代理 -->

        <bean id="personDAO"

            class="org.springframework.transaction.interceptor.         TransactionProxyFactoryBean">

              <!-- 依赖注入PlatformTransactionManager的bean引用,此处使用           Hibernate的bean -->

              <!-- 局部事务器,因此transactionManager 传入Hibernate事务管理器的           引用 -->

              <property name="transactionManager" ref="transactionManager"/>

              <!-- 需要生成代理的目标bean -->

              <property name="target" ref="personDAOTarget"/>

              <!-- 指定事务属性 -->

              <property name="transactionAttributes">

                  <props>

                      <!-- 以下部分为定义事务回滚规则 -->

                        <prop key="insert*">PROPAGATION_REQUIRED,                   -MyCheckedException</prop>

                        <prop key="update*">PROPAGATION_REQUIRED</prop>

                        <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>

                </props>

            </property>

        </bean>

    </beans>

    在上面的定义文件中,没有对DAO对象采用Service层包装。通常情况下,DAO层上应有一层Service层。事务代理则以Service层 Bean为目标Bean。此处为了简化配置,TransactionProxyFactoryBean直接以DAO bean作为目标bean,这一点不会影响事务代理的生成。

    事务回滚规则部分定义了三个回滚规则:

    第一个回滚规则表示所有以insert开始的方法,都应该满足该事务规则。PROPAGATION_REQUIRED事务传播规则指明,该方法必须 处于事务环境中,如果当前执行线程已处于事务环境下,则直接执行;否则,启动新的事务然后执行该方法。该规则还指定,如果方法抛出 MyCheckedException的实例及其子类的实例,则强制回滚。MyCheckedException前的“-”表示强制回滚;“+”则表示强 制提交,即某些情况下,即使抛出异常也强制提交;

    第二个回滚规则表示所有以update开头的方法,都遵守PROPAGATION_REQUIRED的事务传播规则;

    第三个回滚规则表示除前面规定的方法外,其他所有方法都采用PROPAGATION_ REQUIRED事务传播规则,而且只读。

    常见的事务传播规则有如下几个:

      ● PROPAGATION_MANDATORY,要求调用该方法的线程必须处于事务环境中,否则抛出异常。

      ● PROPAGATION_NESTED,如果执行该方法的线程已处于事务环境下,依然启动新的事务,方法在嵌套的事务里执行。如果执行该方法的线程序并未处于事务中,也启动新的事务,然后执行该方法,此时与PROPAGATION_REQUIRED相同。

      ● PROPAGATION_NEVER,不允许调用该方法的线程处于事务环境下,如果调用该方法的线程处于事务环境下,则抛出异常。

      ● PROPAGATION_NOT_SUPPORTED,如果调用该方法的线程处在事务中,则先暂停当前事务,然后执行该方法。

      ● PROPAGATION_REQUIRED,要求在事务环境中执行该方法,如果当前执行线程已处于事务中,则直接调用;如果当前执行线程不处于事务中,则启动新的事务后执行该方法。

      ● PROPAGATION_REQUIRES_NEW,该方法要求有一个线程在新的事务环境中执行,如果当前执行线程已处于事务中,先暂停当前事务,启动新的事务后执行该方法;如果当前调用线程不处于事务中,则启动新的事务后执行该方法。

      ● PROPAGATION_SUPPORTS,如果当前执行线程处于事务中,则使用当前事务,否则不使用事务。

    程序里原来使用personDAO的地方,无须变化。因为,配置文件里将personDAO目标Bean的id改成 personDAOTarget,为TransactionProxyFactoryBean工厂Bean所产生的代理Bean命名为 personDAO。该代理Bean会包含原有personDAO的所有方法,而且为这些方法增加了不同的事务处理规则。

    程序面向PersonDaoImpl类所实现的接口编程,TransactionProxyFactoryBean生成的代理Bean也会实现 TransactionProxyFactoryBean接口。因此,原有的程序中调用DAO组件的代码无须任何改变。程序运行时,由事务代理完成原来目 标Bean完成的工作。

    事实上,Spring不仅支持对接口的代理,整合CGLIB后,Spring甚至可对具体类生成代理。只要设置proxyTargetClass属 性为true就可以。如果目标Bean没有实现任何接口,proxyTargetClass属性默认被设为true,此时Spring会对具体类生成代 理。当然,通常建议面向接口编程,而不要面向具体的实现类编程。

    6.3.4  使用继承简化事务配置 仔细观察配置文件中两个事务代理Bean的配置时,发现两个事务代理Bean的大部分配置完全相同,如果配置文件中包含大量这样的事务代理Bean配置,配置文件将非常臃肿。考虑到大部分事务代理Bean的配置都大同小异,可以使用Bean继承来简化事务代理的配置。

    正如前面部分介绍的,Bean继承是将所有子Bean中相同的配置定义成一个模板,并将此模板Bean定义成一个抽象Bean。考虑所有事务代理Bean中,有如下部分是大致相   同的:

      ● 事务代理Bean所使用的事务管理器。

      ● 事务传播规则。

    因此,现在配置文件中定义如下的事务代理模板Bean,其配置代码如下:

    <!-- 定义所有事务代理Bean的模板 -->

    <bean id="txProxyTemplate" abstract="true"

            class="org.springframework.transaction.interceptor.         TransactionProxyFactoryBean">

        <!-- 为事务代理Bean注入生成代理所需的PlatformTransactionManager实例 -->

        <property name="transactionManager" ref="transactionManager"/>

            <!-- 定义生成事务代理通用的事务属性 -->

            <property name="transactionAttributes">

                <props>

                    <!-- 所有的方法都应用PROPAGATION_REQUIRED的事务传播规则 -->

                    <prop key="*">PROPAGATION_REQUIRED</prop>

                </props>

        </property>

    </bean>

    而真正的事务代理Bean,则改为继承上面的事务模板Bean。考虑到将目标Bean定义在Spring容器中可能增加未知的风险,因此将目标Bean定义成嵌套Bean。

    <!-- 让事务代理Bean继承模板Bean -->

    <bean id="personDAO" parent="txProxyTemplate">

        <!-- 这里采用嵌套Bean的方式来定义目标Bean,当然也可以引用已存在的Bean -->

        <property name="target">

            <bean class="lee.personDAO"/>

        </property>

    </bean>

    此时的personDAO Bean无须具体地定义事务属性,它将在其父Bean txProxyTemplate中获取事务定义属性。此处采用嵌套Bean来定义目标Bean,因此,并未将目标Bean直接暴露在Spring的上下文 中让其他模块调用。当然,也可采用一个已经存在的Bean作为目标Bean;子Bean的事务属性定义,完全可覆盖事务代理模板里的事务属性定义。如下例 所示:

    <!-- 让事务代理bean继承模板Bean -->

    <bean id="personDAO" parent="txProxyTemplate">

        <!-- 这里,采用引用已存在的bean的方式来定义目标Bean -->

        <property name="target" ref ="personDAOTarget"/>

        <!-- 覆盖事务代理模板bean中的事务属性定义 -->

        <property name="transactionAttributes">

            <props>

                <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>

                <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

                <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>

             </props>

        </property>

    </bean>

    可见,采用Bean继承方式定义事务代理的方式,可以很好地简化事务代理的配置,可以避免配置事务代理Bean时的冗余配置。

    提示:使用Bean继承可以很好地简化事务代理Bean的配置,通过将各事务代理Bean共同的配置信息提取成事务模板Bean,可以让实际的事务 代理Bean的配置更加简洁;而且,配置方式相当直观。尽量将目标Bean配置成嵌套Bean,这样的方式可以保证更好的内聚性。

    如果读者还记得前面介绍的AOP知识,应该知道还有一种更加简洁的配置,就是利用Bean后处理器,让Bean后处理器为容器中其他Bean自动创建事务代理。

    6.3.5  使用自动创建代理简化事务配置 回顾6.2.6节和6.2.7节,读者可能已经想到如何自动创建代理。是的,正是通过6.2.6节和6.2.7节所给出的两个自动代理创建类来生成事务代理。

    正如前文已经提到的,使用BeanNameAutoProxyCreator和DefaultAdvisorAutoProxy- Creator来创建代理时,并不一定是创建事务代理,关键在于传入的拦截器,如果传入事务拦截器,将可自动生成事务代理。

    下面是使用BeanNameAutoProxyCreator自动生成事务代理的配置文件:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及相应的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans "

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "

           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd ">

        <!-- 定义数据源Bean,使用C3P0数据源实现 -->

        <bean id="dataSource" class="com.mchange.v2.c3p0.     ComboPooledDataSource" destroy-method="close">

            <!-- 指定连接数据库的驱动 -->

            <property name="driverClass" value="com.mysql.jdbc.Driver"/>

            <!-- 指定连接数据库的URL -->

            <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

            <!-- 指定连接数据库的用户名 -->

            <property name="user" value="root"/>

            <!-- 指定连接数据库的密码 -->

            <property name="password" value="32147"/>

            <!-- 指定连接数据库连接池的最大连接数 -->

            <property name="maxPoolSize" value="40"/>

            <!-- 指定连接数据库连接池的最小连接数 -->

            <property name="minPoolSize" value="1"/>

            <!-- 指定连接数据库连接池的初始化连接数 -->

            <property name="initialPoolSize" value="1"/>

            <!-- 指定连接数据库连接池的连接最大空闲时间 -->

            <property name="maxIdleTime" value="20"/>

        </bean>

        <!-- 使用JDBC的局部事务策略 -->

        <bean id="transactionManager"

            class="org.springframework.jdbc.datasource.DataSource-         TransactionManager">

            <!-- 为事务管理器注入所需的数据源Bean -->

            <property name="dataSource" ref="dataSource"/>

        </bean>

        <!-- 配置目标Bean,该目标Bean将由Bean后处理器自动生成代理 -->

        <bean id="test1" class="lee.TransactionTestImpl">

            <!-- 依赖注入目标Bean所必需的数据源Bean -->

            <property name="ds" ref="dataSource"/>

        </bean>

        <!-- 配置目标Bean,该目标Bean将由Bean后处理器自动生成代理 -->

        <bean id="test2" class="lee.TestImpl">

            <!-- 依赖注入目标Bean所必需的数据源Bean -->

            <property name="ds" ref="dataSource"/>

        </bean>

        <!-- 配置事务拦截器Bean -->

        <bean id="transactionInterceptor"

            class="org.springframework.transaction.interceptor.         TransactionInterceptor">

            <!-- 事务拦截器bean需要依赖注入一个事务管理器 -->

            <property name="transactionManager" ref="transactionManager"/>

            <property name="transactionAttributes">

                <!-- 下面定义事务传播属性 -->

                <props>

                    <prop key="insert*">PROPAGATION_REQUIRED</prop>

                    <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

                    <prop key="*">PROPAGATION_REQUIRED</prop>

                </props>

            </property>

        </bean>

        <!-- 定义BeanNameAutoProxyCreator的Bean后处理器 -->

        <bean class="org.springframework.aop.framework.autoproxy.     BeanNameAutoProxyCreator">

        <!-- 指定对满足哪些bean name的bean自动生成业务代理 -->

            <property name="beanNames">

                <!-- 下面是所有需要自动创建事务代理的Bean -->

                <list>

                    <value>test1</value>

                    <value>test2</value>

                </list>

                <!-- 此处可增加其他需要自动创建事务代理的Bean -->

            </property>

            <!-- 下面定义BeanNameAutoProxyCreator所需的拦截器 -->

            <property name="interceptorNames">

                <list>

                    <value>transactionInterceptor</value>

                    <!-- 此处可增加其他新的Interceptor -->

                </list>

            </property>

        </bean>

    </beans>

    如果配置文件中仅有两个目标Bean,可能不能很清楚地看出这种自动创建代理配置方式的优势,但如果有更多目标Bean需要自动创建事务代理,则可 以很好地体会到这种配置方式的优势:配置文件只需要简单地配置目标Bean,然后在BeanNameAutoProxyCreator配置中增加一行即 可。

    提示:使用BeanNameAutoProxyCreator可以自动创建事务代理,使用DefaultAdvisor- AutoProxyCreator也可自动创建事务代理。关于后一个Bean后处理器的配置方式

    再次表示感谢

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

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

    public interface PlatformTransactionManager

    {

        //平台无关的获得事务的方法

        TransactionStatus getTransaction(TransactionDefinition definition)

            throws TransactionException;

        //平台无关的事务提交方法

        void commit(TransactionStatus status) throws TransactionException;

        //平台无关的事务回滚方法

        void rollback(TransactionStatus status) throws TransactionException;

    }

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

    PlatformTransactionManager接口有许多不同的实现类,应用程序面向与平台无关的接口编程,对不同平台的底层支持,由 PlatformTransactionManager接口的实现类完成。从而,应用程序无须与具体的事务API耦合。因此,使用 PlatformTransactionManager接口,可将代码从具体的事务API中解耦出来。

    即使使用特定容器管理的JTA,代码依然无须执行JNDI查找,无须与特定的JTA资源耦合在一起。通过配置文件,JTA资源传给PlatformTransactionManager的实现类。因此,程序的代码可在JTA事务管理和非JTA事务管理之间轻松切换。

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

    getTransaction(TransactionDefinition definition)返回的TransactionStatus对象,可能是一个新的事务,也可能是一个已经存在的事务对象。如果当前执行的线程已经处 于事务管理下,返回当前线程的事务对象,否则,返回当前线程的调用堆栈已有的事务对象。

    TransactionDefinition接口定义了一个事务规则,该接口必须指定如下几个属性值:

      ● 事务隔离,当前事务和其他事务的隔离程度。例如,这个事务能否看到其他事务未提交的数据等。

      ● 事务传播,通常,在事务中执行的代码都会在当前事务中运行。但是,如果一个事务上下文已经存在,有几个选项可指定该事务性方法的执行行为。例如,大多数情 况下,简单地在现有的事务上下文中运行;或者挂起现有事务,创建一个新的事务。Spring提供EJB CMT(Contain Manager Transaction,容器管理事务)中所有的事务传播选项。

      ● 事务超时,事务在超时前能运行多久。事务的最长持续时间。如果事务一直没有被提交或回滚,将在超出该时间后,系统自动回滚事务。

      ● 只读状态,只读事务不修改任何数据。在某些情况下(例如使用Hibernate时),只读事务是非常有用的优化。

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

    public interface TransactionStatus

    {

        //判断事务是否是新建的事务

        boolean isNewTransaction();

        //设置事务回滚

        void setRollbackOnly();

        //查询事务是否已有回滚标志

        boolean isRollbackOnly();

    }

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

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

    JDBC数据源的局部事务策略:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans "

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "

           xsi:schemaLocation="http://www.springframework.org/schema/beans

           http://www.springframework.org/schema/beans/spring-beans.xsd ">

    <!-- 定义数据源Bean,使用C3P0数据源实现 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">

        <!-- 指定连接数据库的驱动 -->

        <property name="driverClass" value="com.mysql.jdbc.Driver"/>

        <!-- 指定连接数据库的URL -->

        <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

        <!-- 指定连接数据库的用户名 -->

        <property name="user" value="root"/>

        <!-- 指定连接数据库的密码 -->

        <property name="password" value="32147"/>

        <!-- 指定连接数据库连接池的最大连接数 -->

        <property name="maxPoolSize" value="40"/>

        <!-- 指定连接数据库连接池的最小连接数 -->

        <property name="minPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的初始化连接数 -->

        <property name="initialPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的连接最大空闲时间 -->

        <property name="maxIdleTime" value="20"/>

    </bean>

    <!-- 配置JDBC数据源的局部事务管理器 -->

    <!-- 使用DataSourceTransactionManager 类,该类实现PlatformTransactionManager接口 -->

    <!-- 针对采用数据源连接的特定实现 -->

    <bean id="transactionManager"

            class="org.springframework.jdbc.datasource.         DataSourceTransactionManager">

            <!-- DataSourceTransactionManager  bean需要依赖注入一个DataSource         bean的引用 -->

             <property name="dataSource" ref="dataSource"/>

        </bean>

    </beans>

    对于容器管理JTA数据源,全局事务策略的配置文件如下:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans "

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "

           xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd ">

        <!-- 配置JNDI数据源Bean -->

        <bean id="dataSource" class="org.springframework.jndi.     JndiObjectFactoryBean">

        <!--  容器管理数据源的JNDI -->

             <property name="jndiName" value="jdbc/jpetstore"/>

        </bean>

        <!-- 使用JtaTransactionManager类,该类实现PlatformTransactionManager接     口 -->

        <!-- 针对采用全局事务管理的特定实现 -->

        <!-- JtaTransactionManager不需要知道数据源,或任何其他特定资源 -->

        <!-- 因为它使用容器的全局事务管理 -->

        <bean id="transactionManager"

            class="org.springframework.transaction.jta.         JtaTransactionManager" />

    </beans>

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

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans "

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "

           xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd ">

        <!-- 定义数据源Bean,使用C3P0数据源实现 -->

        <bean id="dataSource" class="com.mchange.v2.c3p0.     ComboPooledDataSource" destroy-method="close">

            <!-- 指定连接数据库的驱动 -->

            <property name="driverClass" value="com.mysql.jdbc.Driver"/>

            <!-- 指定连接数据库的URL -->

            <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

            <!-- 指定连接数据库的用户名 -->

            <property name="user" value="root"/>

            <!-- 指定连接数据库的密码 -->

            <property name="password" value="32147"/>

            <!-- 指定连接数据库连接池的最大连接数 -->

            <property name="maxPoolSize" value="40"/>

            <!-- 指定连接数据库连接池的最小连接数 -->

            <property name="minPoolSize" value="1"/>

            <!-- 指定连接数据库连接池的初始化连接数 -->

            <property name="initialPoolSize" value="1"/>

            <!-- 指定连接数据库连接池的连接最大空闲时间 -->

            <property name="maxIdleTime" value="20"/>

        </bean>

        <!-- 定义Hibernate的SessionFactory -->

        <bean id="sessionFactory" class="org.springframework.orm.hibernate3.     LocalSessionFactoryBean">

            <!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource -->

            <property name="dataSource" ref="dataSource"/>

            <!-- mappingResources属性用来列出全部映射文件 -->

            <property name="mappingResources">

                  <list>

                      <!-- 以下用来列出所有的PO映射文件 -->

                    <value>lee/MyTest.hbm.xml</value>

                  </list>

            </property>

              <!-- 定义Hibernate的SessionFactory的属性 -->

            <property name="hibernateProperties">

                 <props>

                    <!-- 指定Hibernate的连接方言 -->

                    <prop key="hibernate.dialect">org.hibernate.dialect.                 MySQLDialect</prop>

                    <!-- 是否根据Hibernate映射创建数据表时,选择create、update、                 create-drop -->

                      <prop key="hibernate.hbm2ddl.auto">update</prop>

                 </props>

            </property>

        </bean>

        <!-- 配置Hibernate的局部事务管理器 -->

        <!-- 使用HibernateTransactionManager类,该类是PlatformTransactionManager     接口,针对采用Hibernate持久化连接的特定实现 -->

        <bean id="transactionManager"

          class="org.springframework.orm.hibernate3.       HibernateTransactionManager">

                <!-- HibernateTransactionManager  Bean需要依赖注入一个             SessionFactorybean的引用 -->

             <property name="sessionFactory" ref="sessionFactory"/>

         </bean>

    </beans>

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

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans "

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "

           xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd ">

        <!-- 配置JNDI数据源Bean -->

        <bean id="dataSource" class="org.springframework.jndi.     JndiObjectFactoryBean">

            <!--  容器管理数据源的JNDI -->

             <property name="jndiName" value="jdbc/jpetstore"/>

        </bean>

        <!--定义Hibernate的SessionFactory -->

        <bean id="sessionFactory" class="org.springframework.orm.hibernate3.     LocalSessionFactoryBean">

            <!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource Bean -->

            <property name="dataSource" ref="dataSource"/>

            <!-- mappingResources属性用来列出全部映射文件 -->

            <property name="mappingResources">

                  <list>

                      <!-- 以下用来列出所有的PO映射文件 -->

                    <value>lee/MyTest.hbm.xml</value>

                  </list>

            </property>

              <!-- 定义Hibernate的SessionFactory的属性 -->

            <property name="hibernateProperties">

                 <props>

                    <!-- 指定Hibernate的连接方言 -->

                    <prop key="hibernate.dialect">org.hibernate.dialect.                 MySQLDialect</prop>

                    <!-- 是否根据Hiberante映射创建数据表时,选择create、update、                 create-drop -->

                      <prop key="hibernate.hbm2ddl.auto">update</prop>

                 </props>

             </property>

        </bean>

        <!-- 使用JtaTransactionManager类,该类是PlatformTransactionManager接口,             针对采用数据源连接的特定实现 -->

        <!--  JtaTransactionManager不需要知道数据源,或任何其他特定资源,

                因为使用容器的全局事务管理 -->

        <bean id="transactionManager"

               class="org.springframework.transaction.jta.            JtaTransactionManager" />

    </beans>

    不论采用哪种持久层访问技术,只要使用JTA数据源,Spring事务管理器的配置都是一样的,因为它们都采用的是全局事务管理。

    可以看到,仅仅通过配置文件的修改,就可以在不同的事务管理策略间切换,即使从局部事务到全局事务的切换。

    提示:Spring所支持的事务策略非常灵活,Spring的事务策略允许应用程序在不同事务策略之间自由切换,即使需要在局部事务策略和全局事务 策略之间切换,只需要修改配置文件,而应用程序的代码无须任何改变。这种灵活的设计,又何尝不是因为面向接口编程带来的优势,可见面向接口编程给应用程序 更好的适应性。

    6.3.2  Spring事务策略的优势 虽然在上面的配置片段中,仅仅配置了JDBC局部事务管理器、Hibernate局部事务管理器、JDBC全局事务管理器等。但Spring支持大部分持久化策略的事务管理器。

    不论采用何种持久化策略,Spring都提供了一致的事务抽象,因此,应用开发者能在任何环境下,使用一致的编程模型。无须更改代码,应用就可在不同的事务管理策略中切换。Spring同时支持声明式事务管理和编程式事务管理。

    使用编程式事务管理,开发者使用的是Spring事务抽象,而无须使用任何具体的底层事务API。Spring的事务管理将代码从底层具体的事务API中抽象出来,该抽象可以使用在任何底层事务基础之上。

    使用声明式策略,开发者通常书写很少的事务管理代码,因此,不依赖Spring或任何其他事务API。Spring的声明式事务无须任何额外的容器 支持,Spring容器本身管理声明式事务。使用声明事务策略,无须在业务代码中书写任何事务代码,可以让开发者更好地专注于业务逻辑的实现。 Spring管理的事务支持多个事务资源的跨越,但无法支持跨越远程调用的事务上下文传播。

    6.3.3  使用TransactionProxyFactoryBean创建事务代理 Spring同时支持编程式事务策略和声明式事务策略,大部分时候,都推荐采用声明式事务策略,使用声明事务策略的优势十分明显:

      ● 声明式事务能大大降低开发者的代码书写量。而且声明式事务几乎不需要影响应用的代码。因此,无论底层事务策略如何变化,应用程序无须任何改变。

      ● 应用程序代码无须任何事务处理代码,可以更专注于业务逻辑的实现。

      ● Spring则可对任何POJO的方法提供事务管理,而且Spring的声明式事务管理无须容器的支持,可在任何环境下使用。

      ● EJB的CMT无法提供声明式回滚规则。而通过配置文件,Spring可指定事务在遇到特定异常时自动回滚。Spring不仅可在代码中使用setRollbackOnly回滚事务,也可在配置文件中配置回滚规则。

      ● 由于Spring采用AOP的方式管理事务,因此,可以在事务回滚动作中插入用户自己的动作,而不仅仅是执行系统默认的回滚。

    提示:本节不打算全面介绍Spring的各种事务策略,因此本节不会介绍编程式事务。如果读者需要更全面了解Spring事务的相关方面,请参阅笔者所著的《Spring2.0宝典》     一书。

    对于采用声明式事务策略,可以使用TransactionProxyFactoryBean来配置事务代理Bean。正如它的类名所暗示的,它是一个工厂Bean,工厂Bean用于生成一系列的Bean实例,这一系列的Bean实例都是Proxy。

    可能读者已经想到了,既然TransactionProxyFactoryBean产生的是代理Bean,可见这种事务代理正是基于Spring AOP组件的。配置TransactionProxyFactoryBean时,一样需要指定目标Bean。

    每个TransactionProxyFactoryBean为一个目标Bean生成事务代理,事务代理的方法改写了目标Bean的方法,就是在目标Bean的方法执行之前加入开始事务,在目标Bean的方法正常结束之前提交事务,如果遇到特定异常则回滚事务。

    TransactionProxyFactoryBean创建事务代理时,需要了解当前事务所处的环境,该环境属性通过 PlatformTransactionManager实例传入,而相关事务传入规则在TransactionProxy- FactoryBean的定义中给出。

    下面给出声明式事务配置文件的完整代码:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans "

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "

           xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd ">

        <!-- 定义数据源Bean,使用C3P0数据源实现 -->

        <bean id="dataSource" class="com.mchange.v2.c3p0.     ComboPooledDataSource" destroy-method="close">

            <!-- 指定连接数据库的驱动 -->

            <property name="driverClass" value="com.mysql.jdbc.Driver"/>

            <!-- 指定连接数据库的URL -->

            <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

            <!-- 指定连接数据库的用户名 -->

            <property name="user" value="root"/>

            <!-- 指定连接数据库的密码 -->

            <property name="password" value="32147"/>

            <!-- 指定连接数据库连接池的最大连接数 -->

            <property name="maxPoolSize" value="40"/>

            <!-- 指定连接数据库连接池的最小连接数 -->

            <property name="minPoolSize" value="1"/>

            <!-- 指定连接数据库连接池的初始化连接数 -->

            <property name="initialPoolSize" value="1"/>

            <!-- 指定连接数据库连接池的连接最大空闲时间 -->

            <property name="maxIdleTime" value="20"/>

        </bean>

        <!-- 定义Hibernate的SessionFactory -->

        <bean id="sessionFactory" class="org.springframework.orm.hibernate3.     LocalSessionFactoryBean">

            <!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource -->

            <property name="dataSource" <ref="dataSource"/>

            <!-- mappingResources属性用来列出全部映射文件 -->

            <property name="mappingResources">

                  <list>

                      <!-- 以下用来列出所有的PO映射文件 -->

                    <value>lee/Person.hbm.xml</value>

                  </list>

            </property>

              <!-- 定义Hibernate的SessionFactory属性 -->

            <property name="hibernateProperties">

                 <props>

                    <!-- 指定Hibernate的连接方言 -->

                    <prop key="hibernate.dialect">org.hibernate.dialect.                 MySQLDialect</prop>

                    <!-- 是否根据Hiberante映射创建数据表时,选择create、update、                 create-drop -->

                      <prop key="hibernate.hbm2ddl.auto">update</prop>

                 </props>

            </property>

        </bean>

        <!-- 配置DAO Bean,该Bean将作为目标Bean使用 -->

        <bean id="personDAOTarget" class="lee.PersonDaoImpl">

            <!-- 采用依赖注入来传入SessionFactory的引用 -->

            <property name="sessionFactory" ref="sessionFactory"/>

        </bean>

        <!-- 配置Hibernate的事务管理器 -->

        <!-- 使用HibernateTransactionManager类,该类实现PlatformTransactionManager     接口,针对采用Hibernate持久化连接的特定实现 -->

        <bean id="transactionManager"

            class="org.springframework.orm.hibernate3.         HibernateTransactionManager">

            <!-- HibernateTransactionManager Bean,它需要依赖注入一个SessionFactory         Bean的引用 -->

            <property name="sessionFactory" ref="sessionFactory"/>

        </bean>

        <!--  配置personDAOTarget Bean的事务代理 -->

        <bean id="personDAO"

            class="org.springframework.transaction.interceptor.         TransactionProxyFactoryBean">

              <!-- 依赖注入PlatformTransactionManager的bean引用,此处使用           Hibernate的bean -->

              <!-- 局部事务器,因此transactionManager 传入Hibernate事务管理器的           引用 -->

              <property name="transactionManager" ref="transactionManager"/>

              <!-- 需要生成代理的目标bean -->

              <property name="target" ref="personDAOTarget"/>

              <!-- 指定事务属性 -->

              <property name="transactionAttributes">

                  <props>

                      <!-- 以下部分为定义事务回滚规则 -->

                        <prop key="insert*">PROPAGATION_REQUIRED,                   -MyCheckedException</prop>

                        <prop key="update*">PROPAGATION_REQUIRED</prop>

                        <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>

                </props>

            </property>

        </bean>

    </beans>

    在上面的定义文件中,没有对DAO对象采用Service层包装。通常情况下,DAO层上应有一层Service层。事务代理则以Service层 Bean为目标Bean。此处为了简化配置,TransactionProxyFactoryBean直接以DAO bean作为目标bean,这一点不会影响事务代理的生成。

    事务回滚规则部分定义了三个回滚规则:

    第一个回滚规则表示所有以insert开始的方法,都应该满足该事务规则。PROPAGATION_REQUIRED事务传播规则指明,该方法必须 处于事务环境中,如果当前执行线程已处于事务环境下,则直接执行;否则,启动新的事务然后执行该方法。该规则还指定,如果方法抛出 MyCheckedException的实例及其子类的实例,则强制回滚。MyCheckedException前的“-”表示强制回滚;“+”则表示强 制提交,即某些情况下,即使抛出异常也强制提交;

    第二个回滚规则表示所有以update开头的方法,都遵守PROPAGATION_REQUIRED的事务传播规则;

    第三个回滚规则表示除前面规定的方法外,其他所有方法都采用PROPAGATION_ REQUIRED事务传播规则,而且只读。

    常见的事务传播规则有如下几个:

      ● PROPAGATION_MANDATORY,要求调用该方法的线程必须处于事务环境中,否则抛出异常。

      ● PROPAGATION_NESTED,如果执行该方法的线程已处于事务环境下,依然启动新的事务,方法在嵌套的事务里执行。如果执行该方法的线程序并未处于事务中,也启动新的事务,然后执行该方法,此时与PROPAGATION_REQUIRED相同。

      ● PROPAGATION_NEVER,不允许调用该方法的线程处于事务环境下,如果调用该方法的线程处于事务环境下,则抛出异常。

      ● PROPAGATION_NOT_SUPPORTED,如果调用该方法的线程处在事务中,则先暂停当前事务,然后执行该方法。

      ● PROPAGATION_REQUIRED,要求在事务环境中执行该方法,如果当前执行线程已处于事务中,则直接调用;如果当前执行线程不处于事务中,则启动新的事务后执行该方法。

      ● PROPAGATION_REQUIRES_NEW,该方法要求有一个线程在新的事务环境中执行,如果当前执行线程已处于事务中,先暂停当前事务,启动新的事务后执行该方法;如果当前调用线程不处于事务中,则启动新的事务后执行该方法。

      ● PROPAGATION_SUPPORTS,如果当前执行线程处于事务中,则使用当前事务,否则不使用事务。

    程序里原来使用personDAO的地方,无须变化。因为,配置文件里将personDAO目标Bean的id改成 personDAOTarget,为TransactionProxyFactoryBean工厂Bean所产生的代理Bean命名为 personDAO。该代理Bean会包含原有personDAO的所有方法,而且为这些方法增加了不同的事务处理规则。

    程序面向PersonDaoImpl类所实现的接口编程,TransactionProxyFactoryBean生成的代理Bean也会实现 TransactionProxyFactoryBean接口。因此,原有的程序中调用DAO组件的代码无须任何改变。程序运行时,由事务代理完成原来目 标Bean完成的工作。

    事实上,Spring不仅支持对接口的代理,整合CGLIB后,Spring甚至可对具体类生成代理。只要设置proxyTargetClass属 性为true就可以。如果目标Bean没有实现任何接口,proxyTargetClass属性默认被设为true,此时Spring会对具体类生成代 理。当然,通常建议面向接口编程,而不要面向具体的实现类编程。

    6.3.4  使用继承简化事务配置 仔细观察配置文件中两个事务代理Bean的配置时,发现两个事务代理Bean的大部分配置完全相同,如果配置文件中包含大量这样的事务代理Bean配置,配置文件将非常臃肿。考虑到大部分事务代理Bean的配置都大同小异,可以使用Bean继承来简化事务代理的配置。

    正如前面部分介绍的,Bean继承是将所有子Bean中相同的配置定义成一个模板,并将此模板Bean定义成一个抽象Bean。考虑所有事务代理Bean中,有如下部分是大致相   同的:

      ● 事务代理Bean所使用的事务管理器。

      ● 事务传播规则。

    因此,现在配置文件中定义如下的事务代理模板Bean,其配置代码如下:

    <!-- 定义所有事务代理Bean的模板 -->

    <bean id="txProxyTemplate" abstract="true"

            class="org.springframework.transaction.interceptor.         TransactionProxyFactoryBean">

        <!-- 为事务代理Bean注入生成代理所需的PlatformTransactionManager实例 -->

        <property name="transactionManager" ref="transactionManager"/>

            <!-- 定义生成事务代理通用的事务属性 -->

            <property name="transactionAttributes">

                <props>

                    <!-- 所有的方法都应用PROPAGATION_REQUIRED的事务传播规则 -->

                    <prop key="*">PROPAGATION_REQUIRED</prop>

                </props>

        </property>

    </bean>

    而真正的事务代理Bean,则改为继承上面的事务模板Bean。考虑到将目标Bean定义在Spring容器中可能增加未知的风险,因此将目标Bean定义成嵌套Bean。

    <!-- 让事务代理Bean继承模板Bean -->

    <bean id="personDAO" parent="txProxyTemplate">

        <!-- 这里采用嵌套Bean的方式来定义目标Bean,当然也可以引用已存在的Bean -->

        <property name="target">

            <bean class="lee.personDAO"/>

        </property>

    </bean>

    此时的personDAO Bean无须具体地定义事务属性,它将在其父Bean txProxyTemplate中获取事务定义属性。此处采用嵌套Bean来定义目标Bean,因此,并未将目标Bean直接暴露在Spring的上下文 中让其他模块调用。当然,也可采用一个已经存在的Bean作为目标Bean;子Bean的事务属性定义,完全可覆盖事务代理模板里的事务属性定义。如下例 所示:

    <!-- 让事务代理bean继承模板Bean -->

    <bean id="personDAO" parent="txProxyTemplate">

        <!-- 这里,采用引用已存在的bean的方式来定义目标Bean -->

        <property name="target" ref ="personDAOTarget"/>

        <!-- 覆盖事务代理模板bean中的事务属性定义 -->

        <property name="transactionAttributes">

            <props>

                <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>

                <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

                <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>

             </props>

        </property>

    </bean>

    可见,采用Bean继承方式定义事务代理的方式,可以很好地简化事务代理的配置,可以避免配置事务代理Bean时的冗余配置。

    提示:使用Bean继承可以很好地简化事务代理Bean的配置,通过将各事务代理Bean共同的配置信息提取成事务模板Bean,可以让实际的事务 代理Bean的配置更加简洁;而且,配置方式相当直观。尽量将目标Bean配置成嵌套Bean,这样的方式可以保证更好的内聚性。

    如果读者还记得前面介绍的AOP知识,应该知道还有一种更加简洁的配置,就是利用Bean后处理器,让Bean后处理器为容器中其他Bean自动创建事务代理。

    6.3.5  使用自动创建代理简化事务配置 回顾6.2.6节和6.2.7节,读者可能已经想到如何自动创建代理。是的,正是通过6.2.6节和6.2.7节所给出的两个自动代理创建类来生成事务代理。

    正如前文已经提到的,使用BeanNameAutoProxyCreator和DefaultAdvisorAutoProxy- Creator来创建代理时,并不一定是创建事务代理,关键在于传入的拦截器,如果传入事务拦截器,将可自动生成事务代理。

    下面是使用BeanNameAutoProxyCreator自动生成事务代理的配置文件:

    <?xml version="1.0" encoding="GBK"?>

    <!-- 指定Spring配置文件的根元素,以及相应的Schema信息 -->

    <beans xmlns="http://www.springframework.org/schema/beans "

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance "

           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd ">

        <!-- 定义数据源Bean,使用C3P0数据源实现 -->

        <bean id="dataSource" class="com.mchange.v2.c3p0.     ComboPooledDataSource" destroy-method="close">

            <!-- 指定连接数据库的驱动 -->

            <property name="driverClass" value="com.mysql.jdbc.Driver"/>

            <!-- 指定连接数据库的URL -->

            <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

            <!-- 指定连接数据库的用户名 -->

            <property name="user" value="root"/>

            <!-- 指定连接数据库的密码 -->

            <property name="password" value="32147"/>

            <!-- 指定连接数据库连接池的最大连接数 -->

            <property name="maxPoolSize" value="40"/>

            <!-- 指定连接数据库连接池的最小连接数 -->

            <property name="minPoolSize" value="1"/>

            <!-- 指定连接数据库连接池的初始化连接数 -->

            <property name="initialPoolSize" value="1"/>

            <!-- 指定连接数据库连接池的连接最大空闲时间 -->

            <property name="maxIdleTime" value="20"/>

        </bean>

        <!-- 使用JDBC的局部事务策略 -->

        <bean id="transactionManager"

            class="org.springframework.jdbc.datasource.DataSource-         TransactionManager">

            <!-- 为事务管理器注入所需的数据源Bean -->

            <property name="dataSource" ref="dataSource"/>

        </bean>

        <!-- 配置目标Bean,该目标Bean将由Bean后处理器自动生成代理 -->

        <bean id="test1" class="lee.TransactionTestImpl">

            <!-- 依赖注入目标Bean所必需的数据源Bean -->

            <property name="ds" ref="dataSource"/>

        </bean>

        <!-- 配置目标Bean,该目标Bean将由Bean后处理器自动生成代理 -->

        <bean id="test2" class="lee.TestImpl">

            <!-- 依赖注入目标Bean所必需的数据源Bean -->

            <property name="ds" ref="dataSource"/>

        </bean>

        <!-- 配置事务拦截器Bean -->

        <bean id="transactionInterceptor"

            class="org.springframework.transaction.interceptor.         TransactionInterceptor">

            <!-- 事务拦截器bean需要依赖注入一个事务管理器 -->

            <property name="transactionManager" ref="transactionManager"/>

            <property name="transactionAttributes">

                <!-- 下面定义事务传播属性 -->

                <props>

                    <prop key="insert*">PROPAGATION_REQUIRED</prop>

                    <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

                    <prop key="*">PROPAGATION_REQUIRED</prop>

                </props>

            </property>

        </bean>

        <!-- 定义BeanNameAutoProxyCreator的Bean后处理器 -->

        <bean class="org.springframework.aop.framework.autoproxy.     BeanNameAutoProxyCreator">

        <!-- 指定对满足哪些bean name的bean自动生成业务代理 -->

            <property name="beanNames">

                <!-- 下面是所有需要自动创建事务代理的Bean -->

                <list>

                    <value>test1</value>

                    <value>test2</value>

                </list>

                <!-- 此处可增加其他需要自动创建事务代理的Bean -->

            </property>

            <!-- 下面定义BeanNameAutoProxyCreator所需的拦截器 -->

            <property name="interceptorNames">

                <list>

                    <value>transactionInterceptor</value>

                    <!-- 此处可增加其他新的Interceptor -->

                </list>

            </property>

        </bean>

    </beans>

    如果配置文件中仅有两个目标Bean,可能不能很清楚地看出这种自动创建代理配置方式的优势,但如果有更多目标Bean需要自动创建事务代理,则可 以很好地体会到这种配置方式的优势:配置文件只需要简单地配置目标Bean,然后在BeanNameAutoProxyCreator配置中增加一行即 可。

    提示:使用BeanNameAutoProxyCreator可以自动创建事务代理,使用DefaultAdvisor- AutoProxyCreator也可自动创建事务代理。关于后一个Bean后处理器的配置方式

    再次表示感谢

    转自:http://blog.csdn.net/gabriel80/archive/2008/05/23/2473880.aspx


    最新回复(0)