基于XML方式的声明式事务
基于XML方式的声明式事务管理是通过在配置文件中配置事务规则的相关声明来实现的。Spring2.0以后,提供了tx命名空间来配置事务,tx命名空间下提供了<tx:advice>元素来配置事务的通知(增强处理)。当使用<tx:advice>元素配置了事务的增强处理后,就可以通过编写的AOP配置,让Spring自动对目标生成代理。
配置<tx:advice>元素时,通常需要指定id和transaction-manager属性,其中id属性是配置文件中的唯一标识,transaction-manager属性用于指定事务管理器。除此之外,还需要配置一个<tx:attributes>子元素,该子元素可通过配置多个<tx:method>子元素来配置执行事务的细节。<tx:advice>元素及其子元素如图1所示。
图1 <tx:advice>元素及其子元素
在图1中,配置<tx:advice>元素的重点是配置<tx:method>子元素,图中使用灰色标注的几个属性是<tx:method>元素中的常用属性。
关于<tx:method>元素的属性描述如表1所示。
表1 <tx:method>元素的属性
属性名称 | 描述 |
---|---|
name | 该属性为必选属性,它指定了与事务属性相关的方法名。其属性值支持使用通配符,如''、'get'、'handle'、'Order'等。 |
propagation | 用于指定事务的传播行为,其属性值就是表5-1中的值,它的默认值为REQUIRED。 |
isolation | 该属性用于指定事务的隔离级别,其属性值可以为DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ和SERIALIZABLE,其默认值为DEFAULT。 |
read-only | 该属性用于指定事务是否只读,其默认值为false。 |
timeout | 该属性用于指定事务超时的时间,其默认值为-1,即永不超时。 |
rollback-for | 该属性用于指定触发事务回滚的异常类,在指定多个异常类时,异常类之间以英文逗号分隔。 |
no-rollback-for | 该属性用于指定不触发事务回滚的异常类,在指定多个异常类时,异常类之间以英文逗号分隔。 |
了解了如何在XML文件中配置事务后,接下来通过一个案例来演示如何通过XML方式来实现Spring的声明式事务管理。本案例以上一章的项目代码和数据表为基础,编写一个模拟银行转账的程序,要求在转账时通过Spring对事务进行控制,其具体实现步骤如下。
(1)在Eclipse中,创建一个名为chapter05的Web项目,在项目的lib目录中导入chapter04项目中的所有JAR包,并将AOP所需JAR包也导入到lib目录中。导入后的lib目录如图2所示。
图2 项目所需JAR包
(2)将chapter04项目中的代码和配置文件复制到chapter05项目的src目录下,并在AccountDao接口中,创建一个转账方法transfer(),其代码如下所示。
// 转账
public void transfer(String outUser,String inUser,Double money);
(3)在其实现类AccountDaoImpl中实现transfer()方法,编辑后的代码如下所示。
/**
* 转账
* inUser:收款人
* outUser:汇款人
* money:收款金额
*/
public void transfer(String outUser, String inUser, Double money) {
// 收款时,收款用户的余额=现有余额+所汇金额
this.jdbcTemplate.update("update account set balance = balance +? "
+ "where username = ?",money, inUser);
// 模拟系统运行时的突发性问题
int i = 1/0;
// 汇款时,汇款用户的余额=现有余额-所汇金额
this.jdbcTemplate.update("update account set balance = balance-? "
+ "where username = ?",money, outUser);
}
在上述代码中,使用了两个update()方法对account表中的数据执行收款和汇款的更新操作。在两个操作之间,添加了一行代码“int i = 1/0;”来模拟系统运行时的突发性问题。如果没有事务控制,那么在转账操作执行后,收款用户的余额会增加,而汇款用户的余额会因为系统出现问题而不变,这显然是有问题的;如果增加了事务控制,那么在转账操作执行后,收款用户的余额和汇款用户的余额在问题出现前后都应该保持不变。
(4)修改配置文件applicationContext.xml,添加命名空间并编写事务管理的相关配置代码,如文件1所示。
文件1 applicationContext.xml
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:aop="http://www.springframework.org/schema/aop"
5 xmlns:tx="http://www.springframework.org/schema/tx"
6 xmlns:context="http://www.springframework.org/schema/context"
7 xsi:schemaLocation="http://www.springframework.org/schema/beans
8 http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
9 http://www.springframework.org/schema/tx
10 http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
11 http://www.springframework.org/schema/context
12 http://www.springframework.org/schema/context/spring-context-4.3.xsd
13 http://www.springframework.org/schema/aop
14 http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
15 <!-- 1.配置数据源 -->
16 <bean id="dataSource"
17 class="org.springframework.jdbc.datasource.DriverManagerDataSource">
18 <!--数据库驱动 -->
19 <property name="driverClassName" value="com.mysql.jdbc.Driver" />
20 <!--连接数据库的url -->
21 <property name="url" value="jdbc:mysql://localhost/spring" />
22 <!--连接数据库的用户名 -->
23 <property name="username" value="root" />
24 <!--连接数据库的密码 -->
25 <property name="password" value="root" />
26 </bean>
27 <!-- 2.配置JDBC模板 -->
28 <bean id="jdbcTemplate"
29 class="org.springframework.jdbc.core.JdbcTemplate">
30 <!-- 默认必须使用数据源 -->
31 <property name="dataSource" ref="dataSource" />
32 </bean>
33 <!--3.定义id为accountDao的Bean -->
34 <bean id="accountDao" class="com.itheima.jdbc.AccountDaoImpl">
35 <!-- 将jdbcTemplate注入到AccountDao实例中 -->
36 <property name="jdbcTemplate" ref="jdbcTemplate" />
37 </bean>
38 <!-- 4.事务管理器,依赖于数据源 -->
39 <bean id="transactionManager" class=
40 "org.springframework.jdbc.datasource.DataSourceTransactionManager">
41 <property name="dataSource" ref="dataSource" />
42 </bean>
43 <!-- 5.编写通知:对事务进行增强(通知),需要编写对切入点和具体执行事务细节 -->
44 <tx:advice id="txAdvice" transaction-manager="transactionManager">
45 <tx:attributes>
46 <!-- name:*表示任意方法名称 -->
47 <tx:method name="*" propagation="REQUIRED"
48 isolation="DEFAULT" read-only="false" />
49 </tx:attributes>
50 </tx:advice>
51 <!-- 6.编写aop,让spring自动对目标生成代理,需要使用AspectJ的表达式 -->
52 <aop:config>
53 <!-- 切入点 -->
54 <aop:pointcut expression="execution(* com.itheima.jdbc.*.*(..))"
55 id="txPointCut" />
56 <!-- 切面:将切入点与通知整合 -->
57 <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
58 </aop:config>
59 </beans>
在文件1中,首先启用了Spring配置文件的aop、tx和context三个命名空间(从配置数据源到声明事务管理器的部分都没有变化),然后定义了id为transactionManager的事务管理器,接下来通过编写的通知来声明事务,最后通过声明AOP的方式让Spring自动生成代理。
(5)在com.itheima.jdbc包中,创建测试类TransactionTest,并在类中编写测试方法xmlTest(),如文件2所示。
文件2 TransactionTest.java
1 package com.itheima.jdbc;
2 import org.junit.Test;
3 import org.springframework.context.ApplicationContext;
4 import
5 org.springframework.context.support.ClassPathXmlApplicationContext;
6 //测试类
7 public class TransactionTest {
8 @Test
9 public void xmlTest(){
10 ApplicationContext applicationContext =
11 new ClassPathXmlApplicationContext("applicationContext.xml");
12 // 获取AccountDao实例
13 AccountDao accountDao =
14 (AccountDao)applicationContext.getBean("accountDao");
15 // 调用实例中的转账方法
16 accountDao.transfer("Jack", "Rose", 100.0);
17 // 输出提示信息
18 System.out.println("转账成功!");
19 }
20 }
在文件2中,获取了AccountDao实例后,调用了实例中的转账方法,由Jack向Rose的账户中转入100元。如果在配置文件中所声明的事务代码能够起作用,那么在整个转账方法执行完毕后,Jack和Rose的账户余额应该都是原来的数值。
在执行转账操作前,先查看account表中的数据,如图3所示。
图3 account表
从图3可以看出,此时Jack的账户余额是2000,而Rose的账户余额是500。执行完文件5-2中的测试方法后,Junit的控制台的显示结果如图4所示。
图4 运行结果
从图4可以看到,Junit控制台中报出了“/by zero”的算术异常信息。此时如果再次查询数据表account,会发现表中Jack和Rose的账户余额并没有发生任何变化(与如图5-4中的显示结果一样),这说明Spring中的事务管理配置已经生效。