初见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方式的注解配置一样。