搭建环境
在上一篇文章十九、Spring之JdbcDaoSupport的使用的基础上,添加多2个和AspectJ相关的jar包
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aspects-4.2.4.RELEASE.jar
完整的jar包如下所示:
编写dao和service层代码
我这里以转账为例,完成a账户向b账户转账的操作,让其在同一个事务中完成。
编辑AccountDao接口,新增3个方法,查询、扣钱和收钱。
package blog.csdn.net.mchenys.dao;
import blog.csdn.net.mchenys.domain.Account;
public interface AccountDao {
//创建账户
void save(Account account);
//查找
Account getAccount(String name);
//扣钱
void outMoney(Account a, double money);
//收钱
void inMoney(Account b, double money);
}
AccountDaoImpl实现类中对这3个方法进行实现
package blog.csdn.net.mchenys.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import blog.csdn.net.mchenys.domain.Account;
//dao层实现类
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void save(Account account) {
// 从父类获取jdbcTemplate
getJdbcTemplate().update("insert into t_account values(null,?,?)", account.getName(), account.getMoney());
}
// 查找账户
@Override
public Account getAccount(String name) {
return getJdbcTemplate().queryForObject("select * from t_account where name=?", new RowMapper<Account>() {
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account ac = new Account();
ac.setId(rs.getInt("id"));
ac.setName(rs.getString("name"));
ac.setMoney(rs.getDouble("money"));
return ac;
}
}, name);
}
// 扣钱
@Override
public void outMoney(Account a, double money) {
getJdbcTemplate().update("update t_account set money=money-? where name=?", money, a.getName());
}
// 收钱
@Override
public void inMoney(Account b, double money) {
getJdbcTemplate().update("update t_account set money=money+? where name=?", money, b.getName());
}
}
由于我们操作事务是在service层处理的,因此还需要编写service层的代码
创建AccountService接口,声明2个方法,转账和查询
package blog.csdn.net.mchenys.service;
import blog.csdn.net.mchenys.domain.Account;
//service层接口
public interface AccountService {
// 转账
void pay(Account a, Account b, double money);
//查找
Account getAccountByName(String name);
}
AccountServiceImpl中对这2个方法实现
package blog.csdn.net.mchenys.service;
import blog.csdn.net.mchenys.dao.AccountDao;
import blog.csdn.net.mchenys.domain.Account;
//service层实现类
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
// 通过set方法+xml的形式注入属性
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void pay(Account a, Account b, double money) {
// A账户先扣钱
accountDao.outMoney(a, money);
// B账户加钱
accountDao.inMoney(b, money);
}
@Override
public Account getAccountByName(String name) {
return accountDao.getAccount(name);
}
}
平台事务管理器
在上面的service层代码中,并没有发现任何有关事务操作的代码,这是因为Spring有自己的方式去处理事务,Spring中定义了专门用于管理事务的接口PlatformTransactionManager
,该接口有具体的实现类,根据不同的持久层框架,需要选择不同的实现类,如果使用的Spring的JDBC模板或者MyBatis框架,需要选择DataSourceTransactionManager
实现类,如果使用的是Hibernate的框架,需要选择HibernateTransactionManager
实现类。
那么要如何使用Spring的事务管理呢?
首先,需要在Spring配置文件中配置事务管理器
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入连接池,因为事务管理需要用到connection -->
<property name="dataSource" ref="dataSource" />
</bean>
然后,配置事务增强,相当于切面类
<!-- 配置事务增强
transaction-manager:指定平台事务管理器
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 哪些方法加事务,通过method指定方法名,方法名可以带*号来通配 -->
<tx:method name="pay"/>
<!-- 若还有其他方法,还可以接着配置.. -->
</tx:attributes>
</tx:advice>
然后,配置AOP,注意这里和我们之前通过<aop:aspect>
标签引入自定义的切面类不同,如果不清楚如何引入自定义的切面类可以先看这篇文章十三、AspectJ的XML方式完成AOP的开发,这里由于我们要引入的是Spring定义的切面类,因此需要使用<aop:advisor>
标签来引入,切入点表达式语法不变。
<!-- 配置AOP切面产生代理 -->
<aop:config>
<!-- 由于使用的是Spring定义的切面类,因此只能使用 aop:advisor来引入
切入点表达式和之前通过aop:aspect引入切面的方式一样
-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* *..*.AccountServiceImpl.pay(..))"/>
</aop:config>
最后,就是注册一下AccountServiceImpl,并注入AccountDaoImpl到其属性中。
<!-- 注册AccountServiceImpl -->
<bean id="accountService" class="blog.csdn.net.mchenys.service.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"/>
</bean>
完整的Spring配置文件如下:
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 注册c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- c3p0的连接属性的名称有所不同,这里需要注意下 -->
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql:///spring_jdbc" />
<property name="user" value="root" />
<property name="password" value="1234" />
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入连接池,因为事务管理需要用到connection -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务增强
transaction-manager:指定平台事务管理器
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 哪些方法加事务,通过method指定方法名,方法名可以带*号来通配 -->
<tx:method name="pay"/>
<!-- 若还有其他方法,还可以接着配置.. -->
</tx:attributes>
</tx:advice>
<!-- 配置AOP切面产生代理 -->
<aop:config>
<!-- 由于使用的是Spring定义的切面类,因此只能使用 aop:advisor来引入
切入点表达式和之前通过aop:aspect引入切面的方式一样
-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* *..*.AccountServiceImpl.pay(..))"/>
</aop:config>
<!-- 注册AccountDaoImpl -->
<bean id="accountDao" class="blog.csdn.net.mchenys.dao.AccountDaoImpl">
<!-- 注入属性 :dataSource属性来自JdbcDaoSupport类,只要继承就可以拥有 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 注册AccountServiceImpl -->
<bean id="accountService" class="blog.csdn.net.mchenys.service.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"/>
</bean>
</beans>
测试
编写测试类
package blog.csdn.net.mchenys.test;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import blog.csdn.net.mchenys.dao.AccountDao;
import blog.csdn.net.mchenys.domain.Account;
import blog.csdn.net.mchenys.service.AccountService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo2 {
@Resource(name = "accountService")
private AccountService service; // 通过注解注入配置文件中注册的AccountServiceImpl
@Test
public void test1() {
// 先查找
Account a = service.getAccountByName("a");
Account b = service.getAccountByName("b");
// 后转账
service.pay(a, b, 100);
}
}
运行前,先在数据库中插入2条记录
insert into t_account values(null,"a",1000);
insert into t_account values(null,"b",1000);
然后,在运行测试代码,如果绿条通过,并且观察数据库,a账户-100,b账户+100,则说明上面的配置是生效的。
模拟异常,验证事务管理
通过模拟异常,就可以检验Spring的事务管理事务生效,在AccountServiceImpl的pay方法中插入异常代码
@Override
public void pay(Account a, Account b, double money) {
// A账户先扣钱
accountDao.outMoney(a, money);
// 模拟异常
int num = 10 / 0;
// B账户加钱
accountDao.inMoney(b, money);
}
然后,在运行测试代码,发现红条,即转账失败,检查数据库中的数据,如果a,b账户的钱没有发生变化如下图所示那样,那么说明事务生效了,
默认情况下pay方法内执行的逻辑是在同一个事务中进行,即a的扣钱和b的收钱是在同一个事务中进行的,任何一个环节出现异常,那么事务就会回滚,只有当所有环节都正常结束,那么事务才会提交,数据库最终才会修改数据。