文章内容输出来源:拉勾教育Java高薪训练营
文章目录
相关文章
问题
在上一篇Spring实践笔记-手写简易的Spring第一篇之IOC中提到了转账业务,A从自己的帐户中取出钱,转到了B的帐户上。
这里要拆分成两个动作:A帐户取出钱、B帐户增加钱。
如果因为某些故障,导致了A帐户取出钱成功,但是B帐户增加钱失败,那就有问题,违反了数据的一致性。
这两个动作要作为一个事务来处理,处理要一起成功,当出错时就要回滚到原来的状态。
问题思路
解决此问题就要为transferService#transfer
转账业务方法添加上事务处理。在方法执行前开启事务,在方法执行后提交事务,当方法出现了异常,就回滚事务。
-
如何实现事务的三种操作
使用数据库连接Connection
提供的setAutoCommit
、commit
、rollback
方法 -
如何为业务方法添加上事务
使用AOP切面的思想,通过动态代理对业务方法进行增强
实现事务管理
1. 数据库连接Connection的复用
在同一个业务方法中对Account
表进行更新金额update
操作,为使事务生效,两个SQL操作需要处于同一个数据库连接中。
在ConnectionUtils
数据库连接工具类中通过ThreadLocal
将连接绑定到当前线程中,实现同一线程下数据库连接的复用。
public class ConnectionUtils {
private final ThreadLocal<DruidPooledConnection> threadLocal = new ThreadLocal();
public DruidPooledConnection getConnection() throws SQLException {
DruidPooledConnection connection = threadLocal.get();
//判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取一个连接绑定到当前线程
if(null == threadLocal.get()) {
connection = DruidUtils.getInstance().getConnection();
threadLocal.set(connection);
}
return connection;
}
2. 增加事务管理类,实现事务的三种操作
在事务管理类TransactionManager
中增加开启事务、提交事务、回滚事务的方法
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
* @throws SQLException
*/
void beginTransaction() throws SQLException {
connectionUtils.getConnection().setAutoCommit(false);
}
/**
* 提交事务
* @throws SQLException
*/
void commit() throws SQLException {
connectionUtils.getConnection().commit();
}
/**
* 回滚事务
* @throws SQLException
*/
void rollback() throws SQLException {
connectionUtils.getConnection().rollback();
}
}
3. 增加代理工厂,实现对方法进行事务的增加
- 在代理对象的方法执行前,开启事务
- 在代理对象的方法执行后,提交事务
- 在代理对象的方法执行抛出异常处理时,回滚事务
public class ProxyFactory {
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* 使用JDK代理增强
* @param obj
* @return
*/
public Object getJdkProxy(final Object obj) {
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
try {
//开启事务
transactionManager.beginTransaction();
result = method.invoke(obj, args);
//提交事务
transactionManager.commit();
}catch (Exception e) {
e.printStackTrace();
//回滚事务
transactionManager.rollback();
throw e;
}
return result;
}
});
}
}
4. 生成事务相关的Bean
在beans.xml
中声明下上述实现类的bean,放到IOC容器中管理
<beans>
<bean id="connectionUtils" class="com.yyh.utils.ConnectionUtils"/>
<bean id="transactionManager" class="com.yyh.component.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"/>
</bean>
<bean id="proxyFactory" class="com.yyh.component.ProxyFactory">
<property name="transactionManager" ref="transactionManager"/>
</bean>
</beans>
5. 测试
在TransferTest
中测试下转账的业务。
- 使用代理工厂去获取
TransferService
的代理对象,然后调用转账方法
public class TransferTest {
private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
private TransferService transferService = (TransferService)proxyFactory.getJdkProxy(BeanFactory.getBean("transferService"));
@Test
public void testTransfer() throws SQLException {
transferService.transfer("123456", "123457", 100);
}
}
- 当要测试出错情况是否回滚事务,在
TransferService#transfer
中增加上一条抛异常的代码
public void transfer(String fromCardNumber, String toCardNumber, int money) throws SQLException {
AccountEntity fromAccount = accountDao.selectOneByCardNumber(fromCardNumber);
AccountEntity toAccount = accountDao.selectOneByCardNumber(toCardNumber);
accountDao.updateMoneyByCardNumber(fromCardNumber, fromAccount.getMoney() - money);
//此处抛出来一个异常
int a = 1 / 0;
accountDao.updateMoneyByCardNumber(toCardNumber, toAccount.getMoney() + money);
}