Spring的事务管理

什么是事务?

l  事务:逻辑上的一组操作,组成这组操作的各个单元,要么全都成功,要么全都失败。

理解事务之前,先讲一个你日常生活中最常干的事:转账。 
比如你给朋友转账1000块钱,大体有两个步骤:首先输入密码金额,你的银行卡扣掉1000元钱;然后你朋友账上增加1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000块但是转账失败,你朋友没收到1000元的话,你将会损失1000元;如果银行卡扣钱失败但是你朋友账户却增加了1000块,那么银行将损失1000元。所以,如果一个步骤成功另一个步骤失败对双方都不是好事,如果不管哪一个步骤失败了以后,整个转账过程都能回滚,也就是完全取消所有操作的话,这对双方都是极好的。 
事务就是用来解决类似问题的。事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。 
在企业级应用程序开发中,事务管理必不可少的技术,用来确保数据的完整性和一致性。 

事务有四个特性:ACID

l  原子性(Atomicity):事务不可分割

l  一致性(Consistency):事务执行前后数据完整性保持一致

l  隔离性(Isolation):一个事务的执行不应该受到其他事务的干扰

l  持久性(Durability):一旦事务结束,数据就持久化到数据库

Spring的事务管理的API

PlatformTransactionManager:平台事务管理器

•平台事务管理器:接口,是Spring用于管理事务的真正的对象。

♦DataSourceTransactionManager  :底层使用JDBC管理事务

♦ HibernateTransactionManager       :底层使用Hibernate管理事务

TransactionDefinition   :事务定义信息

•事务定义:用于定义事务的相关的信息,隔离级别、超时信息、传播行为、是否只读

TransactionStatus:事务的状态

•事务状态:用于记录在事务管理过程中,事务的状态的对象。

事务管理的API的关系:

Spring进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务的管理,在事务管理过程中,

产生各种状态,将这些状态的信息记录到事务状态的对象中。

Spring的事务的传播行为

* 事务的传播行为主要用来解决业务层方法相互调用的问题。

l  Spring中提供了七种事务的传播行为(可分为三类):

♦ 保证多个操作在同一个事务中

•  PROPAGATION_REQUIRED    :默认值,如果A方法有事务,使用A中的事务,如果A没有,创建一个新的事务,将操作包含进来

•  PROPAGATION_SUPPORTS    :支持事务,如果A方法有事务,使用A中的事务。如果A没有事务,不使用事务。

•        PROPAGATION_MANDATORY    :如果A方法有事务,使用A中的事务。如果A没有事务,抛出异常。

♦  保证多个操作不在同一个事务中

•  PROPAGATION_REQUIRES_NEW    :如果A有事务,将A的事务挂起(暂停),创建新事务,只包含自身操作。如果A中没有事务,创建一个新事务,包含自身操作。

•  PROPAGATION_NOT_SUPPORTED   :如果A有事务,将A的事务挂起。不使用事务管理。

•  PROPAGATION_NEVER       :如果A有事务,报异常。

♦  嵌套式事务

•  PROPAGATION_NESTED        :嵌套事务,如果A有事务,按照A的事务执行,执行完成后,设置一个保存点,执行B的操作,如果没有异常,执行通过,如果有异常,可以选择回滚到最初始位置,也可以回滚到保存点。

接着模拟一下上述转账这个过程的事务管理

先搭建这个事物管理环境:

创建Service的接口和实现类

/**
 * 转账的业务层的实现类
 * @author jt
 *
 */
public class AccountServiceImpl implements AccountService {

    // 注入DAO:
    private AccountDao accountDao;
    
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    
    @Override
    /**
     * from:转出账号
     * to:转入账号
     * money:转账金额
     */
    public void transfer( String from,  String to,  Double money) {
        
        accountDao.outMoney(from, money);
//        int d = 1/0;
//             上一行是模拟转账过程出问题,这里先暂时注释掉。
        accountDao.inMoney(to, money);
        
    }

}

创建DAO的接口和实现类

/**
 * 转账的DAO的实现类
 * @author jt
 *
 */
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
  //这里继承了JdbcDaoSupport是模版类,就省的再进行模版注入了
    @Override
    public void outMoney(String from, Double money) {
        this.getJdbcTemplate().update("update account set money = money - ? where name = ?", money,from);
    }

    @Override
    public void inMoney(String to, Double money) {
        this.getJdbcTemplate().update("update account set money = money + ? where name = ?", money ,to);
    }

}

配置Service和DAO:交给Spring管理

<!-- 配置Service============= -->
    <bean id="accountService" class="com.xk.xx.xx.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>
    
    <!-- 配置DAO================= -->
    <bean id="accountDao" class="com.xk.xx.xx.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>

 配置连接池和JDBC的模板

<!-- 配置连接池 -->
    <!-- 第二种方式通过context标签引入的 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <!-- 配置C3P0连接池=============================== -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

这是运行测试类之前的数据库数据。

编写测试类:

/**
 * 测试转账的环境
 * @author jt
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {

    @Resource(name="accountService")
    private AccountService accountService;
    
    @Test
    public void demo1(){
        accountService.transfer("甲", "丁", 1000d);
    }
}

运行测试类后数据库数据如下:

转账正常进行。

接着将数据恢复转账之前,然后将之前AccountServiceImpl中提到的注释打开,模拟转账出现问题。然后运行测试类

会发现,甲的钱被扣了1000,但是丁却没有收到

这就是开头所举例子。

那么接下来用事物管理来解决这种问题的发生。

首先配置事务管理器

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

配置增强

<!-- 配置事务的增强=============================== -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 事务管理的规则 -->
            <!-- <tx:method name="save*" propagation="REQUIRED" isolation="DEFAULT"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="find*" read-only="true"/> -->
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
        </tx:attributes>
    </tx:advice>

AOP的配置

<!-- aop的配置 -->
    <aop:config>
        <aop:pointcut expression="execution(* com.xk.xx.xx.AccountServiceImpl.*(..))" id="pointcut1"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
    </aop:config>

 再次运行测试类:

发现转账失败,但是没有扣甲的钱,丁的钱也没有增加

因为转账一旦出现问题,整个过程都会回滚。

这就是事务管理的作用之一。


上面采用的声明式事务管理是用xml配的。采用的是AOP思想。

也可以选择用注解的方式:

这里,配置文件中,只需配置事务管理器然后开启注解事务即可

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

然后在业务层添加注解

@Transactional还有许多属性,根据需要添加即可

注解的方式,配置少些,但是每次必须自行给需要的业务层添加注解。

原文地址:https://www.cnblogs.com/xk920/p/9805409.html