zoukankan      html  css  js  c++  java
  • Spring的事务机制

    JAVA EE传统事务机制

    通常有两种事务策略:全局事务和局部事务。全局事务可以跨多个事务性资源(即数据源,典型的是数据库和消息队列),通常都需要J2EE应用服务器的管理,其底层需要服务器的JTA支持。而局部事务则与底层采用的持久化技术有关,如果底层直接使用JDBC,需要用Connection对象来操事务。如果采用Hibernate持久化技术,则需要使用session对象来操作事务。

    通常的,使用JTA事务,JDBC事务及Hibernate事务的编程流程大致如下,

    上图也可以看出,采用传统事务编程,程序代码必须和具体的事务策略的API耦合,如果应用需要切换一种策略,意味着需要大幅修改代码。但是如果使用Spring事务的话,就不会有这个问题了。

    Spring事务机制

    Sring没有提供任何事务支持,它只是负责包装底层的事务,而在Spring层面,对外提供统一的编程API。Spring事务的核心是PlatformTransactionManager接口,

    PlatformTransactionManager代表与具体类型无关的事务接口,可以代表任何事务,包括JDBC事务,Hibernate事务,甚至是JTA事务。

    Springa事务机制是一种典型的策略模式,PlatformTransactionManager代表事务管理接口,但它并不知道到底如何管理事务,它只要求事务管理提供开始事务getTransaction(),提交事务commit()和回滚事务rollback()这三个方法,但具体如何实现则交给其实现类完成。编程人员只需要在配置文件中根据具体需要使用的事务类型做配置,Spring底层就自动会使用具体的事务实现类进行事务操作,而对于程序员来说,完全不需要关心底层过程,只需要面向PlatformTransactionManager接口进行编程即可。PlatformTransactionManager接口中提供了如下方法:getTransaction(..), commit(); rollback(); 这些都是与平台无关的事务操作。

    getTransaction()的完整写法为 TransactionStatus getTransaction(TransactionDefinition definiton)

    这个方法用来返回一个事务对象,其中的参数TransactionDefinition 则可以为事务对象指定各种属性,通常可以指定 事务的隔离属性, 传播属性, 超时,只读 这几个属性。

    Spring具体的事务管理需要在配置文件中配置好PlatformTransactionManager,下面是不同类型的事务对应的Spring配置。

    • JDBC数据源的局部事务管理器的配置如下,
     1     <!-- 定义数据源Bean,使用C3P0数据源实现,并注入数据源的必要信息 -->
     2     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSrouce"
     3         destroy-method="close"
     4         p:driverClass="com.mysql.jdbc.Driver"
     5         p:jdbcUrl="jdbc:mysql://localhost/test"
     6         p:user="root"
     7         p:password=""
     8         p:maxPoolSize="40"
     9         p:minPoolSize="2"
    10         p:initialPoolSize="2"
    11         p:maxIdleTime="30" />
    12     <!-- 配置JDBC数据源的局部数据管理器,使用DataSourceTransactionManager类 -->
    13     <bean id="transactionManager"
    14         class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
    15         p:dataSource-ref="dataSource" />
    • 容器管理的JTA全局事务管理器的配置如下,
    1     <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean" 
    2         p:jndiName="jdbc/jpetstore" />
    3     <!-- 使用JtaTransactionManager类, 该类实现了PlatformTransactionManager接口 -->
    4     <!-- 使用JTA全局事务,Spring容器可以自行从Java EE服务器中获取事务性资源,无需依赖注入 -->
    5     <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" />

    对于JTA全局事务,只需要指定事务管理器的实现类JtaTransactionManager即可,Spring容器会自行从J2EE服务器获取数据源,无需显式注入进事务管理器。

    • 基于Hibernate持久化技术的Spring局部事务配置如下,
     1     <!-- 定义数据源Bean,使用C3P0数据源实现,并注入数据源的必要信息 -->
     2     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSrouce"
     3         destroy-method="close"
     4         p:driverClass="com.mysql.jdbc.Driver"
     5         p:jdbcUrl="jdbc:mysql://localhost/test"
     6         p:user="root"
     7         p:password=""
     8         p:maxPoolSize="40"
     9         p:minPoolSize="2"
    10         p:initialPoolSize="2"
    11         p:maxIdleTime="30" />
    12     <!-- 定义Hibernate的SessionFactory, SessionFactory需要依赖数据源,注入dataSource -->
    13     <bean id="sessionFactory"
    14         class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
    15         p:dataSource-ref="dataSource">
    16         <!-- annotatedClasses用来列出全部持久化类 -->    
    17         <property name="annotatedClasses">
    18             <list>
    19                 <!-- 以下用来列出所有PO类 -->
    20                 <value>com.entity.User</value>
    21             </list>
    22         </property>
    23         <!-- 定义Hibernate的sessionFactory属性 -->
    24         <property name="hibernateProperties">
    25             <props>
    26                 <!-- 指定Hibernate的连接方言 -->
    27                 <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
    28                 <!-- 是否根据Hibernate映射表创建数据表 -->
    29                 <prop key="hibernate.hbm2ddl.auto">update</prop>
    30             </props>
    31         </property>
    32     </bean>
    33     <!-- 配置Hibernate的局部数据管理器,使用HibernateTransactionManager类 -->
    34     <!-- 该类是PlatformTransactionManager接口针对Hibernate的特定实现 -->
    35     <!-- 配置HibernateTransactionManager需要注入sessionFactory -->
    36     <bean id="transactionManager"
    37         class="org.springframework.orm.hibernate4.HibernateTransactionManager"
    38         p:sessionFactory-ref="sessionFactory" />

    Spring事务如果采用Hibernate策略,一般需要配置三点:数据源, sessionFactory, 事务管理器。

    • 如果底层采用Hibernate持久层技术,而事务采用JTA全局事务时,配置如下,
     1     <!-- 配置JTA数据源-->
     2     <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean" 
     3         p:jndiName="jdbc/jpetstore" />
     4     <!-- 定义Hibernate的SessionFactory, SessionFactory需要依赖数据源,注入dataSource -->
     5     <bean id="sessionFactory"
     6         class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
     7         p:dataSource-ref="dataSource">
     8         <!-- annotatedClasses用来列出全部持久化类 -->    
     9         <property name="annotatedClasses">
    10             <list>
    11                 <!-- 以下用来列出所有PO类 -->
    12                 <value>com.entity.User</value>
    13             </list>
    14         </property>
    15         <!-- 定义Hibernate的sessionFactory属性 -->
    16         <property name="hibernateProperties">
    17             <props>
    18                 <!-- 指定Hibernate的连接方言 -->
    19                 <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
    20                 <!-- 是否根据Hibernate映射表创建数据表 -->
    21                 <prop key="hibernate.hbm2ddl.auto">update</prop>
    22             </props>
    23         </property>
    24     </bean>
    25     <!-- 使用JtaTransactionManager类,该类是PlatformTransactionManager接口的实现类 -->
    26     <!-- 针对全局事务管理的特定实现 -->
    27     <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" />

    这与前面的基于Hibernate的Spring事务比起来,就是将数据源换成了JNDI数据源,  将事务管理器换成了JtaTransactionManager.

    对于JTA全局事务,因为需要底层应用服务器的支持,而不同应用服务器所提供的JTA全局事务可能存在细节上的差异,因此实际配置全局事务管理器时可能需要使用JtaTransactionManager的子类,例如Oracle的JavaEE应用服务器提供的OC4JJtaTransactionManager,Oracle为WebLogic提供的WebLogicJtaTransactionManager, IBM为WebSphere提供的WebSphereUowTransactionManager等。

    从上面各种事务类型的Spring配置可以看出,当应用程序采用Spring事务管理时,应用程序无需与具体的事务API耦合,应用程序只需要面向PlatormTransactionManager接口编程即可,ApplicationContext会根据配置文件选择合适的事务策略实现类(即PlatormTransactionManager的实现类)。

    那么在具体在Spring中如何进行事务控制编程呢,通常有两种方式,

    编程式事务管理:就是直接在代码中使用PlatormTransactionManager提供的三个抽象方法进行事务流程控制。也可以在Spring容器中获取PlatormTransactionManager类型的Bean,该Bean总是PlatormTransactionManager的具体实现类的实例,具体的实现类则由ApplicationContext按照策略模式进行选择,编程人员无需关心,只需要面向接口编程即可。

    声明式事务管理:这种方式不需要讲事务控制流程写入代码中,而是通过AOP的方式,完全由配置文件完成事务的织入。即XML配置文件可以为业务组件配置事务代理,事务代理为业务组件提供事务控制。现在这种方式是最好的,源码侵入性最低。

    使用声明式事务管理-使用XML Schema配置事务策略

    当使用声明式事务时,只需要写好配置文件,配置需要事务控制的组件种类,业务组件就会在AOP机制下被织入事务控制,而编程人员不需要写任何事务管理代码,可以专注于业务组件的开发。因此通常都推荐使用声明式事务管理。

    Spring的XML Schema方式提供了简洁的事务配置策略,通过命名空间 <tx:advice> 来配置一个事务增强处理,其中可以指定事务的各种属性(例如隔离属性, 传播属性, 超时,只读属性等等),然后通过<aop:config>标签可以将事务的增强与AOP的切入点(即Bean的执行方法)进行绑定,从而实现对Bean的方法织入事务操作。下面是一个简单的例子,配置一个NewsDaoImpl bean进行数据操作,使用c3p0数据源,Spring的JDBC事务管理器,在<tx:advice对事务设置属性。

    完整的Spring配置如下,

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3     xmlns="http://www.springframework.org/schema/beans"
     4     xmlns:p="http://www.springframework.org/schema/p"
     5     xmlns:aop="http://www.springframework.org/schema/aop"
     6     xmlns:tx="http://www.springframework.org/schema/tx"
     7     xsi:schemaLocation="http://www.springframework.org/schema/beans
     8     http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
     9     http://www.springframework.org/schema/aop
    10     http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
    11     http://www.springframework.org/schema/tx
    12     http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
    13     <!-- 定义数据源Bean,使用C3P0数据源实现,并注入数据源的必要信息 -->
    14     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    15         destroy-method="close"
    16         p:driverClass="com.mysql.jdbc.Driver"
    17         p:jdbcUrl="jdbc:mysql://localhost/test?useUnicode=true&amp;characterEncoding=UTF-8"
    18         p:user="root"
    19         p:password=""
    20         p:maxPoolSize="40"
    21         p:minPoolSize="2"
    22         p:initialPoolSize="2"
    23         p:maxIdleTime="30" />
    24     <!-- 配置JDBC数据源的局部数据管理器,使用DataSourceTransactionManager类 -->
    25     <bean id="transactionManager"
    26         class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
    27         p:dataSource-ref="dataSource" />
    28     
    29     <!-- 配置一个业务逻辑Bean -->
    30     <bean id="newsDao" class="com.dao.impl.NewsDaoImpl" p:ds-ref="dataSource" />
    31     <!-- 配置事务增强处理, 指定事务管理器 -->
    32     <tx:advice id="txAdvice"
    33         transaction-manager="transactionManager">
    34         <!-- 用于配置详细的事务定义 -->
    35         <tx:attributes>
    36             <!-- 所有以get开头的方法都是只读的 -->
    37             <tx:method name="get*" read-only="true" />
    38             <!-- 其他方法默认都适用事务,指定超时5秒 -->
    39             <tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" timeout="5" />
    40         </tx:attributes>
    41     </tx:advice>
    42     <aop:config>
    43         <!-- 配置一个切入点,匹配impl包下所有以impl结尾的类里的所有方法的执行 -->
    44         <aop:pointcut expression="execution(* com.dao.impl.*Impl.*(..))" id="myPointcut" />
    45         <!-- 将切入点myPointcut和增强txAdvice绑定-->
    46         <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut" />
    47         <!-- 再配置一个切入点,匹配impl包下所有以abc开头类里的所有方法的执行  -->
    48     </aop:config>
    49 </beans>

    NewsDaoImpl代码中,则是插入重复数据到表中,

     1 package com.dao.impl;
     2 
     3 import javax.sql.DataSource;
     4 import org.springframework.jdbc.core.JdbcTemplate;
     5 import com.dao.NewsDao;
     6 
     7 public class NewsDaoImpl implements NewsDao {
     8     private DataSource ds;
     9     public void setDs(DataSource ds) {
    10         this.ds = ds;
    11     }
    12     @Override
    13     public void insert(String title, String content) {
    14         //c3p0数据池的用法
    15         JdbcTemplate jt = new JdbcTemplate(ds);
    16         jt.update("insert into news_inf" + " values(100,?,?)", title, content);
    17         jt.update("insert into news_inf" + " values(100,?,?)", title, content);
    18         //如果没有事务控制,则第一条记录可以被插入
    19         //如果增加事务控制,将发现第一条记录也插不进去
    20     }
    21 }

    下面是测试方法,

    1     public static void test3() {
    2         ApplicationContext ctx = new ClassPathXmlApplicationContext("beans4JDBC.xml");
    3         //获取事务代理Bean
    4         NewsDao dao = (NewsDao)ctx.getBean("newsDao", NewsDao.class);
    5         dao.insert("java编程核心思想", "轻量级Java EE开发");
    6         System.out.println("执行完毕");
    7     }

    执行测试方法会发现抛出异常(因为有重复数据),而又因为事务控制,数据库中讲不会有数据插入。

    可以看到上面例子中,通常对于XML Schema的配置中,其实就是对一个普通的Bean做了AOP配置,织入一个advice增强,而advice增强中则配置一个事务管理器,事务管理器又依赖数据源。

    对于<aop:advisor>中,将advice和切入点的绑定,而在Spring底层是由Bean后处理器完成(例如BeanNameAutoProxyCreator, DefaultAdvisorAutoProxyCreator),其本质就是动态代理。

    另外,在<tx:advice>配置增强中,还可以为事务指定再遇到特定异常时,进行强制rollback和强制不rollback,即rollback-for="xxxException", no-rollback-for="xxxException"

    使用@Transactionl

    除了使用XML Schema的方法之外,也可以直接在方法上添加@Transaction注解,使这个方法具有事务属性。 在@Transaction中可以为事务配置各种属性(例如隔离属性, 传播属性, 超时,只读属性等等),此外,还需要在在XML配置中加入<tx:annotation-triven配置表明Spring会根据注解来配置事务代理,这样,事务的属性配置和AOP切入配置就可以只通过一步(直接通过注解配置在方法名上)完成了。

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

    NewsDaoImpl.

    1     @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.DEFAULT, timeout=5)
    2     @Override
    3     public void insert(String title, String content) {

    Spring事务机制涉相关链接

    http://www.mamicode.com/info-detail-1248286.html 

    http://kingj.iteye.com/blog/1680350

  • 相关阅读:
    【初心】
    【杂题集】单题小总结
    【模板】(旧)矩阵模板
    【机智题?】【Vijos】【天平称量】
    【杂题集】【51NOD 1267】4个数和为0
    【模板】(旧)Miller Rabin 素数判定
    【杂谈】只是想想
    【杂谈】思考
    【学习?】组合和排序
    【模版】读入优化
  • 原文地址:https://www.cnblogs.com/fysola/p/6384399.html
Copyright © 2011-2022 走看看