先上代码:项目源码下载(软件:IDEA):
AOP的具体应用:案例:模拟转账
链接:https://pan.baidu.com/s/1b3aaKzLRmFhPAorPSj9gMQ
提取码:offs
[理解]AOP的具体应用
案例:模拟转账(并且模拟转账异常)
- 汇款人账户减少一定的金额
- 收款人账户增加一定的金额
- 计算之后,更新数据库
问题:模拟转账异常(人为制造异常,在两次update之间造了异常)
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!--
半xml 半注解
第三方用xml配置 自己的用 注解
-->
<!--
开启扫描
-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--
定义 dataSource
-->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring02"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--
queryrunner
-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
</beans>
dao层
package com.itheima.dao;
import com.itheima.pojo.Account;
import com.itheima.utils.ConnectionUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
@Qualifier("queryRunner")
private QueryRunner qr;
@Autowired
@Qualifier("connectionUtil")
private ConnectionUtil connectionUtil;
@Override
public Account queryAccountByName(String name) throws Exception {
String sql = "select * from account where name = ?";
Object[] params = {name};
return qr.query(connectionUtil.getConnection(),sql, new BeanHandler<>(Account.class), params);
}
@Override
public void updateAccount(Account account) throws Exception {
String sql = "update account set money=? where id=?";
Object[] params = {account.getMoney(),account.getId()};
qr.update(connectionUtil.getConnection(),sql,params);
}
}
service层
package com.itheima.service;
import com.itheima.dao.AccountDao;
import com.itheima.pojo.Account;
import com.itheima.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
@Qualifier("accountDao")
private AccountDao accountDao;
@Autowired
@Qualifier("transactionManager")
private TransactionManager transactionManager;
/**
* 实现类 实现转账功能
* 转账思路
* 1:根据账户名查出 汇款人 收款人 账户。
* 2:计算转账金额 汇款人-money 收款人+money
* 3: 将更新数据 更新到数据库
*
*/
@Override
public void transfer(String formName, String toName, Float money)
throws Exception {
try{
//开启事务 (把事务的自动提交关闭)
transactionManager.begin();
//业务逻辑代码
// 根据账户名查出 汇款人 收款人 账户。
Account fromAccount = accountDao.queryAccountByName(formName);
Account toAccount = accountDao.queryAccountByName(toName);
// 计算转账金额 汇款人-money 收款人+money
fromAccount.setMoney(fromAccount.getMoney()-money);
toAccount.setMoney(toAccount.getMoney()+money);
// 完成 转账更新
accountDao.updateAccount(toAccount);
// System.out.println(1/0);
accountDao.updateAccount(fromAccount);
// 正常了 提交
transactionManager.commit();
}catch(Exception e){
e.printStackTrace();
//回滚
transactionManager.rollback();
}finally {
//释放资源
transactionManager.close();
}
}
}
测试
package com.itheima.test;
import com.itheima.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TransTest {
@Autowired
@Qualifier("accountService")
private AccountService accountService;
// 转账测试
@Test
public void eazyTransfer() throws Exception {
accountService.transfer("jack","rose",5000f);
}
}
问题分析:
两次update使用了两个connection(这两个connection分别有自己的事务,而且事务是自动提交的)
换句话说,事务现在是在dao层的
问题处理解决
两次update得使用同一个连接
两次update属于service同一个方法,也就是在同一个线程内,那就把一个连接绑定到当前线程,两个update都用这个连接
创建ConnectionUtil类
ConnectionUtil类--线程和连接对象绑定
package com.itheima.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 连接工具类
* 获取连接对象
* 保证一件事情 在一个线程中 只有一个连接对象!!
*
* 做的事情
* 1:线程和连接对象绑定
* 2:连接从连接池中获取
*/
@Component("connectionUtil")
public class ConnectionUtil {
// 创建好一个 存储当前线程与 连接对象的一个 容器
private ThreadLocal<Connection> threadLocal= new ThreadLocal<>();
@Autowired
@Qualifier("dataSource")
private DataSource dataSource;
/*
获取连接 里面要完成 连接对象与线程绑定操作
*/
public Connection getConnection() throws SQLException {
// 获取当前线程对应的连接对象
Connection connection = threadLocal.get();
if(connection==null){//当前线程没有连接对象 需要从连接池获取
connection = dataSource.getConnection();
//需要将连接对象 设置到threadlocal中
threadLocal.set(connection);
}
return connection;
}
/**
* 使用完了 解除绑定
*/
public void remove(){
threadLocal.remove();
}
}
TransactionManager--事务管理类
package com.itheima.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
/*
这个类 就是用来管理事务操作的 !!!
*/
@Component("transactionManager")
public class TransactionManager {
@Autowired
@Qualifier("connectionUtil")
private ConnectionUtil connectionUtil;
/**
* 开启事务(找到连接对象 然后设置成为手动提交)
* 需要找到 ConnectionUtil 获取到 连接对象
*/
public void begin() throws SQLException {
connectionUtil.getConnection().setAutoCommit(false);
}
/**
* 提交事务(找到连接对象 然后设置成为手动提交)
* 需要找到 ConnectionUtil 获取到 连接对象
*/
public void commit() throws SQLException {
connectionUtil.getConnection().commit();
}
/**
* 回滚事务(找到连接对象 然后设置成为手动提交)
* 需要找到 ConnectionUtil 获取到 连接对象
*/
public void rollback() throws SQLException {
connectionUtil.getConnection().rollback();
}
/**
* 释放资源
* 需要找到 ConnectionUtil 获取到 连接对象
*/
public void close() throws SQLException {
connectionUtil.getConnection().close();
//解除绑定关系
connectionUtil.remove();
}
}
业务层改造层
package com.itheima.service;
import com.itheima.dao.AccountDao;
import com.itheima.pojo.Account;
import com.itheima.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service("accountServiceNoTCF")
public class AccountServiceImplNoTCF implements AccountService {
@Autowired
@Qualifier("accountDao")
private AccountDao accountDao;
@Autowired
@Qualifier("transactionManager")
private TransactionManager transactionManager;
/**
* 实现类 实现转账功能
* 转账思路
* 1:根据账户名查出 汇款人 收款人 账户。
* 2:计算转账金额 汇款人-money 收款人+money
* 3: 将更新数据 更新到数据库
*
*/
@Override
public void transfer(String formName, String toName, Float money)
throws Exception {
//业务逻辑代码
// 根据账户名查出 汇款人 收款人 账户。
Account fromAccount = accountDao.queryAccountByName(formName);
Account toAccount = accountDao.queryAccountByName(toName);
// 计算转账金额 汇款人-money 收款人+money
fromAccount.setMoney(fromAccount.getMoney()-money);
toAccount.setMoney(toAccount.getMoney()+money);
// 完成 转账更新
accountDao.updateAccount(toAccount);
System.out.println(1/0);
accountDao.updateAccount(fromAccount);
}
}
我们不可能在每一个方法中添加tcf控制,因此引入动态代理技术来在不改变原有业务逻辑代码的基础上做逻辑增强。
动态代理工厂类
package com.itheima.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
/*
这个类 就是用来管理事务操作的 !!!
*/
@Component("transactionManager")
public class TransactionManager {
@Autowired
@Qualifier("connectionUtil")
private ConnectionUtil connectionUtil;
/**
* 开启事务(找到连接对象 然后设置成为手动提交)
* 需要找到 ConnectionUtil 获取到 连接对象
*/
public void begin() throws SQLException {
connectionUtil.getConnection().setAutoCommit(false);
}
/**
* 提交事务(找到连接对象 然后设置成为手动提交)
* 需要找到 ConnectionUtil 获取到 连接对象
*/
public void commit() throws SQLException {
connectionUtil.getConnection().commit();
}
/**
* 回滚事务(找到连接对象 然后设置成为手动提交)
* 需要找到 ConnectionUtil 获取到 连接对象
*/
public void rollback() throws SQLException {
connectionUtil.getConnection().rollback();
}
/**
* 释放资源
* 需要找到 ConnectionUtil 获取到 连接对象
*/
public void close() throws SQLException {
connectionUtil.getConnection().close();
//解除绑定关系
connectionUtil.remove();
}
}