Fork me on GitHub

Spring 框架的事务管理

1. Spring 框架的事务管理相关的类和API

  • PlateformTransactionManager 接口: 平台事务管理器(真正管理事务的类);
  • TransactionDefinition 接口: 事务定义信息(事务的隔离级别,传播行为,超时,只读等);
  • TransactionStatus 接口: 事务的状态;
  • 平台事务管理器真正管理事务对象,根据事务定义信息(TransactionDefinition)进行事务管理,在管理事务
    中产生的一些状态记录到TransactionStatus中;

1.1 PlateformTransactionManager 接口中实现类和常用的方法

  1. 接口的实现类
    • 如果使用 Spring 框架的JDBC模板或者MyBatis框架,需要选择DataSourceTransactionManager实现类;
    • 如果使用的是 Hibernate 框架,需要选择HibernateTransactionManager实现类;
  2. 该接口的常用方法
    • void commit(TransactionStatus status)
    • TransactionStatus getTransaction(TransactionDefinition definition)
    • void rollback(TransactionStatus status)

1.2 TransactionDefinition

  1. 事务隔离级别的常量

    • static int ISOLATION_DEFAULT: 采用数据库的默认隔离级别;
    • static int ISOLATION_READ_UNCOMMITTED
    • static int ISOLATION_READ_COMMITTED
    • static int ISOLATION_REPEATABLE_READ
    • static int ISOLATION_SERIALIZABLE
  2. 事务的传播行为常量(一般使用默认值)

    • 事务的传播行为:解决的是业务层之间的方法调用!
    • 保证 A,B 在同一个事务中:
      • PROPAGATION_REQUIRED(默认值): A中有事务,使用A中的事务;如果没有,B会开启一个新的事务,
        将A包含进来(保证A,B在同一个事务中);
      • PROPAGATION_SUPPORTS: A中有事务,使用A中的事务;如果A中没有事务,那么B也不使用事务;
      • PROPAGATION_MANDATORY: A中有事务,使用A中的事务; 如果A中没有事务,抛出异常;
    • 保证 A,B 没有在同一个事务中:
      • PROPAGATION_REQUIRES_NEW: A中有事务,将A中的事务挂起,B创建一个新的事务,
        (保证A,B不在同一个事务中);
      • PROPAGATION_NOT_SUPPORTED: A中有事务,将A中的事务挂起;
      • PROPAGATION_NEVER: A中有事务,抛出异常;
    • PROPAGATION_NESTED: 嵌套事务,当 A 执行之后,就会在这个位置设置一个保存点,如果B没有问题,
      执行通过;如果B出现异常,根据需求回滚(可以回滚到保存点或最初始的状态);

2. 搭建事务管理转账案例的环境

2.1 导入 jar 包

  • Spring 框架的基本开发包(6个);
  • Spring 的传统AOP的开发包
    • spring-aop-4.3.10.RELEASE
    • org.aopalliance-1.10.0 (在 Spring 依赖包中)
  • aspectJ 的开发包
    • org.aspectj.weave-1.6.8.RELEASE.jar (在 Spring 依赖包中)
    • spring-aspects-4.3.10.RELEASE.jar
  • JDBC 模板所需 jar 包
    • mysql-connector-java.jar: MySql 驱动包;
    • Spring-jdbc.jar;
    • Spring-tx.jar: Spring 事务包;
  • c3p0 的 jar 包: com.mchange.v2.c3p0-0.9.1.2.jar

2.2 创建对应的包结构和类

  • com.itheima.demo
    • AccountService
    • AccountServiceImpl
    • AccountDao
    • AccountDaoImpl
// AccountDao.java
    public class AccountDao {
        // 扣钱
        public void outMoney(String out, double money);

        // 加钱
        public void inMoney(String in, double money);
    }

// AccountService.java
    public class AccountService{
        public void pay(String out,String in, double money);
    }

// AccountDaoImpl.java
    // 第一种方式, 使用 jdbc 模板类
    public class AccountDaoImpl implements AccountDao{
        // 使用 jdbc 模板操作数据库
        // 在配置文件中注入 jdbcTemplate 对象
        private JdbcTemplate jdbcTemplate;
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
            this.jdbcTemplate = jdbcTemplate;
        }

        // 扣钱
        public void outMoney(String out, double money){
            jdbcTemplate.update(sql,args);
        }

        // 加钱
        public void inMoney(String out, double money){
            jdbcTemplate.update(sql,args);
        }
    }

    // 第二种方式, 继承 JdbcDaoSupport类, 该类中包含 jdbcTemplate,并且提供了 set方法
    //           applicationContext.xml 中仍需要注入 jdbcTemplate 对象

    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{

        // 扣钱
        public void outMoney(String out, double money){
            this.getJdbcTemplate().update(sql,args);
        }

        // 加钱
        public void inMoney(String in, double money){
            this.getJdbcTemplate().update(sql,args);
        }
    }

    // 第三种方式: applicationContext.xml 中无需注入 jdtcTemplate 对象
    //           只需要注入连接池对象,
    // JdbcDaoSupport 类内部,在 jdbcTemplate 对象为 null 时, 根据连接池创建 jdbcTemplate 对象

    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{

        // 扣钱
        public void outMoney(String out, double money){
            this.getJdbcTemplate().update("UPDATE t_account SET"
                                +"money = money - ? WHERE name = ?",money,out);
        }

        // 加钱
        public void inMoney(String in, double money){
            this.getJdbcTemplate().update("UPDATE t_account SET"
                                +"money = money - ? WHERE name = ?",money,in);
        }
    }


// AccountServiceImpl.java
    public class AccountServiceImpl implements AccountService{

        private AccountDao accountDao;
        public void setAccountDao(AccountDao accountDao){
            this.accountDao = accountDao;
        }

        // 实现转账功能
        public void pay(String out, String in, double money){
            accountDao.outMoney(out,money);
            accountDao.inMoney(in,money);
        }
    }


// 测试类
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class Demo{

        @Resource(name="accountService")
        private AccountServiceImpl accountService;

        @Test
        public void fun(){
            // 测试支付方法
            accountService.pay("张三","李四",100);
        }
    }

2.3 applicationContext.xml

<!-- 管理c3p0 连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mydb1"/>
    <property name="user" value="root"/>
    <property name="password" value="root"/>
</bean>

<!-- 管理JDBC的模板类 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" value="dataSource"/>
</bean>

<!-- 配置业务层和持久层 -->
<bean id="accountService" class="com.itheima.demo.AccountServiceImpl">
    <property name="accountDao" value="accountDao"/>
</bean>

<!-- 第一种和第二种方式, 需要注入 jdbcTemplate 对象 -->
<bean id="accountDao" class="com.itheima.demo.AccountDaoImpl">
    <property name="jdbcTemplate" value="jdbcTemplate"/>
</bean>

<!-- 第三种方式, 只需要注入连接池 -->
<bean id="accountDao" class="com.itheima.demo.AccountDaoImpl">
    <property name="dataSource" ref="dataSource"/>
</bean>

2.4 Spring 框架事务管理的分类

  1. Spring 的编程式事务管理(不推荐使用)
    • 通过手动编写代码的方式完成事务的管理;
  2. Spring 的声明式事务管理(底层采用AOP的技术)
    • 通过一段配置的方式完成事务的管理;
2.4.1 编程式的事务管理
  1. 手动编程的方式来管理事务,需要使用 TransactonTemplate模板类;
// 具体步骤
// 1. 配置一个事务管理器
//    Spring 框架使用 PlatFormTransactionManager 接口来管理事务,需要使用到它的实现类
<bean id="transactionManager"
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    // 底层操作事务的是 connection, 该对象存放在连接池
    // 事务管理器管理事务,需要从连接池中获取 connection 对象
    <property name="dataSource" ref="dataSource"/>
</bean>

// 2. 配置事务管理的模板
<bean id="transactionTemplate"
                class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager"/>
 </bean>

// 3. 在需要进行事务管理的类中,注入事务管理的模板
//     备注: 事务需要在业务层开启
    <bean id="accountService" class="com.itheima.demo.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="transactionTemplate" ref="transactionTemplate"/>
    </bean>

// 4. 在业务层使用模板管理事务
public void AccountServiceImpl implements AccountService{

    private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao){
        this.accountDao = accountDao;
    }

    // 注入事务模板对象
    private TransactionTemplate transactionTemplate;
    public void setTransactionTemplate(TransactionTemplate transactionTemplate){
        this.transactionTemplate = transactionTemplate;
    }

    // 实现转账功能
    public void pay(final String out, final String in, final double money){
        transactionTemplate.execute(new TransactionCallbackWithoutResult(){
            protected void doInTransactionWithoutResult(TransactionStatus status){
                // 扣钱
                accountDao.outMoney(out,money);

                // 异常
                int a = 10 / 0;

                // 加钱
                accountDao.inMoney(in,money);
            }
        });
    }
}
2.4.2 声明式事务管理
  1. 声明式事务管理:即通过配置文件来完成事务管理(AOP 思想)
    • 基于AspectJ的XML方式;(重点掌握)
    • 基于AspectJ的注解方式;(重点掌握)
基于AspectJ的XML方式
// applicationContext.xml
// 1. 配置一个事务管理器
//    Spring 框架使用 PlatFormTransactionManager 接口来管理事务,需要使用到它的实现类
<bean id="transactionManager"
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    // 底层操作事务的是 connection, 该对象存放在连接池
    // 事务管理器管理事务,需要从连接池中获取 connection 对象
    <property name="dataSource" ref="dataSource"/>
</bean>

// 2. 配置事务增强(通知)
<tx:advice id="myAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!--
          可以配置多个 method;
          name       : 绑定事务的方法名,可以使用通配符;
          propagation: 传播行为;
          isolation  : 隔离级别;
          read-only  : 缓存是否只读;
          timeout    : 超时信息;
          rollback-for: 发生哪些异常信息回滚;
          no-rollback-for: 发生哪些异常,不回滚;
          -->

          <tx:method name="pay" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

// 3. 配置 AOP 切面
//    备注: 如果是自定义的切面,使用<aop:aspect>标签, 如果是系统自带的,使用<aop:advisor>标签
<aop:config>
    <aop:advisor advice-ref="myAdvice"
                pointcut="execution(public * com.itheima.demo.AccountServiceImpl.pay(..))"/>
</aop:config>

// 4. 持久层配置
    <bean id="accountService" class="com.itheima.demo.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>
基于AspectJ的注解方式(重点掌握,这是最简单的方式)
// 1. 配置事务管理器
<bean id="transactionManager"
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

// 2. 开启注解事务
<tx:annotation-driven transaction-manager="transactionManager"/>

// 3. 在业务层上添加一个注解: @Transactional
// AccountServiceImpl.java

    // 如果在这个类上添加注解, 类中的所有方法全部都有事务
    @Transactional
    public class AccountServiceImpl implements AccountService{

        private AccountDao accountDao;
        public void setAccountDao(AccountDao accountDao){
            this.accountDao = accountDao;
        }

        // 实现转账功能
        // 如果在方法上添加,只有该方法有事务
        @Transactional
        public void pay(String out, String in, double money){
            accountDao.outMoney(out,money);
            accountDao.inMoney(in,money);
        }
    }

参考资料

posted @ 2017-10-24 17:12  小a的软件思考  阅读(286)  评论(0编辑  收藏  举报