Spring 中的事务管理

1、事务简介
  • 事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性。
  • 事务就是一系列的动作,它们被当做一个单独的工作单元。这些动作要么全部完成,要么全部不起作用
  • 事务的四个关键属性 (ACID)

    • 原子性 (atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成要么完全不起作用。
    • 一致性 (consistency):一旦所有事务动作完成,事务就被提交。数据和资源就处于一种满足业务规则的一致性状态中。
    • 隔离性 (isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
    • 持久性 (durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该收到影响。通常情况下,事务的结果被写到持久化存储器中。
2、Spring 中的事务管理
  • 作为企业级应用程序框架,Spring 在不同的事务管理 API 之上定义了一个抽象层。而应用程序开发人员不必了解底层的事务管理 API,就可以使用 Spring 的事务管理机制。
  • Spring 既支持编程式的事务管理,也支持声明式的事务管理。
  • 编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码。
  • 声明式事务管理:大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点,可以通过 AOP 方法模块化。Spring通过 Spring AOP 框架支持声明式事务管理。
3、Spring 中的事务管理器
  • Spring 从不同的事务管理 API 中抽象了一整套的事务机制。开发人员不必了解底层的事务 API,就可以利用这些事务机制。有了这些事务机制,事务管理代码就能独立于特定的事务技术了。
  • Spring 的核心事务管理抽象是 Interface PlatformTransactionManager 管理封装了一组独立于技术的方法。无论使用 Spring 的哪种事务管理策略 (编程式或声明式),事务管理器都是必须的。
  • Spring 中的事务管管理器的不同实现

    • Class DataSourceTransactionManager:在应用程序中主需要处理一个数据源,而且通过 JDBC 存取

      配置事务管理器:

      <!-- 配置事务管理器 -->
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"/>
      </bean>
      
      <!-- 启用事务注解 -->
      <tx:annotation-driven transaction-manager="transactionManager"/>

      添加事务注解 @Transactional:

      @Repository("bookShopService")
      public class BookShopServiceImpl implements BookShopService {
      
          @Autowired
          private BookShopDao bookShopDao;
      
          //添加事务注解
          @Transactional
          @Override
          public void purchase(String isbn, String username) {
              //1.获取书的单价
              int price = bookShopDao.findBookPriceByIsbn(isbn);
      
              //2.更新书的库存
              bookShopDao.updateBookStock(isbn);
      
              //3.更新用户余额
              bookShopDao.updateUserAccount(username, price);
      
          }
      
      }
    • Class JtaTransactionManager:在 JavaEE 应用服务器上用 JTA (Java Transaction API) 进行事务管理
    • Class HibernateTransactionManager:用 Hibernate 框架存取数据库
    • ……
    • 事务管理器以普通的 Bean 形式声明在 Spring IOC 容器中
4、事务的传播属性
  • 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新的事务,并在自己的事务中运行。
  • 事务的传播行为可以由 传播属性指定。Spring 定义了 7 中类传播行为。
  • 传播属性描述
    REQUIRED如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行。
    REQUIRED_NEW当前的方法必须启动新事务,并在它自己的事务给运行。如果有事务正在运行,应该将它挂起。
    SUPPORTS如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中。
    NOT_SUPPORTED当前的方法不应该运行在事务中,如果有运行的事务,将它挂起。
    MANDATORY当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常。
    NEVER当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常。
    NESTED如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行。
    • REQUIRED 传播行为

      当 bookService 的 purchase() 方法被另一个事务方法 checkout() 调用时,它默认会在现有的事务内运行。这个默认的传播行为就是 REQUIRED。 因此在 checkout() 方法的开始和终止边界内只有一个事务。这个事务只在 checkout() 方法结束的时候被提交,结果用户一本书都买不了。

      事务传播属性可以在 @Transactional 注解的 propagation 属性中定义。

    • REQUIRES_NEW 传播行为

      另一种常见的传播行为是 REQUIRES_NEW。它表示该方法必须启动一个新事物,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

5、事务的其他属性
  • 隔离级别

    • 使用 ioslation 指定事务的隔离级别,最常用的取值为 READ_COMMITTED。
  • 回滚

    • 默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下取默认值即可。
  • 只读

    • 使用 readOnly 指定事务是否为只读。
    • 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。
    • 若真的只是一个只读取数据库值的方法,应设置 readOnly = true。
  • 过期

    • 使用 timeout 指定强制回滚之前事务可以占用的时间。
    • 单位是 秒(s)。
6、基于 xml 文件的方式配置事务管理器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!-- 导入资源文件 -->
    <context:property-placeholder location="classpath:db.properties"/>

    <!-- 配置 c3p0 数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>

        <property name="initialPoolSize" value="${jdbc.initPoolSize}"/>
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
    </bean>

    <!-- 配置 Spring 的 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置 bean -->
    <bean id="bookShopDao" class="com.spring.tx.xml.BookShopDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    <bean id="bookShopService" class="com.spring.tx.xml.service.impl.BookShopServiceImpl">
        <property name="bookShopDao" ref="bookShopDao"/>
    </bean>
    <bean id="cashier" class="com.spring.tx.xml.service.impl.CashierImpl">
        <property name="bookShopService" ref="bookShopService"/>
    </bean>

    <!-- 1.配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 2.配置事务属性 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 根据方法名指定事务的属性 -->
            <tx:method name="purchase" propagation="REQUIRES_NEW"/>
            <!-- 通常情况下,对于 get 或者 find 开头的方法 read-only 的值为 true -->
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来 -->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.spring.tx.xml.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>

</beans>
7、Spring 整合 Hibernate
  • Spring 整合 Hibernate 整合什么?

    • 由 IOC 容器来管理 Hibernate 的 SessionFactory
    • 让 Hibernate 使用上 Spring 的声明式事务
  • 整合步骤:

    • 加入 hibernate

      • 导入 jar 包

        <dependency>
                <groupId>org.jboss.logging</groupId>
                <artifactId>jboss-logging</artifactId>
                <version>3.1.0.GA</version>
            </dependency>
            <dependency>
                <groupId>org.jboss.spec.javax.transaction</groupId>
                <artifactId>jboss-transaction-api_1.1_spec</artifactId>
                <version>1.0.1.Final</version>
            </dependency>
            <dependency>
                <groupId>org.javassist</groupId>
                <artifactId>javassist</artifactId>
                <version>3.15.0-GA</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate.javax.persistence</groupId>
                <artifactId>hibernate-jpa-2.0-api</artifactId>
                <version>1.0.1.Final</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>4.2.4.Final</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate.common</groupId>
                <artifactId>hibernate-commons-annotations</artifactId>
                <version>4.0.2.Final</version>
            </dependency>
            <dependency>
                <groupId>dom4j</groupId>
                <artifactId>dom4j</artifactId>
                <version>1.6.1</version>
            </dependency>
            <dependency>
                <groupId>antlr</groupId>
                <artifactId>antlr</artifactId>
                <version>2.7.7</version>
            </dependency>
      • 添加 hibernate 的配置文件:hibernate.cfg.xml

        <?xml version='1.0' encoding='utf-8'?>
        <!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD//EN"
            "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
        <hibernate-configuration>
          <session-factory>
            <!-- 配置 hibernate 的基本信息 -->
            <!-- 1. 数据源需配置到 IOC 容器中,所以在此处不需要再配置数据源 -->
            <!-- 2. 关联的 .hbm.xml 也在 IOC 容器配置 SessionFactory 实例时再进行配置 -->
            <!-- 3. 配置 hibernate 的基本属性:方言,SQL 显示及格式化,生成数据表的策略以及二级缓存等 -->
            <property name="hibernate.dialect"> org.hibernate.dialect.MySQL5InnoDBDialect </property>
        
            <property name="hibernate.show_sql"> true </property>
            <property name="hibernate.format_sql"> true </property>
        
            <property name="hibernate.hbm2ddl.auto"> update </property>
        
            <!-- 配置 hibernate 二级缓存相关的属性 -->
        
            
          </session-factory>
        </hibernate-configuration>
  • 加入 Spring
  • 整合