初见Spring之事务管理

初见Spring之事务管理

    在很多业务的场景下,需要对数据库进行操作,在对访问数据库的时候,尤其是对数据的内容进行增删改操作时,引入事务就是必要的了。在通常情况下利用Java自带的JDBC操作完成事务管理时,需要在每个访问数据库的方法中都引入事务的支持。为了避免这种重复性的代码,更加专注于业务逻辑的设计,将AOP的思想引入到事务管理中,将重复的事务管理的代码抽取出来放在一个切面当中形成事务通知,在必要的时候可以将切面中的事务通知织入到目标方法。Spring提供了对AOP编程的强大支持,并且提供对了JDBC的支持,那么Spring自带会在AOP基础上提过了对事务的支持。

    Spring可以通过XML和注解的方式完成AOP编程,那么基于AOP的事务管理同样也可以通过XML和注解的方式进行配置。本文主要介绍Spring中基于XML方式和基于注解方式的事务管理。

一丶基于XML的事务管理

    本文借助一个经典的银行转账案例来描述Spring 中事务管理的基本内容

1.设计一个Account类,代表银行账户:

public class Account {
    //账户名称
    publicString name;
    //账户余额
    publicdouble money;
    publicAccount(){
      
    }
    publicAccount(String name ,double money){
       this.name= name;
       this.money=money;
    }
    publicString getName() {
       returnname;
    }
    publicvoid setName(String name) {
       this.name= name;
    }
    publicdouble getMoney() {
       returnmoney;
    }
    publicvoid setMoney(double money) {
       this.money= money;
    }
}

2.Dao层类设计,用来访问数据库,在该类中设计了一个transfer方法模拟转账操作,在实际的生活中,转账操作应该具有原子性。在程序设计转账操作时一般分为两部分进行,第一个步骤是转出用户金额减小,第二个步骤是转入账户金额增加。这样有可能第一个步骤执行成功,第二个步骤执行失败,破坏了转账操作原子性特点,为了解决这种情况对转账操作引入了事务,保证原子性,那么两个步骤同时成功,要么两个步骤同时失败。

AccountDaoImpl类设计如下:

  @Repository("accountDao")
public class AccountDaoImpl implements AccountDao{
    @Resource(name="jdbcTemplate")
    JdbcTemplate JdbcTemplate;
    /**
     * 创建Account表
     */
    @Override
    publicvoid createAccountTable() {
       // TODO Auto-generated method stub
           String sql = "create tableaccount("
           + "id int primary keyauto_increment,"
           + "name char(50),"
           + "money double)";
           JdbcTemplate.execute(sql);
    }
    /**
     * 向数据表中插入一行记录
     * @param account 插入的账户实体
     */
    @Override
    publicvoid insertAccount(Account account){
       // TODO Auto-generated method stub
       String sql ="insert intoaccount(name,money) value(?,?)";
       JdbcTemplate.update(sql,account.name,account.money);
    }
    /**
     * 转账操作
     * @param outName 转出账户名字
     * @param inName 转入账户名字
     * @param 转账金额
     */
    publicvoid transfer(String outName,StringinName,double money){
       String sqlIn = "update account setmoney =money+? where name=?";
       JdbcTemplate.update(sqlIn,money,inName);
       /*用除0的操作模拟,转账异常。假如没有引入事务,那么第一条语句会执行成功,账户金额增加,第二条语句执行失败金额不变。若引入事务那么两个语句同时失效,两方金额都不变换
*/
       inti =1/0;
       String sqlOut ="update account setmoney=money-? where name=?";
       JdbcTemplate.update(sqlOut,money,outName);
    }

3.Spring 配置文件,基于XML方式的事务配置重点在Spring配置文件的书写。为完成上述案例,必要要配置Spring JDBC的支持,包括DataSource和JDBC Template。配置事务通知的第一步是需要配置事务管理器,事务管理器依赖DataSource。第二部是配置事务通知,在里面指定,生效方法名称,事务隔离级别以及事务传播属性和只读属性等。最后一步就是配置切面,将事务通知和切面整合起来,通过配置切点织入到目标方法中。Spring配置文件如下:

<?xmlversion="1.0" encoding="UTF-8"?>
<beansxmlns="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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
    http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
    http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-4.3.xsd
    http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
      <!-- 指定需要扫描的包,使注解生效 -->
      <context:component-scan base-package="jdbc"/>
      <context:component-scan base-package="AspectJ"/>
      <context:component-scan base-package="transtaction"/>
      <!--AOP注解生效-->
      <aop:aspectj-autoproxy />
      <!-- 1配置数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
       <!--数据库驱动 -->
       <property name="driverClassName"value="com.mysql.jdbc.Driver" />
       <!--连接数据库的url -->
       <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
       <!--连接数据库的用户名 -->
       <property name="username"value="root" />
       <!--连接数据库的密码 -->
       <property name="password"value="123456" />
    </bean>
   
    <!-- 2配置JDBC模板 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
       <!-- 默认必须使用数据源 -->
       <property name="dataSource"ref="dataSource" />
    </bean>
    <!-- 3配置事务管理器-->
    <bean id="transactionManeger"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource"ref="dataSource"></property>
    </bean>
    <!-- 4配置事务通知 -->
    <tx:advice id="txAdvice"transaction-manager="transactionManeger">
       <tx:attributes>
           <!-- name:*表示任意方法名称 -->
           <tx:method name="*"propagation="REQUIRED"
                           isolation="DEFAULT"read-only="false" />
       </tx:attributes>
    </tx:advice>
    <!-- 5配置切面-->
    <aop:config>
       <!--配置切面的织入点-->
       <aop:pointcut expression="execution(*transtaction.*.*(..))" id="pointCut"/>
       <!--将事务和切面进行整合-->
       <aop:advisor advice-ref="txAdvice"pointcut-ref="pointCut"/>
    </aop:config>
</beans>

4.测试代码:测试代码引入Junit4这个单元测试框架,测试了建表,插入和转账等操作。

public class MainTransaction{
    @Test
    publicvoid createTest(){
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       AccountDao accountDao = (AccountDao)context.getBean("accountDao");
       accountDao.createAccountTable();
    }
    @Test
    publicvoid insertTest(){
       Account account = new Account("SmartMeng",2000);
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       AccountDao accountDao = (AccountDao)context.getBean("accountDao");
       accountDao.insertAccount(account);
    }
    @Test
    publicvoid insetTransfer(){
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       AccountDao accountDao = (AccountDao)context.getBean("accountDao");
      
       accountDao.transfer("SmartMeng","SmartTu", 1000);
    }
}

5.Transfer测试结果:

                   

                  图1 Transfer操作之前数据库情况


                 

                     图2 转账中发现的除0异常


  图3 转账之后数据库情况

  可以看到,通对Transfer方法引入了事务后,在Transfer执行过程中即使发生了异常,第二条sql语句执行失败,也没有只修改一方账户金额。因为事务发生回滚,保证了transfer操作的原子性,要么成功要么同时失败。

二丶基于注解的事务管理

    Spring提供了基于注解的方式配置事务,显然基于注解的方式配置事务可以减少很多xml代码的书写工作,基于注解的事务配置十分常用。

    基于注解配置的事务管理,主要是修改两个地方:

1.第一个地方就是Spring配置文件,删除了事务通知的xml文件以及切面xml文件的书写,只是保留了TransactionManager的步骤,同时增加了注解事务的启动项。修改后Spring文件配置如下:

  <?xml version="1.0" encoding="UTF-8"?>
<beansxmlns="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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
    http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
    http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-4.3.xsd
    http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
      <!-- 指定需要扫描的包,使注解生效 -->
      <context:component-scan base-package="jdbc"/>
      <context:component-scan base-package="AspectJ"/>
      <context:component-scan base-package="transaction"/>
      <!--开启注解配置事务驱动-->
      <tx:annotation-driven transaction-manager="transactionManager"/>
      <!--AOP注解生效-->
      <aop:aspectj-autoproxy />
      <!-- 1配置数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
       <!--数据库驱动 -->
       <property name="driverClassName"value="com.mysql.jdbc.Driver" />
       <!--连接数据库的url -->
       <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
       <!--连接数据库的用户名 -->
       <property name="username"value="root" />
       <!--连接数据库的密码 -->
       <property name="password"value="123456" />
    </bean>
   
    <!-- 2配置JDBC模板 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
       <!-- 默认必须使用数据源 -->
       <property name="dataSource"ref="dataSource" />
    </bean>
    <!-- 3配置事务管理器-->
    <bean id="transactionManeger"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource"ref="dataSource"></property>
    </bean>
   
</beans>

 2.第二个地方便是在需要引入事务的方法伤添加Transactional注解,依据上述例子的需求,我们需要在transfer方法中引入注解支持,所transfer方法修改如下(仅仅添加一行注解即可):  

/**
     * 转账操作
     * @param outName 转出账户名字
     * @param inName 转入账户名字
     * @param 转账金额
     */
   @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)
    publicvoid transfer(String outName,StringinName,double money){
       String sqlIn = "update account setmoney =money+? where name=?";
       JdbcTemplate.update(sqlIn,money,inName);
       inti =1/0;
       String sqlOut ="update account setmoney=money-? where name=?";
       JdbcTemplate.update(sqlOut,money,outName);
    }

可以看到Transactional注解中的属性和xml文件中的事务通知的属性是一一对应的,name属性被省略了,因为我们直接将注解配置到需要引入事务的方法上了,自然切面织入的过程也可以省略了,基于注解的事务配置显然比基于xml方法的事务配置简单了不少。测试方法和测试结果和基于xml方式的注解配置一样。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值