12 Spring框架 SpringDAO的事务管理

上一节我们说过Spring对DAO的两个支持分为两个知识点,一个是jdbc模板,另一个是事务管理。

事务是数据库中的概念,但是在一般情况下我们需要将事务提到业务层次,这样能够使得业务具有事务的特性,来管理业务。

例如:在银行的转账系统中,张三转账给李四,需要完成从张三的账户上扣取指定金额并加到李四的账户上,这样一个过程需要具有原子性,即要成功都成功,要失败都失败。转账的过程即两个对账户更新,需要将事务提升到业务层次,使得两个操作具有原子性!

对以上的实现,Spring的API中有两个常用的接口我们会使用到:

分别是:PlatformTransactionManager 平台事务管理接口,和TransactionDefinition事务定义接口。

(1)PlatformTransactionManager 
平台管理器接口里面只定义了三个方法:

分别是:提交,回滚,获得当前事务的状态方法。

平台管理器接口有两个主要的实现类:

①DataSourceTransactionManager:使用jdbc或ibatis进行数据持久化时使用

②HibernateTransactionManager:使用Hibernate进行数据持久化时使用

Spring的默认回滚方式有两种:一种是运行时错误进行回滚,另一种是发生受查异常时提交,但是对于受查异常我们可以手工设置其回滚方式。

(2)TransactionDefinition

     事务定义接口里面定义了五个事务隔离级别常量,七个事务传播行为常量,和默认事务超时时限。(这里就不具体介绍,AIP里面都有详细说明)

Spring提供了两种管理事务的方式:

  ①事务代理工厂 
  ②事务注解 
Spring也整合了AspectJ的: 
  ①AOP配置事务管理

我们来实现一个以下的功能,并在这个功能上来进行事务管理:

需求:银行系统需要完成一个转账系统,完成两个用户之间的转账,转出用户减少指定的金额,转入账户增加指定的金额。

项目环境:win7,eclipse,Junit4,Spring jdbc模板(上一节的环境)

项目目录结构:

按照需求我们先写一个service接口:

//转账服务
public interface TransferAccountsService {
    //开户,这里就以name为唯一的标识
    void createAccount(String name,int balance);
    //转账
    void transferAccounts(String userA,int money,String userB);
}

然后实现类:

public class TransferAccountServiceImpl implements TransferAccountsService{
    //accountDao这个有Spring进行注入
    AccountDao accountDao;
    //保留setter方法
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
    //开户方法实现
    @Override
    public void createAccount(String name, int balance) {
        accountDao.insert(name, balance);

    }
    //转账方法实现
    @Override
    public void transferAccounts(String userA, int money, String userB) {
        accountDao.update(userA, money, true);//true 表示转出
        accountDao.update(userB, money, false);//false代表转入
    }
}

然后就要完成DAO了,还是一个DAO接口:

//DAO接口
public interface AccountDao {
    //插入用户
    void insert(String name, int balance);
    //更新用户
    void update(String user, int money,boolean transfer);
}

AccountDao 的实现类

//因为需要使用jdbc模板,所以需要继承JdbcDaoSupport
//如果对jdbc模板不熟悉的可以看一下我Spring框架11章的内容
public class AccountDaoImpl extends  extends JdbcDaoSupport implements AccountDao{

    @Override
    public void insert(String name, int balance) {
        String sql = "insert into account (name,balance) values(?,?)";
        this.getJdbcTemplate().update(sql,name,balance);
    }

    //更新账户,如果transfer为true表示转出,否则转入
    @Override
    public void update(String user, int money,boolean transfer) {
        String sql = "update account set balance=balance+? where name=?";
        if(transfer) {
            sql = "update account set balance=balance-? where name=?";
        }
        this.getJdbcTemplate().update(sql,money,user);
    }

然后是我的完整配置文件:

<?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" 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">

    <!-- 注册c3p0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///test?useUnicode=true&amp;characterEncoding=utf8"/>
        <property name="user" value="root"/>
        <property name="password" value="123"/>
    </bean> 

    <!-- 注册jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 注册AccountDaoImpl -->
    <bean id="AccountDaoImpl" class="com.testSpring.Dao.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

    <!-- 注册TransferAccountServiceImpl -->
    <bean id="transferAccout" class="com.testSpring.Service.TransferAccountServiceImpl">
        <property name="accountDao" ref="AccountDaoImpl"></property>
    </bean>

</beans>

完成以上的步骤我们就可以测试了:

测试:

public class Test01 {
    private TransferAccountsService service;
    @Before
    public void before() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        service = (TransferAccountsService)ac.getBean("transferAccout");
    }

    //创建两个用户
//  public void test01() {
//      service.createAccount("张三",10000);
//      service.createAccount("李四", 10000);
//  }

    @Test
    public void test02() {
        //service.transferAccounts("张三", 1000, "李四");
        service.transferAccounts("李四",1000,"张三");
    }
}

上面test01中我们先创建了两个用户:张三,李四,并为他们预存了10000元人民币;test02中我们分别从张三向李四和李四向张三转账1000元,通过查看数据库,我们得知测试成功。

以上的代码是通过我测试的,但是在应用过程中,难免会发生从张三的账户扣取了1000元,而李四的账户未增加金额(扣除和增加更新操作中出现了异常),所以我们来模拟这个场景:

我们创建一个异常:

//转账异常
public class TransferException extends Exception {
    private static final long serialVersionUID = 1L;

    public TransferException() {
        super();

    }

    public TransferException(String message) {
        super(message);
    }
}

并在service代码中的两个更新操作之间抛出:

public class TransferAccountServiceImpl implements TransferAccountsService{
    AccountDao accountDao;

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

    @Override
    public void createAccount(String name, int balance) {
        accountDao.insert(name, balance);
    }

    @Override
    public void transferAccounts(String userA, int money, String userB) throws TransferException {
        accountDao.update(userA, money, true);//true 表示转出
        if(1==1) {
            throw new TransferException("转账不成功");
        }

        accountDao.update(userB, money, false);//false代表转入
    }
}

这样就会产生异常(更改后的代码需要在service接口,service方法和test方法后throws异常)。

当产生异常的时候就会发生,张三账户余额少了,但是李四账户余额不变的情况。

我们就要使用Spring提供的途径来解决这样的问题:

(一)使用Spring提供的事务代理工厂bean来管理事务:

这个类名全称为:TransactionProxyFactoryBean,是我们之前在AOP中使用的ProxyFactoryBean的子类,事务的管理其实就是利用AOP的方法来实现管理。

这样我们按照AOP的思想,我们还需要一个切面类,这个类是我们自己写吗?不,这个类已经有了,就是我们上面说到的DataSourceTransactionManager,和HibernateTransactionManager,因为我们使用的是jdbc模板,所以这里我们使用DataSourceTransactionManager。

说了那么多不如我们具体看看到底是如何配置的:

<!--省略了上面的配置-->
<!-- 注册事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 生成Service的事务代理对象 -->
    <bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="target" ref="transferAccout"/>
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="createAccount">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
                <prop key="transferAccounts">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>

这里的配置是不是和Spring的AOP配置有那么点相似?

transactionAttributes事务属性中填写了相关的事务常量,里面的ISOLATION_DEFAULT,PROPAGATION_REQUIRED是Spring默认使用的两种:默认隔离级别,传播需要。

上面我们指定了两个方法:createAccount(创建用户方法),transferAccounts(转账方法),都是默认事务属性。

我们之前说过了,Spring的默认回滚方式有两种:一种是运行时错误进行回滚,另一种是发生受查异常时提交。

因为我们自定义的异常是属于受查异常,所以我们可以控制它进行回滚,只要我们再事务属性指定上添加上这样的代码:

<property name="transactionAttributes">
            <props>
                <prop key="createAccount">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
                <prop key="transferAccounts">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-TransferException</prop>
            </props>
        </property>
        <!--在transferAccounts方法上加-TransferException,当发生TransferException异常的时候会进行回滚-->

当我们需要将受查异常都设置成回滚的时候,我们就可以将所有的方法所设置成这样:

<prop key="*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-Exception</prop>

这样,所有的方法事务属性都是默认的,且当发生受查异常时都会发生回滚。

当我们需要将发生的运行时错误进行提交的时候,就在方法的事务属性后添加“+异常名”。 
即“-”回滚,“+”提交

进行了以上的修改,我们再进行测试会发现虚拟机报异常(我们将所有的异常都向外面抛,最后虚拟机挂掉),但是张三和李四的账户余额都未发生改变,这样就完成了我们对事务的管理。

(二)使用Spring的事务注解来管理事务

同样,上面的xml配置事务管理器在实际开发中不太使用(因为后两种用的比较频繁),当我们需要为很多事务添加事务管理的时候,配置文件会变得很臃肿,所以我们可以使用注解的方式来实现我们事务管理器。

第一步:

环境:在上面例子的基础上,我们需要更改下约束文件,我们到Spring的开发包中找到对应的事务约束:

(图片可能不太清楚,具体的到spring-framework-4.1.6.RELEASE-dist/spring-framework-4.1.6.RELEASE/docs/spring-framework-reference/html/xsd-config.html 查看)

约束文件添加结束后我们再eclipse的preferences中的xml下添加约束标签:

接下来我们将事务代理对象bean删除(其他不变),加入:

  <!-- 注解驱动,在使用之前要添加事务的约束 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <!--transactionManager为上面我们注册的事务管理器-->

第二步:

添加注解,在我们需要添加事务的方法上添加Transactional注解:

上面的代码和之前的测试代码一毛一样,在需要添加事务的方法上加上如上图的注解:

@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=TransferException.class)

这样我们就可以测试了,但是这里的getBean("transferAccout")不再是之前的代理对象了,而是我们注册的业务类。

public class Test01 {
    private TransferAccountsService service;
    @Before
    public void before() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        service = (TransferAccountsService)ac.getBean("transferAccout");
    }

    //创建两个用户
//  public void test01() {
//      service.createAccount("张三",10000);
//      service.createAccount("李四", 10000);
//  }

    @Test
    public void test02() throws TransferException {
        service.transferAccounts("张三", 1000, "李四");
        //service.transferAccounts("李四",1000,"张三");
    }
}

这样,我们Spring对事务的两种管理就是这样了(其实是一种)。

接下来我们看看AspectJ怎么使用AOP思想来对事务进行管理:

(三) AspectJ的AOP配置管理事务

环境: 
我们要使用AspectJ还是要导入Spring对AspectJ整合的jar包,还要导入AOP联盟和SpringAOP的jar包。

这里我们给出整个配置文件:

<?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:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.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">

    <!-- 注册c3p0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///test?useUnicode=true&amp;characterEncoding=utf8"/>
        <property name="user" value="root"/>
        <property name="password" value="123"/>
    </bean> 

    <!-- 注册jdbcTemplate -->
<!--    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean> -->

    <!-- 注册AccountDaoImpl-->
    <bean id="AccountDaoImpl" class="com.testSpring.Dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean> 

    <!-- 注册TransferAccountServiceImpl-->
    <bean id="transferAccout" class="com.testSpring.Service.TransferAccountServiceImpl">
        <property name="accountDao" ref="AccountDaoImpl"></property>
    </bean>

    <!-- 注册事务管理器 -->
    <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="createAccount" isolation="DEFAULT" propagation="REQUIRED"/>
            <tx:method name="transferAccounts" isolation="DEFAULT" propagation="REQUIRED" rollback-for="TransferException"/>
        </tx:attributes>
    </tx:advice>

    <!-- AspectJ的AOP配置 -->
    <aop:config>
        <!-- 这里和配置AspectJ时候一样,配置切入点表达式 -->
        <aop:pointcut expression="execution(* *..Service.*.*(..))" id="pointCut"/>

        <!-- pointcut-ref里面可以填写切入点表达式,也可以填写上一行代码的pointCut -->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pointCut"/>

    </aop:config>

</beans>

因为这里只是运用之前的AOP知识完成事务的管理,所以配置的具体流程就不再详细的说明。

总结: 
至此我们就完成了使用Spring(AspectJ)对事务的管理,当然我们这里都是设置了默认隔离级别,和REQUIRED事务传播行为,并且配置了一些对受查异常了Rollback操作,在具体的使用当中,我们应该灵活的使用这个隔离级别和事务传播行为!

版权声明:本文为博主原创文章,如需转载请表明出处。 https://blog.csdn.net/qq_39266910/article/details/78826171

原文地址:https://www.cnblogs.com/chengshun/p/9780765.html

时间: 2024-08-07 16:19:42

12 Spring框架 SpringDAO的事务管理的相关文章

Spring整合hibernate4:事务管理

Spring和Hibernate整合后,通过Hibernate API进行数据库操作时发现每次都要opensession,close,beginTransaction,commit,这些都是重复的工作,我们可以把事务管理部分交给spring框架完成. 配置事务(xml方式) 使用spring管理事务后在dao中不再需要调用beginTransaction和commit,也不需要调用session.close(),使用API  sessionFactory.getCurrentSession()来

全面分析 Spring 的编程式事务管理及声明式事务管理--转

开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本教程假定您已经掌握了 Java 基础知识,并对 Spring 有一定了解.您还需要具备基本的事务管理的知识,比如:事务的定义,隔离级别的概念,等等.本文将直接使用这些概念而不做详细解释.另外,您最好掌握数据库的基础知识,虽然这不是必须. 系统需求 要试验这份教程中的工具和示例,硬件配置需求为:至少带

分析 Spring 的编程式事务管理及声明式事务管理(转)

开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本教程假定您已经掌握了 Java 基础知识,并对 Spring 有一定了解.您还需要具备基本的事务管理的知识,比如:事务的定义,隔离级别的概念,等等.本文将直接使用这些概念而不做详细解释.另外,您最好掌握数据库的基础知识,虽然这不是必须. 系统需求 要试验这份教程中的工具和示例,硬件配置需求为:至少带

Spring 的编程式事务管理及声明式事务管理

本文将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. Spring 事务属性分析 事务管理对于企业应用而言至关重要.它保证了用户的每一次操作都是可靠的,即便出现了异常的访问情况,也不至于破坏后台数据的完整性.就像银行的自助取款机,通常都能正常为客户服务,但是也难免遇到操作过程中机器突然出故障的情况,此时,事务就必须确保出故障前对账户的操作不生效,就像用户刚才完全没有使用过取款机一样,以保

Spring多数据源分布式事务管理/springmvc+spring+atomikos[jta]+druid+mybatis

项目进行读写分离及分库分表,在一个业务中,在一个事务中处理时候将切换多个数据源,需要保证同一事务多个数据源数据的一致性.此处使用atomikos来实现:最后附源码: 1:spring3.0之后不再支持jtom[jta]了,第三方开源软件atomikos(http://www.atomikos.com/)来实现. 2:org.springframework.transaction.jta.JotmFactoryBean类,spring-tx-2.5.6.jar中有此类,spring-tx-3.0.

Spring Transaction + MyBatis SqlSession事务管理机制研究学习

线上的系统中,使用的是Spring+Mybatis+Mysql搭建的框架,由于客户需要,最近一直在对性能提升部分进行考虑,主要是涉及Mysql的一些重要参数的配置学习,以及Spring事务管理机制的学习,因为通过观察服务器日志,发现在这两部分的时候耗时比较严重,特别是进行mysql事务提交的时候,项目源码中使用了Spring的声明式事务,即通过@Transactional注解来控制事务的开启与提交,这两天看了一些关于Spring Transaction事务的一些文章,也debug了源码,总算有点

Spring的数据访问---------------事务管理

ThreadLocal ThreadLocal为每一个使用该变量的线程分配一个变量副本,所以每一个线程在改变自己的副本时,不会改变其他线程的副本信息.该类主要包含四个方法: public void set(Object obj) public Object get() public void remove() protected Object InitialValue() package thread; public class ThreadLocalTest { private static

[JavaEE - JPA] 3. Spring Framework中的事务管理

前文讨论了事务划分(Transaction Demarcation)在EJB中是如何实现的,本文继续介绍在Spring Framework中是如何完成事务划分的. 我们已经知道了当采用Container事务类型的时候,事务划分主要有以下两种方案(参考这里): 使用JTA接口在应用中编码完成显式划分 在容器的帮助下完成自动划分 在使用JavaEE的EJB规范时,这两种方案分别被实现为BMT以及CMT,关于BMT和CMT在上一篇文章中有比较详尽的讨论(参考这里). 那么对于Spring Framew

spring mvc + mybatis + spring aop声明式事务管理没有作用

在最近的一个项目中,采用springMVC.mybatis,发现一个很恼人的问题:事务管理不起作用!!网上查阅了大量的资料,尝试了各种解决办法,亦未能解决问题! spring版本:3.0.5 mybatis版本:3.2.2 1.applicationContext.xml配置: mvc + mybatis + spring aop声明式事务管理没有作用" title="spring mvc + mybatis + spring aop声明式事务管理没有作用">2.spr