Spring学习笔记整理——事务

学习链接地址:http://www.iteye.com/blogs/subjects/spring3

第一部分 数据库事务概述

事务必需满足ACID(原子性、一致性、隔离性和持久性)特性,缺一不可:

  • 原子性(Atomicity):即事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做;
  • 一致性(Consistency):在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是处于正确的状态,即数据完整性约束没有被破坏;如银行转帐,A转帐给B,必须保证A的钱一定转给B,一定不会出现A的钱转了但B没收到,否则数据库的数据就处于不一致(不正确)的状态。
  • 隔离性(Isolation):并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性;
  • 持久性(Durability):事务一旦执行成功,它对数据库的数据的改变必须是永久的,不会因比如遇到系统故障或断电造成数据不一致或丢失。

在实际项目开发中数据库操作一般都是并发执行的,即有多个事务并发执行,并发执行就可能遇到问题,目前常见的问题如下:

  • 丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的;
  • 脏读:一个事务看到了另一个事务未提交的更新数据;
  • 不可重复读:在同一事务中,多次读取同一数据却返回不同的结果;也就是有其他事务更改了这些数据;
  • 幻读:一个事务在执行过程中读取到了另一个事务已提交的插入数据;即在第一个事务开始时读取到一批数据,但此后另一个事务又插入了新数据并提交,此时第一个事务又读取这批数据但发现多了一条,即好像发生幻觉一样。

为了解决这些并发问题,需要通过数据库隔离级别来解决,在标准SQL规范中定义了四种隔离级别:

  • 未提交读(Read Uncommitted):最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读;
  • 提交读(Read Committed):一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不可能可能出现丢失更新、脏读,但可能出现不可重复读、幻读;
  • 可重复读(Repeatable Read):保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,可能可能出现丢失更新、脏读、不可重复读,但可能出现幻读;
  • 序列化(Serializable):最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读。

隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。因此在实际项目开发中为了考虑并发性能一般使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但可以在可能出现的场合使用悲观锁或乐观锁来解决这些问题。

事务类型

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

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

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

  • JDBC事务:就是数据库事务类型中的本地事务,通过Connection对象的控制来管理事务;
  • JTA事务:JTA指Java事务API(Java Transaction API),是Java EE数据库事务规范,JTA只提供了事务管理接口,由应用程序服务器厂商(如WebSphere Application Server)提供实现,JTA事务比JDBC更强大,支持分布式事务。

Java EE事务类型有本地事务和全局事务:

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

按是否通过编程实现事务有声明式事务和编程式事务;

  • 声明式事务: 通过注解或XML配置文件指定事务信息;
  • 编程式事务:通过编写代码实现事务。

Spring提供的事务管理

Spring框架最核心功能之一就是事务管理,而且提供一致的事务管理抽象,这能帮助我们:

  • 提供一致的编程式事务管理API,不管使用Spring JDBC框架还是集成第三方框架使用该API进行事务编程;
  • 无侵入式的声明式事务支持。

Spring支持声明式事务和编程式事务事务类型。


第二部分 事务管理器

概述

Spring框架支持事务管理的核心是事务管理器抽象,对于不同的数据访问框架(如Hibernate)通过实现策略接口PlatformTransactionManager,从而能支持各种数据访问框架的事务管理,PlatformTransactionManager接口定义如下:

public interface PlatformTransactionManager {
    /**
    * 返回一个已经激活的事务或创建一个新的事务(根据给定的TransactionDefinition类型参数定义的事务属性),
    * 返回的是TransactionStatus对象代表了当前事务的状态,
    * 其中该方法抛出TransactionException(未检查异常)表示事务由于某种原因失败。
    */
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    /**
    * 提交TransactionStatus参数代表的事务
    */
    void commit(TransactionStatus status) throws TransactionException;
    /**
    * 回滚TransactionStatus参数代表的事务
    */
    void rollback(TransactionStatus status) throws TransactionException;
}
public interface TransactionDefinition {
    int getPropagationBehavior();//返回定义的事务传播行为;
    int getIsolationLevel();//返回定义的事务隔离级别;
    int getTimeout();//返回定义的事务超时时间;
    boolean isReadOnly();//返回定义的事务是否是只读的;
    String getName();//返回定义的事务名字。
}
public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();//返回当前事务状态是否是新事务;
    boolean hasSavepoint();//返回当前事务是否有保存点;
    void setRollbackOnly();//设置当前事务应该回滚;
    boolean isRollbackOnly();//回当前事务是否应该回滚;
    void flush();//用于刷新底层会话中的修改到数据库,一般用于刷新如Hibernate/JPA的会话,可能对如JDBC类型的事务无任何影响;
    boolean isCompleted();//当前事务否已经完成
}

内置事务管理器实现

这里写图片描述
Spring不仅提供这些事务管理器,还提供对如JMS事务管理的管理器等。

声明对本地事务的支持
  • JDBC及iBATIS、MyBatis框架事务管理器
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
  • Hibernate事务管理器
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>
Spring对全局事务的支持
  • Jta事务管理器
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="
       http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/jee
       http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">

    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/test"/>
    <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManagerName"  value="java:comp/TransactionManager"/>
    </bean>
</beans>

两个不依赖于应用服务器的开源JTA事务实现:JOTM和Atomikos Transactions Essentials。

  • JOTM:即基于Java开放事务管理器(Java Open Transaction Manager),实现JTA规范,能够运行在非应用服务器环境中,Web容器或独立Java SE环境,官网地址:http://jotm.objectweb.org/
  • Atomikos Transactions Essentials:其为Atomikos开发的事务管理器,该产品属于开源产品,另外还一个商业的Extreme Transactions。官网地址为:http://www.atomikos.com

使用Atomikos Transactions Essentials来进行演示JTA事务使用

<bean id="dataSource1" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> 
    <property name="uniqueResourceName" value="jdbc/test1"/> 
    <property name="xaDataSourceClassName" value="org.apache.derby.jdbc.EmbeddedXADataSource"/> 
    <property name="poolSize" value="5"/> 
    <property name="xaProperties"> 
        <props> 
            <prop key="databaseName">test1</prop> 
            <prop key="createDatabase">create</prop> 
        </props> 
    </property> 
</bean> 

<bean id="dataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> 
    <property name="uniqueResourceName" value="jdbc/test2"/> 
    <property name="xaDataSourceClassName" value="org.apache.derby.jdbc.EmbeddedXADataSource"/> 
    <property name="poolSize" value="5"/> 
    <property name="xaProperties"> 
        <props> 
            <prop key="databaseName">test2</prop> 
            <prop key="createDatabase">create</prop> 
        </props> 
    </property>
</bean> 
<!-- 定义了AtomikosTransactionsEssentials事务管理器 -->
<bean id="atomikosTransactionManager" class = "com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method = "close">   
      <property name="forceShutdown" value="true"/>   
</bean>
<!-- 定义UserTransaction,该Bean是线程安全的 -->
<bean id="atomikosUserTransaction"  class="com.atomikos.icatch.jta.UserTransactionImp">   
</bean>   
<!-- 定义Spring事务管理器,transactionManager属性指定外部事务管理器(真正的事务管理者),使用userTransaction指定UserTransaction,该属性一般用于本地JTA实现,如果使用应用服务器事务管理器,该属性将自动从JNDI获取。 -->
<bean id="transactionManager"  class="org.springframework.transaction.jta.JtaTransactionManager">   
    <property name="transactionManager">   
        <ref bean="atomikosTransactionManager"/>   
    </property>   
    <property name="userTransaction">   
        <ref bean="atomikosUserTransaction"/>
    </property>   
</bean>

第三部分 编程式事务

编程式事务概述

所谓编程式事务指的是通过编码方式实现事务,即类似于JDBC编程实现事务管理。
Spring框架提供一致的事务抽象,因此对于JDBC还是JTA事务都是采用相同的API进行编程。

Connection conn = null;
UserTransaction tx = null;
try {
    tx = getUserTransaction();//1.获取事务
    tx.begin();//2.开启JTA事务
    conn = getDataSource().getConnection();//3.获取JDBC
    //4.声明SQL
    String sql = "select * from INFORMATION_SCHEMA.SYSTEM_TABLES";
    PreparedStatement pstmt = conn.prepareStatement(sql);//5.预编译SQL
    ResultSet rs = pstmt.executeQuery(); //6.执行SQL
    process(rs); //7.处理结果集
    closeResultSet(rs); //8.释放结果集
    tx.commit(); //7.提交事务
} catch (Exception e) {
    tx.rollback();//8.回滚事务
    throw e;
} finally {
    conn.close();//关闭连接
}

Spring对编程式事务的支持

Spring中的事务分为物理事务和逻辑事务;

  • 物理事务:就是底层数据库提供的事务支持,如JDBC或JTA提供的事务;
  • 逻辑事务:是Spring管理的事务,不同于物理事务,逻辑事务提供更丰富的控制,而且如果想得到Spring事务管理的好处,必须使用逻辑事务,因此在Spring中如果没特别强调一般就是逻辑事务;

逻辑事务即支持非常低级别的控制,也有高级别解决方案:

【低级别解决方案】

  • 工具类:使用工具类获取连接(会话)和释放连接(会话),如使用org.springframework.jdbc.datasource包中的ConnectionUtils类来获取和释放具有逻辑事务功能的连接。当然对集成第三方ORM框架也提供了类似的工具类,如对Hibernate提供了SessionFactoryUtils工具类,JPA的EntityManagerFactoryUtils等,其他工具类都是使用类似***Utils命名;
//获取具有Spring事务(逻辑事务)管理功能的连接
DataSourceUtils. getConnection(DataSource dataSource)
//释放具有Spring事务(逻辑事务)管理功能的连接
DataSourceUtils. releaseConnection(Connection con, DataSource dataSource)
  • TransactionAwareDataSourceProxy:使用该数据源代理类包装需要Spring事务管理支持的数据源,该包装类必须位于最外层,主要用于遗留项目中可能直接使用数据源获取连接和释放连接支持或希望在Spring中进行混合使用各种持久化框架时使用,其内部实际使用ConnectionUtils工具类获取和释放真正连接;
<!--使用该方式包装数据源,必须在最外层,targetDataSource 知道目标数据源-->
<bean id="dataSourceProxy" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
    <property name="targetDataSource" ref="dataSource"/>
</bean>

【高级别解决方案】

  • 模板类:使用Spring提供的模板类,如JdbcTemplate、HibernateTemplate和JpaTemplate模板类等,而这些模板类内部其实是使用了低级别解决方案中的工具类来管理连接或会话

使用PlatformTransactionManager

<?xml version="1.0" encoding="UTF-8"?>
<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-3.0.xsd">

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:chapter7/resources.properties</value>
            </list>
        </property>
    </bean>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
        <property name="targetDataSource">
            <bean class="org.logicalcobwebs.proxool.ProxoolDataSource">
                <property name="driver" value="${db.driver.class}" />
                <property name="driverUrl" value="${db.url}" />
                <property name="user" value="${db.username}" />
                <property name="password" value="${db.password}" />
                <property name="maximumConnectionCount" value="${proxool.maxConnCount}" />
                <property name="minimumConnectionCount" value="${proxool.minConnCount}" />
                <property name="statistics" value="${proxool.statistics}" />
                <property name="simultaneousBuildThrottle" value="${proxool.simultaneousBuildThrottle}" />
                <property name="trace" value="${proxool.trace}" />
            </bean>
        </property>
    </bean>
</beans>
/**
* 事务管理器定义
*/

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

public class TransactionTest {
    //id自增主键从0开始
    private static final String CREATE_TABLE_SQL = "create table test (id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name varchar(100))";
    private static final String DROP_TABLE_SQL = "drop table test";
    private static final String INSERT_SQL = "insert into test(name) values(?)";
    private static final String COUNT_SQL = "select count(*) from test";
……
}
/**
* 初始化Spring容器
*/

public class TransactionTest {
    private static ApplicationContext ctx;
    private static PlatformTransactionManager txManager;
    private static DataSource dataSource;
    private static JdbcTemplate jdbcTemplate;
……
    @BeforeClass
    public static void setUpClass() {
        String[] configLocations = new String[] {
                "classpath:chapter7/applicationContext-resources.xml",
                "classpath:chapter9/applicationContext-jdbc.xml"};
        ctx = new ClassPathXmlApplicationContext(configLocations);
        txManager = ctx.getBean(PlatformTransactionManager.class);
        dataSource = ctx.getBean(DataSource.class);
        jdbcTemplate = new JdbcTemplate(dataSource);
    }
……
}
/**
* 使用高级别方案JdbcTemplate来进行事务管理器测试
*/

@Test
public void testPlatformTransactionManager() {
    /**
    * 事务定义,定义如隔离级别、传播行为等,即在本示例中隔离级别为ISOLATION_READ_COMMITTED(提交读),
    * 传播行为为PROPAGATION_REQUIRED(必须有事务支持,即如果当前没有事务,就新建一个事务,如果已经存在一个事务中,就加入到这个事务中)。
    */
    DefaultTransactionDefinition def = new DefaultTransactionDefinition(); 
    def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
    /**
    * 事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;
    * 获取事务状态后,Spring根据传播行为来决定如何开启事务;
    */
    TransactionStatus status = txManager.getTransaction(def);
    /**
    * 对象执行相应的SQL操作,且自动享受到事务支持,
    * 注意事务是线程绑定的,因此事务管理器可以运行在多线程环境;
    */
    jdbcTemplate.execute(CREATE_TABLE_SQL);
    try {
        jdbcTemplate.update(INSERT_SQL, "test");
        txManager.commit(status);
    } catch (RuntimeException e) {
        txManager.rollback(status);
    }
    jdbcTemplate.execute(DROP_TABLE_SQL);
}
/**
* 使用低级别解决方案来进行事务管理器测试
*/

@Test
public void testPlatformTransactionManagerForLowLevel1() {
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    TransactionStatus status = txManager.getTransaction(def);
    Connection conn = DataSourceUtils.getConnection(dataSource);
    try {
        conn.prepareStatement(CREATE_TABLE_SQL).execute();
        PreparedStatement pstmt = conn.prepareStatement(INSERT_SQL);
        pstmt.setString(1, "test");
        pstmt.execute();
        conn.prepareStatement(DROP_TABLE_SQL).execute();
        txManager.commit(status);
    } catch (Exception e) {
        status.setRollbackOnly();
        txManager.rollback(status);
    } finally {
        DataSourceUtils.releaseConnection(conn, dataSource);
    }
}

使用TransactionTemplate

TransactionTemplate模板类用于简化事务管理,事务管理由模板类定义,而具体操作需要通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理。
TransactionTemplate模板类使用的回调接口:

  • TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus status) ”方法来定义需要事务管理的操作代码;
  • TransactionCallbackWithoutResult:继承TransactionCallback接口,提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事务操作代码。
@Test
public void testTransactionTemplate() {//位于TransactionTest类中
    jdbcTemplate.execute(CREATE_TABLE_SQL);
    /**
    * 通过new TransactionTemplate(txManager)创建事务模板类,
    * 其中构造器参数为PlatformTransactionManager实现,并通过其相应方法设置事务定义,
    * 如事务隔离级别、传播行为等,此处未指定传播行为,其默认为PROPAGATION_REQUIRED;
    */
    TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
    /**
    * 此处使用不带返回的回调实现,其doInTransactionWithoutResult方法实现中定义了需要事务管理的操作
    */
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            jdbcTemplate.update(INSERT_SQL, "test");
    }});
    /**
    * 通过该方法执行需要事务管理的回调
    */
    jdbcTemplate.execute(DROP_TABLE_SQL);
}

对于抛出Exception类型的异常且需要回滚时,需要捕获异常并通过调用status对象的setRollbackOnly()方法告知事务管理器当前事务需要回滚,如下所示:

try {
    //业务操作
} catch (Exception e) { //可使用具体业务异常代替
    status.setRollbackOnly();
}
/**
* JTA分布式事务管理
*/

@Test
public void testJtaTransactionTemplate() {
    String[] configLocations = new String[] {"classpath:chapter9/applicationContext-jta-derby.xml"};
    ctx = new ClassPathXmlApplicationContext(configLocations);
    final PlatformTransactionManager jtaTXManager = ctx.getBean(PlatformTransactionManager.class);
    final DataSource derbyDataSource1 = ctx.getBean("dataSource1", DataSource.class);
    final DataSource derbyDataSource2 = ctx.getBean("dataSource2", DataSource.class);
    final JdbcTemplate jdbcTemplate1 = new JdbcTemplate(derbyDataSource1);
    final JdbcTemplate jdbcTemplate2 = new JdbcTemplate(derbyDataSource2);
    /**
    * 使用jtaTXManager事务管理器的事务管理模板类,其隔离级别为提交读,传播行为默认为PROPAGATION_REQUIRED
    * (必须有事务支持,即如果当前没有事务,就新建一个事务,如果已经存在一个事务中,就加入到这个事务中);
    */
    TransactionTemplate transactionTemplate = new TransactionTemplate(jtaTXManager);  //jtaTXManager: JTA事务管理器
    transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
    jdbcTemplate1.update(CREATE_TABLE_SQL);
    int originalCount = jdbcTemplate1.queryForInt(COUNT_SQL);
    try {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                jdbcTemplate1.update(INSERT_SQL, "test");
                //因为数据库2没有创建数据库表因此会回滚事务
                jdbcTemplate2.update(INSERT_SQL, "test");
        }});
    } catch (RuntimeException e) {
        int count = jdbcTemplate1.queryForInt(COUNT_SQL);
        Assert.assertEquals(originalCount, count);
    }
    jdbcTemplate1.update(DROP_TABLE_SQL);
}

事务属性

事务属性通过TransactionDefinition接口实现定义,主要有事务隔离级别、事务传播行为、事务超时时间、事务是否只读。
Spring提供TransactionDefinition接口默认实现DefaultTransactionDefinition,可以通过该实现类指定这些事务属性。

事务隔离级别

用来解决并发事务时出现的问题,其使用TransactionDefinition中的静态变量来指定:

  • ISOLATION_DEFAULT:默认隔离级别,即使用底层数据库默认的隔离级别;
  • ISOLATION_READ_UNCOMMITTED:未提交读;
  • ISOLATION_READ_COMMITTED:提交读,一般情况下我们使用这个;
  • ISOLATION_REPEATABLE_READ:可重复读;
  • ISOLATION_SERIALIZABLE:序列化。
    可以使用DefaultTransactionDefinition类的setIsolationLevel(TransactionDefinition. ISOLATION_READ_COMMITTED)来指定隔离级别,其中此处表示隔离级别为提交读,也可以使用或setIsolationLevelName(“ISOLATION_READ_COMMITTED”)方式指定,其中参数就是隔离级别静态变量的名字,但不推荐这种方式。
事务传播行为

Spring管理的事务是逻辑事务,而且物理事务和逻辑事务最大差别就在于事务传播行为,事务传播行为用于指定在多个事务方法间调用时,事务是如何在这些方法间传播的,Spring共支持7种传播行为:

  • Required:必须有逻辑事务,否则新建一个事务,使用PROPAGATION_REQUIRED指定,表示如果当前存在一个逻辑事务,则加入该逻辑事务,否则将新建一个逻辑事务
    这里写图片描述
    这里写图片描述
  • RequiresNew:创建新的逻辑事务,使用PROPAGATION_REQUIRES_NEW指定,表示每次都创建新的逻辑事务(物理事务也是不同的)
    这里写图片描述
    这里写图片描述
  • Supports:支持当前事务,使用PROPAGATION_SUPPORTS指定,指如果当前存在逻辑事务,就加入到该逻辑事务,如果当前没有逻辑事务,就以非事务方式执行
    这里写图片描述
    这里写图片描述
  • NotSupported:不支持事务,如果当前存在事务则暂停该事务,使用PROPAGATION_NOT_SUPPORTED指定,即以非事务方式执行,如果当前存在逻辑事务,就把当前事务暂停,以非事务方式执行。
    这里写图片描述
    这里写图片描述
  • Mandatory:必须有事务,否则抛出异常,使用PROPAGATION_MANDATORY指定,使用当前事务执行,如果当前没有事务,则抛出异常(IllegalTransactionStateException)
    这里写图片描述
    这里写图片描述
  • Never:不支持事务,如果当前存在是事务则抛出异常,使用PROPAGATION_NEVER指定,即以非事务方式执行,如果当前存在事务,则抛出异常(IllegalTransactionStateException)
    这里写图片描述
    这里写图片描述
  • Nested:嵌套事务支持,使用PROPAGATION_NESTED指定,如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚
    这里写图片描述
    这里写图片描述
事务超时

设置事务的超时时间,单位为秒,默认为-1表示使用底层事务的超时时间;

  • 使用如setTimeout(100)来设置超时时间,如果事务超时将抛出org.springframework.transaction.TransactionTimedOutException异常并将当前事务标记为应该回滚,即超时后事务被自动回滚;
  • 可以使用具体事务管理器实现的defaultTimeout属性设置默认的事务超时时间,如DataSourceTransactionManager.setDefaultTimeout(10)。
事务只读

将事务标识为只读,只读事务不修改任何数据;

  • 对于JDBC只是简单的将连接设置为只读模式,对于更新将抛出异常;
  • 而对于一些其他ORM框架有一些优化作用,如在Hibernate中,Spring事务管理器将执行“session.setFlushMode(FlushMode.MANUAL)”即指定Hibernate会话在只读事务模式下不用尝试检测和同步持久对象的状态的更新
  • 如果使用设置具体事务管理的validateExistingTransaction属性为true(默认false),将确保整个事务传播链都是只读或都不是只读
    这里写图片描述

配置方式实现事务管理

/**
* 在业务类中无需显示的事务管理代码
*/

public class ConfigAddressServiceImpl implements IAddressService {
    private IAddressDao addressDao;
    public void setAddressDao(IAddressDao addressDao) {
        this.addressDao = addressDao;
    }
    @Override
    public void save(final AddressModel address) {
        addressDao.save(address);
    }
    @Override
    public int countAll() {
        return addressDao.countAll();
    }
}


public class ConfigUserServiceImpl implements IUserService {
    private IUserDao userDao;
    private IAddressService addressService; 
    public void setUserDao(IUserDao userDao) {
        this.userDao = userDao;
    }
    public void setAddressService(IAddressService addressService) {
        this.addressService = addressService;
    }
    @Override
    public void save(final UserModel user) {
        userDao.save(user);
        user.getAddress().setUserId(user.getId());
        addressService.save(user.getAddress());

    }
    @Override
    public int countAll() {
        return userDao.countAll();
    }
}
<!-- 目标类定义 -->
<bean id="targetUserService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl">
    <property name="userDao" ref="userDao"/>
    <property name="addressService" ref="targetAddressService"/>
</bean>

<bean id="targetAddressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl">
    <property name="addressDao" ref="addressDao"/>
</bean>

<!-- 配置TransactionProxyFactoryBean类 -->
<bean id="transactionProxyParent" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
    <property name="transactionManager" ref="txManager"/>
    <property name="transactionAttributes">
        <props>
            <prop key="save*">PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,timeout_10,-Exception,+NoRollBackException</prop>
            <prop key="*">PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,readOnly</prop>
        </props>
    </property>
</bean>

<!-- 定义代理Bean -->
<bean id="proxyUserService" parent="transactionProxyParent">
    <property name="target" ref="targetUserService"/>
</bean>

<bean id="proxyAddressService" parent="transactionProxyParent">
    <property name="target" ref="targetAddressService"/>
</bean>
/**
* 测试类
*/

@Test
public void testConfigTransaction() {
    String[] configLocations = new String[] {
            "classpath:chapter7/applicationContext-resources.xml",
            "classpath:chapter9/dao/applicationContext-jdbc.xml",
            "classpath:chapter9/service/applicationContext-service.xml"};
    ApplicationContext ctx2 = new ClassPathXmlApplicationContext(configLocations);

    DataSource dataSource2 = ctx2.getBean(DataSource.class);
    JdbcTemplate jdbcTemplate2 = new JdbcTemplate(dataSource2);
    jdbcTemplate2.update(CREATE_USER_TABLE_SQL);
    jdbcTemplate2.update(CREATE_ADDRESS_TABLE_SQL);

    IUserService userService = ctx2.getBean("proxyUserService", IUserService.class);
    IAddressService addressService = ctx2.getBean("proxyAddressService", IAddressService.class);
    UserModel user = createDefaultUserModel();

    userService.save(user);
    Assert.assertEquals(1, userService.countAll());
    Assert.assertEquals(1, addressService.countAll());

    jdbcTemplate2.update(DROP_USER_TABLE_SQL);
    jdbcTemplate2.update(DROP_ADDRESS_TABLE_SQL);
}

这里写图片描述

代理方式实现事务管理只是将硬编码的事务管理代码转移到代理中去由代理实现,在代理中实现事务管理。

在代理模式下,默认只有通过代理对象调用的方法才能应用相应的事务属性,而在目标方法内的“自我调用”是不会应用相应的事务属性的,即被调用方法不会应用相应的事务属性,而是使用调用方法的事务属性。


第四部分 声明式事务

声明式实现事务管理

<bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl">
    <property name="userDao" ref="userDao"/>
    <property name="addressService" ref="addressService"/>
</bean>
<bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl">
    <property name="addressDao" ref="addressDao"/>
</bean>
<!-- 事务相关配置 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
        <tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED" read-only="true"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="serviceMethod" expression="execution(* cn..chapter9.service..*.*(..))"/>
    <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/>
</aop:config>

多事务语义配置

说白了就是为不同的Bean配置不同的事务属性,因为我们项目中不可能就几个Bean,而可能很多,这可能需要为Bean分组,为不同组的Bean配置不同的事务语义。

<!-- 声明式事务配置实践 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="save*" propagation="REQUIRED" />
        <tx:method name="add*" propagation="REQUIRED" />
        <tx:method name="create*" propagation="REQUIRED" />
        <tx:method name="insert*" propagation="REQUIRED" />
        <tx:method name="update*" propagation="REQUIRED" />
        <tx:method name="merge*" propagation="REQUIRED" />
        <tx:method name="del*" propagation="REQUIRED" />
        <tx:method name="remove*" propagation="REQUIRED" />
        <tx:method name="put*" propagation="REQUIRED" />
        <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
        <tx:method name="count*" propagation="SUPPORTS" read-only="true" />
        <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
        <tx:method name="list*" propagation="SUPPORTS" read-only="true" />
        <tx:method name="*" propagation="SUPPORTS" read-only="true" />
    </tx:attributes>
</tx:advice>
<aop:config>
    <aop:pointcut id="txPointcut" expression="execution(* cn.javass..service.*.*(..))" />
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
</aop:config>

@Transactional实现事务管理

public class AnnotationUserServiceImpl implements IUserService {
    private IUserDao userDao;
    private IAddressService addressService;
    public void setUserDao(IUserDao userDao) {
        this.userDao = userDao;
    }
    public void setAddressService(IAddressService addressService) {
        this.addressService = addressService;
    }
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)
    @Override
    public void save(final UserModel user) {
        userDao.save(user);
        user.getAddress().setUserId(user.getId());
        addressService.save(user.getAddress());
    }
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, readOnly=true)
    @Override
    public int countAll() {
        return userDao.countAll();
    }
}
@Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)
public class AnnotationAddressServiceImpl implements IAddressService {
    private IAddressDao addressDao;    
    public void setAddressDao(IAddressDao addressDao) {
        this.addressDao = addressDao;
    }
    @Override
    public void save(final AddressModel address) {
        addressDao.save(address);
        throw new RuntimeException();//将导致事务回滚
    }
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, readOnly=true)
    @Override
    public int countAll() {
        return addressDao.countAll();
    }
}
<!-- 业务实现配置 -->
<bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl">
    <property name="userDao" ref="userDao"/>
    <property name="addressService" ref="addressService"/>
</bean>
<bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl">
    <property name="addressDao" ref="addressDao"/>
</bean>

<!-- 事务相关配置 -->
<tx:annotation-driven transaction-manager="txManager"/>

使用@Transaction注解事务管理需要特别注意以下几点:

  • 如果在接口、实现类或方法上都指定了@Transactional 注解,则优先级顺序为方法 > 实现类 > 接口;
  • 建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制是没问题,因为其使用基于接口的代理;而使用使用CGLIB代理机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”;
  • 在JDK代理机制下,“自我调用”同样不会应用相应的事务属性,其语义和< tx:tags>中一样;
  • 默认只对RuntimeException异常回滚;
  • 在使用Spring代理时,默认只有在public可见度的方法的@Transactional 注解才是有效的,其它可见度(protected、private、包可见)的方法上即使有@Transactional 注解也不会应用这些事务属性的,Spring也不会报错,如果你非要使用非公共方法注解事务管理的话,可考虑使用AspectJ。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值