zoukankan      html  css  js  c++  java
  • Spring事务管理

    事务
    什么是事务?事务是指的是逻辑上的一组操作,这次操作要么全部成功,要么全部失败。
    事务的特性:
    原子性是指事务是一个不可分割的工作单位,事务中的操作,要么全都发生,要么都不发生。
    一致性指事务前后数据的完整性必须保持一致。
    隔离性指多个用户并发访问数据库时,一个用户的事物不能被其他用户的事物所干扰,多个并发事务之间数据要相互隔离。
    持久性是指一个事务一旦被提交,它对数据库中的数据的改变就是永久性的,即使数据库发生故障也不应该对其有任何影响。 
    接口介绍
    spring事务管理高层抽象主要包括3个接口:
      PlatformTransactionManager 平台事务管理器;
      TransactionDefinition 事务定义信息(隔离、传播、超时、只读)
      TransactionStatus 事务具体运行状态
    spring为不同持久化框架提供了不同的PlatformTransactionManager接口实现。
    TransactionDefinition定义事务隔离级别,spring默认隔离级别和使用的数据库一样。mysql默认repeatable_read,oracle默认read_commited。
    TransactionDefinition定义事务传播行为,事务传播行为解决业务层方法之间的相互调用的问题。事务七种传播行为,分为三类:在同一事务、不在同一事务、事务嵌套(若bbb方法失败,aaa回滚到事务保存点或全回滚)。 
    转账环境的搭建
    spring支持两种方式事务管理。
    1、编程式的事务管理:
    在实际应用中很少很用,通过TransactionTemplate手动管理事务。
    2、使用XML配置声明式事务:
    开发中推荐使用(代码侵入性小),spring的声明式事务是通过AOP实现的。 
    创建数据表account
    CREATE TABLE `account` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(20) NOT NULL,
      `money` double DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    INSERT INTO `account` VALUES ('1', 'aaa', '1000');
    INSERT INTO `account` VALUES ('2', 'bbb', '1000');
    INSERT INTO `account` VALUES ('3', 'ccc', '1000');

    项目需要jar包、配置文件:

    jar包:
    com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar
    com.springsource.org.aopalliance-1.0.0.jar
    com.springsource.org.apache.commons.logging-1.1.1.jar
    com.springsource.org.apache.log4j-1.2.15.jar
    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
    mysql-connector-java-5.0.8-bin.jar
    spring-aop-3.2.0.RELEASE.jar
    spring-aspects-3.2.0.RELEASE.jar
    spring-beans-3.2.0.RELEASE.jar
    spring-context-3.2.0.RELEASE.jar
    spring-core-3.2.0.RELEASE.jar
    spring-expression-3.2.0.RELEASE.jar
    spring-jdbc-3.2.0.RELEASE.jar
    spring-test-3.2.0.RELEASE.jar
    spring-tx-3.2.0.RELEASE.jar
    核心配置文件:applicationContext.xml, log4j.properties, jdbc.properties
    jdbc.properties:
    jdbc.driverClass=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://192.168.216.101:3306/spring_transaction
    jdbc.username=user
    jdbc.password=123456
    log4j.properties:
    log4j.rootLogger=INFO,logfile,stdout           
    #log4j.logger.org.springframework.web.servlet=INFO,db 
    #log4j.logger.org.springframework.beans.factory.xml=INFO
    #log4j.logger.com.neam.stum.user=INFO,db 
    #log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    #log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    #log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p [%c] %X{remoteAddr}  %X{remotePort}  %X{remoteHost}  %X{remoteUser} operator:[u59D3u540D:%X{userName} u5DE5u53F7:%X{userId}] message:<%m>%n
    #write log into file
    log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.logfile.Threshold=warn
    log4j.appender.logfile.File=${webapp.root}\logs\Spring-transaction.log
    log4j.appender.logfile.DatePattern=.yyyy-MM-dd
    log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
    log4j.appender.logfile.layout.ConversionPattern=[SpringTransaction] %d{yyyy-MM-dd HH:mm:ss} %X{remoteAddr} %X{remotePort} %m %n
    #display in console
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Threshold=info
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=[SpringTransaction] %d{yyyy-MM-dd HH:mm:ss} %X{remoteAddr} %X{remotePort} %m %n 
    建maven项目。
    建包cn.it.spring.demo1,下面的文件除了配置文件全建在这个包里。
    面向接口开发,所以创建接口:AccountService。
    /**
    * 转账安全的业务层接口
    */
    public interface AccountService {
    /**
    * @param out :转出账号
    * @param in :转入账号
    * @param money :转账金额
    */
    public void transfer(String out,String in,Double money);
    }

    实现类AccountServiceImpl.java:

    //注入转账的DAO,因为要调用dao,所以要注入dao。提供set方法进行注入。在下面有set方法。而且在applicationContext.xml里的要配置注入。spring和junit4的整合包有了,所以在测试类里引入applicationContext就好了。
    private AccountDao accountDao;
    /**
    * @param out :转出账号
    * @param in :转入账号
    * @param money :转账金额
    */
    @Override
    public void transfer(String out, String in, final Double money) {
      accountDao.outMoney(out, money);
      //int i = 1/0;
      accountDao.inMoney(in, money);
    }
    public void setAccountDao(AccountDao accountDao) {
      this.accountDao = accountDao;
    }

    AccountDao.java:

    public interface AccountDao {
    /**
    * @param out :转出账号
    * @param money :转账金额
    */
    public void outMoney(String out,Double money);
    /**
    *
    * @param in :转入账号
    * @param money :转账金额
    */
    public void inMoney(String in,Double money);
    }

    实现类AccountDaoImpl.java:

    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {//继承JdbcDaoSupport就可以使用jdbc的模版了。继承了之后就可以查看相应的源代码。找到spring-jdbc-3.2.0.RELEASE.sources.jar(在spring-framework-3.2.0.RELEASE官方JAR包),里面可以看到提供了一个jdbc模板,下面有setJdbcTemplate,所在直接向dao注入模板就可以了。还有一种办法,里面有setDataSource设置了连接池,所以只要给dao连接池的话,它就会创建一个模板。所以可以直接向dao里注入连接池。因为只要给连接池,它就可以帮我们创建jdbc的模板。下面的配置有向dao注入连接池,只要注入连接池就有jdbc模板。
    /**
    * @param out :转出账号
    * @param money :转账金额
    */
    @Override
    public void outMoney(String out, Double money) {
      String sql = "update account set money = money-? where name = ?";
      this.getJdbcTemplate().update(sql, money, out);    //使用这个模板。后面两个是可变参数。
    }
    /**
    * @param in :转入账号
    * @param money :转账金额
    */
    @Override
    public void inMoney(String in, Double money) {
      String sql = "update account set money = money+? where name = ?";
      this.getJdbcTemplate().update(sql,money,in);
    }
    }

    上面这些类要用spring管理,所以要配置:

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

    下面的代码写在beans里。

    <!-- 引入外部的属性文件 -->
    <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>
    <!-- 配置业务层类 -->
    <bean id="accountService" class="com.zs.spring.demo1.AccountServiceImpl">
    <property name="accountDao" ref="accountDao" />
    </bean>
    <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
    <bean id="accountDao" class="com.zs.spring.demo1.AccountDaoImpl">
    <property name="dataSource" ref="dataSource" />    //在dao里注入连接池。
    </bean>

    测试类 TransactionTest :

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext1.xml")
    public class TransactionTest {
      @Resource(name="accountService")//name就是applicationContext里配置的名字。
      private AccountService accountService;
      @Test
      public void demo1(){
        accountService.transfer("aaa", "bbb", 200d);
      }
    }

    这时环境就搭好了,进行测试查看数据库,然后把AccountServiceImpl两句转账语句间加int i = 8/0进行测试。发现有事务问题。 

    编程式事务管理
    spring为了简化编写代码,它给我们提供了事务管理的模板,叫TransactionTemplate,需要在哪个地方使用事务,就在哪个地方注入这个模板。
    打开spring的API,找到PlatformTransactionManager接口,实现类有很多,比如HibernateTransactionManager, DataSourceTransactionManager等,这里使用第二个,首先配置事务管理器,在applicationContext.xml里配置:
    <!-- ==================================1.编程式的事务管理。因使用事务的时候要自己手动写代码。哪个类需要事务管理呢,业务层的类,所以在业务层的类中进行模板的注入。=============================================== -->
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 使用jdbc的方式进行事务管理,所以bean里需要配置属性。因为jdbc进行事务管理是获得到连接对象connection.setAutoCommit(false);就不自动提交了,执行完代码的commit,如果发生异常就rollback。谁能获取到连接呢,事务连接池,所以在事务管理器的模板中注入连接池对象,因为事务管理模板是真正进行事务管理的类,连接池可以获得到连接对象: -->
    <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 但是直接进行事务管理的话代码比较繁琐,所以spring为了简化我们事务管理的代码,它给我们提供了事务管理的模板,叫:TransactionTemplate所以定义一个事务管理模板: -->
    <!-- 配置事务管理的模板:Spring为了简化事务管理的代码而提供的类。就像它还给我们提供过jdbc的模板。 -->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <!-- 这个模板是简化事务管理的,真正进行事务管理的,是transactionManager,所以注入: -->
    <property name="transactionManager" ref="transactionManager" />
    </bean>

    业务类AccountServiceImpl中注入,如果要注入的话,还有提供set方法:

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

    这里提供set方法了,配置文件中需要注入事务管理的模板,哪个类需要事务管理,就在哪个类中注入模板类:

    <bean id="accountService" class="cn.it.spring.demo1.AccountServiceImpl">
    <property name="accountDao" ref="accountDao" />
    <!-- 注入事务管理的模板 -->
    <property name="transactionTemplate" ref="transactionTemplate" />
    </bean>

    AccountServiceImpl中的那两条转账语句就在一个事务里的,使用模板中的方法:

    public void transfer(final String out, final String in, final Double money) {
    // accountDao.outMoney(out, money);
    // int i = 1/0;
    // accountDao.inMoney(in, money);
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {//调用这个方法时看到它的参数要传一个TransactionCallback的接口,可以自己写一个类实现它,也可以直接使用匿名内部类的形式,new完之后里面有个方法doInTransactionWithoutResult。
      @Override
      protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {//方法里面要传一个对象,就是事务的状态,匿名内部类中使用了外部类中的变量,所以要把外部变量变成final
        accountDao.outMoney(out, money);
        int i = 1/0;
        accountDao.inMoney(in, money);
      }
    });
    }

    测试,发现事务回滚成功。 

    声明式事务管理方式一:基于TransactionProxyFactoryBean的方式

    编程式事务管理需要手动改service代码,开发中不好用。
     
    声明式事务管理基于AOP的思想完成的,也就是在转账的两句代码之前做一些事,之后做一些事。这就是AOP思想。
    建包cn.it.spring.demo2,把上面的代码复制并恢复。
    新建配置文件applicationContext2.xml。配置也还原。
    因为基于aop,所以这里要引入aop包(和spring整合)和aop联盟(aop是一个联盟提出的)包:com.springsource.org.aopalliance-1.0.0.jar, spring-aop-3.2.0.RELEASE.jar
    applicationContext2.xml:
    <!-- 配置业务层类 -->
    <bean id="accountService" class="cn.it.spring.demo2.AccountServiceImpl">
    <property name="accountDao" ref="accountDao" />
    </bean>
    <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
    <bean id="accountDao" class="cn.it.spring.demo2.AccountDaoImpl">
    <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- ==================================2.使用XML配置声明式的事务管理(原始方式)=============================================== -->
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 配置业务层的代理 -->
    <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <!-- 配置目标对象 -->
    <property name="target" ref="accountService" />
    <!-- 注入事务管理器 -->
    <property name="transactionManager" ref="transactionManager"></property>
    <!-- 注入事务的属性 -->
    <property name="transactionAttributes">
    <props>
    <!--
    prop的格式:
    * PROPAGATION :事务的传播行为
    * ISOTATION :事务的隔离级别
    * readOnly :只读
    * -EXCEPTION :发生哪些异常回滚事务
    * +EXCEPTION :发生哪些异常不回滚事务
    -->
    <prop key="transfer">PROPAGATION_REQUIRED</prop>
    <!-- <prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop> -->
    <!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
    </props>
    </property>
    </bean>

     TransactionTest:

    // @Resource(name = "accountService") // name就是applicationContext里配置的名字。
    @Resource(name = "accountServiceProxy")//这里要用代理。
    private AccountService accountService;
    @Test
    public void demo1() {
    accountService.transfer("aaa", "bbb", 200d);
    }

     AccountServiceImpl:

    // 注入转账的DAO,因为要调用dao,所以要注入dao。提供set方法进行注入。在下面有set方法。而且在applicationContext.xml里的要配置注入。spring和junit4的整合包有了,所以在测试类里引入applicationContext就好了。
    private AccountDao accountDao;
    public void transfer(final String out, final String in, final Double money) {
    accountDao.outMoney(out, money);
    int i = 1/0;
    accountDao.inMoney(in, money);
    }
    public void setAccountDao(AccountDao accountDao) {
    this.accountDao = accountDao;
    }
     声明式事务管理方式二:基于AspectJ的XML方式

    上面的原始方式不经常使用,因为需要为每一个进行事务管理的类都需要配置一个transactionProxyFactoryBean。只能对一个目标进行增强。

    所以要用另一种AspectJ的XML方式:
    aspectJ是为是简化AOP编程,开源的第三方AOP开发框架。
    aspectJ相应开发包:com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar(aspectJ包),spring-aspects-3.2.0.RELEASE.jar(spring整合aspectJ的包)。
    建包cn.it.spring.demo3
    复制applicationContext3.xml:
    <!-- 配置业务层类 -->
    <bean id="accountService" class="cn.it.spring.demo3.AccountServiceImpl">
    <property name="accountDao" ref="accountDao" />
    </bean>
    <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
    <bean id="accountDao" class="cn.it.spring.demo3.AccountDaoImpl">
    <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- ==================================3.使用XML配置声明式的事务管理,基于tx/aop=============================================== -->
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 配置事务的通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
    <!--
    propagation :事务传播行为
    isolation :事务的隔离级别
    read-only :只读
    rollback-for:发生哪些异常回滚
    no-rollback-for :发生哪些异常不回滚
    timeout :过期信息
    -->
    <tx:method name="transfer" propagation="REQUIRED"/>
    </tx:attributes>
    </tx:advice> 
    <!-- 配置切面 -->
    <aop:config>
    <!-- 配置切入点 -->
    <aop:pointcut expression="execution(* cn.it.spring.demo3.AccountService+.*(..))" id="pointcut1"/>
    <!-- 配置切面 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
    </aop:config>

    不管用哪种方式,事务管理器是必须的。

    这种代理方式是自动代理,就是在类的生成过程中,类的本身就是一个代理对象。而不需要注入代理对象。因为在产生的过程中就已经对这个类增强了。
     TransactionTest:
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext3.xml")
    public class TransactionTest {
      @Resource(name = "accountService") // name就是applicationContext里配置的名字。
      private AccountService accountService;
      @Test
      public void demo1() {
        accountService.transfer("aaa", "bbb", 200d);
      }
    }
     声明式事务管理方式三:基于注解的方式
    建包cn.it.spring.demo4
    复制applicationContext4.xml:
    也要配事务管理器,因为它才是真正事务管理的类。里面注入连接池。开启注解事务后就能使用注解了。注解驱动标签,标签里配置事务管理器对象。然后在类上添加注解。注解里也有属性,像隔离级别,传播行为,只读,等等。
    <!-- 配置业务层类 -->
    <bean id="accountService" class="cn.it.spring.demo4.AccountServiceImpl">
    <property name="accountDao" ref="accountDao" />
    </bean>
    <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
    <bean id="accountDao" class="cn.it.spring.demo4.AccountDaoImpl">
    <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- ==================================4.使用注解配置声明式事务============================================ -->
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 开启注解事务 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

     AccountServiceImpl

    /**
    *@Transactional中的的属性
    *propagation :事务的传播行为
    *isolation :事务的隔离级别
    *readOnly :只读
    *rollbackFor :发生哪些异常回滚
    *noRollbackFor :发生哪些异常不回滚
    *rollbackForClassName 根据异常类名回滚
    */
    //@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)
    @Transactional
    public class AccountServiceImpl implements AccountService {
      // 注入转账的DAO,因为要调用dao,所以要注入dao。提供set方法进行注入。在下面有set方法。而且在applicationContext.xml里的要配置注入。spring和junit4的整合包有了,所以在测试类里引入applicationContext就好了。
      private AccountDao accountDao;
      /**
      * @param out
      *            :转出账号
      * @param in
      *            :转入账号
      * @param money
      *            :转账金额
      */
      public void transfer(final String out, final String in, final Double money) {
        accountDao.outMoney(out, money);
        // int i = 1/0;
        accountDao.inMoney(in, money);
      }
      public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
      }
    }

     总结 

    Spring将事务管理分成了两类:
      * 编程式事务管理
        * 手动编写代码进行事务管理.(很少使用)
      * 声明式事务管理:
        * 基于TransactionProxyFactoryBean的方式.(很少使用)
          * 需要为每个进行事务管理的类,配置一个TransactionProxyFactoryBean进行增强.
        * 基于AspectJ的XML方式.(经常使用)
          * 一旦配置好之后,类上不需要添加任何东西
        * 基于注解方式.(经常使用)
          * 配置简单,需要在业务层上添加一个@Transactional的注解.
     
  • 相关阅读:
    mysql免安装使用(win7 64位系统)
    [NOIP2011]瑞士轮
    [NOIP2011]数的划分
    [洛谷2994]超级弹珠
    并查集
    [codevs1073]家族
    快速幂
    [NOI2002]银河英雄传说
    [NOIP2007]矩阵取数游戏
    [洛谷2415]集合求和
  • 原文地址:https://www.cnblogs.com/yigechengxuyuan/p/8397008.html
Copyright © 2011-2022 走看看