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

本文内容纲要: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
voidcommit(TransactionStatus status)

Commit the given transaction, with regard to its status.

TransactionStatusgetTransaction(TransactionDefinition definition)

Return a currently active transaction or create a new one, according to the specified propagation behavior.

voidrollback(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 层的业务方法中。

本文内容总结:Spring4笔记9--Spring的事务管理(AOP应用的例子)

原文链接:https://www.cnblogs.com/qjjazry/p/6366204.html

以上是 Spring4笔记9--Spring的事务管理(AOP应用的例子) 的全部内容, 来源链接: utcz.com/z/362826.html

回到顶部