二十、Spring事务管理之基于AspectJ的XML方式

搭建环境

在上一篇文章十九、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的收钱是在同一个事务中进行的,任何一个环节出现异常,那么事务就会回滚,只有当所有环节都正常结束,那么事务才会提交,数据库最终才会修改数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值