Spring4笔记9--Spring的事务管理(AOP应用的例子)

Spring的事务管理:

  事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。  
  在 Spring 中通常可以通过以下三种方式来实现对事务的管理:
    (1)使用 Spring 的事务代理工厂管理事务
    (2)使用 Spring 的事务注解管理事务
    (3)使用 AspectJ 的 AOP 配置管理事务

  Spring事务管理API:

    Spring 的事务管理,主要用到两个事务相关的接口。

    (1)事务管理器接口
        事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。查看 SpringAPI 帮助文档:Spring 框架解压目录下的docs/javadoc-api/index.html。

Modifier and TypeMethod and Description
void commit(TransactionStatus status)
Commit the given transaction, with regard to its status.
TransactionStatus getTransaction(TransactionDefinition definition)
Return a currently active transaction or create a new one, according to the specified propagation behavior.
void rollback(TransactionStatus status)
Perform a rollback of the given transaction.

      A、常用的两个实现类
        PlatformTransactionManager 接口有两个常用的实现类:  
          DataSourceTransactionManager:使用 JDBC 或 iBatis  进行持久化数据时使用。  
          HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
      B、Spring 的回滚方式
        Spring 事务的默认回滚方式是:发生运行时异常时回滚,发生受查异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式

    (2)事务定义接口:
      事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别事务传播行为事务默认超时时限,及对它们的操作

      A、定义了五个事务隔离级别常量:
        这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
          DEFAULT:采用DB默认的事务隔离级别。MySql的默认为REPEATABLE_READ;  Oracle默认为 READ_COMMITTED。
          READ_UNCOMMITTED:读未提交。未解决任何并发问题。
          READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。  
          REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读  
          SERIALIZABLE:串行化。不存在并发问题。

      B、定义了七个事务传播行为常量
        所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。  
事务传播行为常量都是以 PROPAGATION_  开头,形如 PROPAGATION_XXX。  
          REQUIRED:指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是Spring 默认的事务传播行为。  
          如该传播行为加在 doOther()方法上。若 doSome()方法在执行时就是在事务内的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。

          SUPPORTS:指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。 

          MANDATORY:指定的方法必须在当前事务内执行,若当前没有事务,则直接抛出异常。

          REQUIRES_NEW:总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

          NOT_SUPPORTED:指定的方法不能在事务环境中执行,若当前存在事务,就将当前事务挂起。

          NEVER:指定的方法不能在事务环境下执行,若当前存在事务,就直接抛出异常。

          NESTED:指定的方法必须在事务内执行。若当前存在事务,则在嵌套事务内执行;若当前没有事务,则创建一个新事务。

      C、定义了默认事务超时时限
        常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,及不支持事务超时时限设置的 none 值。
        注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。

 

  Spring事务代码详解:

    要求:实现模拟购买股票。存在两个实体:银行账户 Account 与股票账户 Stock。当要购买股票时,需要从 Account 中扣除相应金额的存款,然后在 Stock 中增加相应的股票数量。而在这个过程中,可能会抛出一个用户自定义的异常。异常的抛出,将会使两个操作回滚。

    Step1:创建数据库表 account、stock
    Step2:创建实体类 Account 与 Stock (略)
    Step3:定义 Dao 接口  IAccountDao 与 IStockDao

1 package com.tongji.dao;
2 
3 public interface IAccountDao {
4 
5     void insertAccount(String aname, double money);
6 
7     void updateAccount(String aname, double money, boolean isBuy);
8 
9 }
1 package com.tongji.dao;
2 
3 public interface IStockDao {
4 
5     void insertStock(String sname, int amount);
6 
7     void updateStock(String sname, int amount, boolean isBuy);
8 
9 }

    Step4:定义 Dao 实现类 AccountDaoImpl 与 StockDaoImpl

 1 package com.tongji.dao;
 2 
 3 import org.springframework.jdbc.core.support.JdbcDaoSupport;
 4 
 5 public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
 6 
 7     @Override
 8     public void insertAccount(String aname, double money) {
 9         String sql = "insert into account(aname, balance) values(?,?)";
10         this.getJdbcTemplate().update(sql, aname, money);
11     }
12 
13     @Override
14     public void updateAccount(String aname, double money, boolean isBuy) {
15         String sql = "update account set balance=balance+? where aname=?";
16         if (isBuy) {
17             sql = "update account set balance=balance-? where aname=?";
18         }
19         this.getJdbcTemplate().update(sql, money, aname);
20         
21     }
22 
23 }
 1 package com.tongji.dao;
 2 
 3 import org.springframework.jdbc.core.support.JdbcDaoSupport;
 4 
 5 public class StockDaoImpl extends JdbcDaoSupport implements IStockDao {
 6 
 7     @Override
 8     public void insertStock(String sname, int amount) {
 9         String sql = "insert into stock(sname, count) values (?,?)";
10         this.getJdbcTemplate().update(sql , sname, amount);
11     }
12 
13     @Override
14     public void updateStock(String sname, int amount, boolean isBuy) {
15         //isBuy为true,则表示购买股票,此时应增加股票账户中的股票数量
16         String sql = "update stock set count=count-? where sname=?";
17         if (isBuy) {
18             sql = "update stock set count=count+? where sname=?";
19         }
20         this.getJdbcTemplate().update(sql, amount, sname);
21     }
22 
23 }

    Step5:定义异常类 StockException

 1 package com.tongji.beans;
 2 
 3 public class StockException extends Exception {
 4     private static final long serialVersionUID = 5377570098437361228L;
 5 
 6     public StockException() {
 7         super();
 8     }
 9 
10     public StockException(String message) {
11         super(message);
12     }
13     
14 }

    Step6:定义 Service 接口 IStockProcessService

1 package com.tongji.service;
2 
3 public interface IStockProcessService {
4     void openAccount(String aname, double money);
5     void openStock(String sname, int amount);
6     void buyStock(String aname, double money, String sname, int amount);
7 }

    Step7:定义 service 的实现类 StockProcessServiceImpl

 1 package com.tongji.service;
 2 
 3 import org.springframework.transaction.annotation.Isolation;
 4 import org.springframework.transaction.annotation.Propagation;
 5 import org.springframework.transaction.annotation.Transactional;
 6 
 7 import com.tongji.beans.StockException;
 8 import com.tongji.dao.IAccountDao;
 9 import com.tongji.dao.IStockDao;
10 
11 public class StockProcessServiceImpl implements IStockProcessService{
12      private IAccountDao accountDao;
13      private IStockDao stockDao;
14     
15     public void setAccountDao(IAccountDao accountDao) {
16         this.accountDao = accountDao;
17     }
18 
19     public void setStockDao(IStockDao stockDao) {
20         this.stockDao = stockDao;
21     }
22 
23     @Override
24     public void openAccount(String aname, double money) {
25         accountDao.insertAccount(aname, money);
26     }
27 
28     @Override
29     public void openStock(String sname, int amount) {
30         stockDao.insertStock(sname, amount);
31     }
32 
33     @Override
34     public void buyStock(String aname, double money, String sname, int amount) throws StockException {
35         boolean isBuy = true;
36         accountDao.updateAccount(aname, money, isBuy);
37         //故意抛出异常
38         if (true) {
39             throw new StockException("购买股票异常");
40         }
41         stockDao.updateStock(sname, amount, isBuy);
42     }
43 
44 }

    Step8:定义Spring 配置文件 

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
 5         http://www.springframework.org/schema/beans 
 6         http://www.springframework.org/schema/beans/spring-beans.xsd
 7         http://www.springframework.org/schema/context 
 8         http://www.springframework.org/schema/context/spring-context.xsd">
 9     
10     <!-- 注册数据源:C3P0数据源 -->
11     <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
12         <property name="driverClass" value="${jdbc.driverClass}" />
13         <property name="jdbcUrl" value="${jdbc.url}" />
14         <property name="user" value="${jdbc.user}" />
15         <property name="password" value="${jdbc.password}" />
16     </bean>
17     
18     <!-- 注册JDBC属性文件 -->
19     <context:property-placeholder location="classpath:jdbc.properties"/>
20     
21     <!-- 注册Dao -->
22     <bean id="accountDao" class="com.tongji.dao.AccountDaoImpl">
23         <property name="dataSource" ref="myDataSource"/>
24     </bean>
25     <bean id="stockDao" class="com.tongji.dao.StockDaoImpl">
26         <property name="dataSource" ref="myDataSource"/>
27     </bean>
28     <!-- 注册Service -->
29     <bean id="stockService" class="com.tongji.service.StockProcessServiceImpl">
30         <property name="accountDao" ref="accountDao"/>
31         <property name="stockDao" ref="stockDao"/>
32     </bean>    
33 </beans>

    Step9:测试类

 1 package com.tongji.test;
 2 
 3 import org.junit.Before;
 4 import org.junit.Test;
 5 import org.springframework.context.ApplicationContext;
 6 import org.springframework.context.support.ClassPathXmlApplicationContext;
 7 
 8 import com.tongji.service.IStockProcessService;
 9 
10 public class MyTest {
11     
12     private IStockProcessService service;
13 
14     @Before
15     public void before() {
16         //创建容器
17         @SuppressWarnings("resource")
18         ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
19         service = (IStockProcessService) ac.getBean("stockService");
20     }
21     
22     @Test
23     public void testOpen() {
24         service.openAccount("张三", 10000);
25         service.openStock("华为", 5);
26     }
27     
28     @Test
29     public void testBuyStock() {
30         service.buyStock("张三", 2000, "华为", 5);
31     }
32     
33 }

    此配置文件没有采用事务管理,所以购买股票的时候,出现了数据库中账户金额减少了,但是股票数目没有增加的不一致情况。

  

  使用 Spring 的事务代理工厂管理事务:

    该方式是,需要为目标类,即 Service 的实现类创建事务代理。事务代理使用的类是TransactionProxyFactoryBean,该类需要初始化如下一些属性:
      (1)transactionManager:事务管理器
      (2)target:目标对象,即 Service 实现类对象
      (3)transactionAttributes:事务属性设置
      对于 XML 配置代理方式实现事务管理时,受查异常的回滚方式,程序员可以通过以下方式进行设置:通过“-异常”方式,可使发生指定的异常时事务回滚;通过“+异常”方式,可使发生指定的异常时事务提交。

    修改Spring配置文件:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
 5         http://www.springframework.org/schema/beans 
 6         http://www.springframework.org/schema/beans/spring-beans.xsd
 7         http://www.springframework.org/schema/context 
 8         http://www.springframework.org/schema/context/spring-context.xsd">
 9     
10     <!-- 注册数据源:C3P0数据源 -->
11     <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
12         <property name="driverClass" value="${jdbc.driverClass}" />
13         <property name="jdbcUrl" value="${jdbc.url}" />
14         <property name="user" value="${jdbc.user}" />
15         <property name="password" value="${jdbc.password}" />
16     </bean>
17     
18     <!-- 注册JDBC属性文件 -->
19     <context:property-placeholder location="classpath:jdbc.properties"/>
20     
21     <!-- 注册Dao -->
22     <bean id="accountDao" class="com.tongji.dao.AccountDaoImpl">
23         <property name="dataSource" ref="myDataSource"/>
24     </bean>
25     <bean id="stockDao" class="com.tongji.dao.StockDaoImpl">
26         <property name="dataSource" ref="myDataSource"/>
27     </bean>
28     <!-- 注册Service -->
29     <bean id="stockService" class="com.tongji.service.StockProcessServiceImpl">
30         <property name="accountDao" ref="accountDao"/>
31         <property name="stockDao" ref="stockDao"/>
32     </bean>    
33     
34     <!-- 事务 -->
35     <!-- 注册事务管理器 -->
36     <bean id="myTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
37         <property name="dataSource" ref="myDataSource"/>
38     </bean>
39     <!-- 生成事务代理 -->
40     <bean id="stockServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
41         <property name="transactionManager" ref="myTxManager"/>
42         <property name="target" ref="stockService"/>
43         <property name="transactionAttributes">
44             <props>
45                 <prop key="open*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
46                 <prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-StockException</prop>
47             </props>
48         </property>
49     </bean>
50 </beans>

    由于本项目使用的是 JDBC 进行持久化,所以使用 DataSourceTransactionManager 类作为事务管理器。

    修改测试类:

     public void before() {
           //创建容器
           @SuppressWarnings("resource")
           ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
           service = (IStockProcessService) ac.getBean("stockServiceProxy");
       }

 

  使用 Spring 的事务注解管理事务:
    通过@Transactional 注解方式,也可将事务织入到相应方法中。而使用注解方式,只需在配置文件中加入一个 tx 标签,以告诉 spring 使用注解来完成事务的织入。该标签只需指定一个属性,事务管理器。

    修改Spring配置文件:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4         xmlns:context="http://www.springframework.org/schema/context"
 5         xmlns:aop="http://www.springframework.org/schema/aop"
 6         xmlns:tx="http://www.springframework.org/schema/tx" 
 7         xsi:schemaLocation="
 8         http://www.springframework.org/schema/beans 
 9         http://www.springframework.org/schema/beans/spring-beans.xsd
10         http://www.springframework.org/schema/context 
11         http://www.springframework.org/schema/context/spring-context.xsd
12         http://www.springframework.org/schema/tx 
13         http://www.springframework.org/schema/tx/spring-tx.xsd
14         http://www.springframework.org/schema/aop 
15         http://www.springframework.org/schema/aop/spring-aop.xsd">
16     
17     <!-- 注册数据源:C3P0数据源 -->
18     <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
19         <property name="driverClass" value="${jdbc.driverClass}" />
20         <property name="jdbcUrl" value="${jdbc.url}" />
21         <property name="user" value="${jdbc.user}" />
22         <property name="password" value="${jdbc.password}" />
23     </bean>
24     
25     <!-- 注册JDBC属性文件 -->
26     <context:property-placeholder location="classpath:jdbc.properties"/>
27     
28     <!-- 注册Dao -->
29     <bean id="accountDao" class="com.tongji.dao.AccountDaoImpl">
30         <property name="dataSource" ref="myDataSource"/>
31     </bean>
32     <bean id="stockDao" class="com.tongji.dao.StockDaoImpl">
33         <property name="dataSource" ref="myDataSource"/>
34     </bean>
35     <!-- 注册Service -->
36     <bean id="stockService" class="com.tongji.service.StockProcessServiceImpl">
37         <property name="accountDao" ref="accountDao"/>
38         <property name="stockDao" ref="stockDao"/>
39     </bean>    
40     
41     <!-- 事务 -->
42     <!-- 注册事务管理器 -->
43     <bean id="myTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
44         <property name="dataSource" ref="myDataSource"/>
45     </bean>
46     <!-- 开启注解驱动 -->
47     <tx:annotation-driven transaction-manager="myTxManager"/>
48 </beans>

    修改 service 的实现类 StockProcessServiceImpl:

 1 package com.tongji.service;
 2 
 3 import org.springframework.transaction.annotation.Isolation;
 4 import org.springframework.transaction.annotation.Propagation;
 5 import org.springframework.transaction.annotation.Transactional;
 6 
 7 import com.tongji.beans.StockException;
 8 import com.tongji.dao.IAccountDao;
 9 import com.tongji.dao.IStockDao;
10 
11 public class StockProcessServiceImpl implements IStockProcessService{
12      private IAccountDao accountDao;
13      private IStockDao stockDao;
14     
15     public void setAccountDao(IAccountDao accountDao) {
16         this.accountDao = accountDao;
17     }
18 
19     public void setStockDao(IStockDao stockDao) {
20         this.stockDao = stockDao;
21     }
22 
23     @Override
24     @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED)
25     public void openAccount(String aname, double money) {
26         accountDao.insertAccount(aname, money);
27     }
28 
29     @Override
30     @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED)
31     public void openStock(String sname, int amount) {
32         stockDao.insertStock(sname, amount);
33     }
34 
35     @Override
36     @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, rollbackFor=StockException.class)
37     public void buyStock(String aname, double money, String sname, int amount) throws StockException {
38         boolean isBuy = true;
39         accountDao.updateAccount(aname, money, isBuy);
40         if (true) {
41             throw new StockException("购买股票异常");
42         }
43         stockDao.updateStock(sname, amount, isBuy);
44     }
45 
46 }

    @Transactional 的所有可选属性如下所示:
      propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED。
      isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为Isolation.DEFAULT。
      readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。
      timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。
      rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
      rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
      noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
      noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
     需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。  
    若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

  使用 AspectJ 的 AOP 配置管理事务(重点):

    使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类较多,配置文件会变得非常臃肿。使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。

    修改Spring配置文件:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4         xmlns:context="http://www.springframework.org/schema/context"
 5         xmlns:aop="http://www.springframework.org/schema/aop"
 6         xmlns:tx="http://www.springframework.org/schema/tx" 
 7         xsi:schemaLocation="
 8         http://www.springframework.org/schema/beans 
 9         http://www.springframework.org/schema/beans/spring-beans.xsd
10         http://www.springframework.org/schema/context 
11         http://www.springframework.org/schema/context/spring-context.xsd
12         http://www.springframework.org/schema/tx 
13         http://www.springframework.org/schema/tx/spring-tx.xsd
14         http://www.springframework.org/schema/aop 
15         http://www.springframework.org/schema/aop/spring-aop.xsd">
16     
17     <!-- 注册数据源:C3P0数据源 -->
18     <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
19         <property name="driverClass" value="${jdbc.driverClass}" />
20         <property name="jdbcUrl" value="${jdbc.url}" />
21         <property name="user" value="${jdbc.user}" />
22         <property name="password" value="${jdbc.password}" />
23     </bean>
24     
25     <!-- 注册JDBC属性文件 -->
26     <context:property-placeholder location="classpath:jdbc.properties"/>
27     
28     <!-- 注册Dao -->
29     <bean id="accountDao" class="com.tongji.dao.AccountDaoImpl">
30         <property name="dataSource" ref="myDataSource"/>
31     </bean>
32     <bean id="stockDao" class="com.tongji.dao.StockDaoImpl">
33         <property name="dataSource" ref="myDataSource"/>
34     </bean>
35     <!-- 注册Service -->
36     <bean id="stockService" class="com.tongji.service.StockProcessServiceImpl">
37         <property name="accountDao" ref="accountDao"/>
38         <property name="stockDao" ref="stockDao"/>
39     </bean>    
40     
41     <!-- 事务 -->
42     <!-- 注册事务管理器 -->
43     <bean id="myTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
44         <property name="dataSource" ref="myDataSource"/>
45     </bean>
46     <!-- 注册事务通知 -->
47     <tx:advice id="txAdvice" transaction-manager="myTxManager">
48         <tx:attributes>
49             <!-- 指定在连接点方法上应用的事务属性 -->
50             <tx:method name="open*" isolation="DEFAULT" propagation="REQUIRED"/>
51             <tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED" rollback-for="StockException"/>
52         </tx:attributes>
53     </tx:advice>
54     
55     <!-- AOP配置 -->
56     <aop:config>
57         <!-- 指定切入点 -->
58         <aop:pointcut expression="execution(* *..service.*.*(..))" id="stockPointCut"/>
59         <aop:advisor advice-ref="txAdvice" pointcut-ref="stockPointCut"/>
60     </aop:config>
61 </beans>

  总结:Spring 的事务管理,是 AOP 的应用,将事务作为切面织入到了 Service 层的业务方法中。

 

posted @ 2017-02-04 19:06  拉夫德尔  阅读(10874)  评论(0编辑  收藏  举报