本文基于下述教程编写:【B站】ssm教程
事务控制
事务要满足四大特性:原子性
、一致性
、隔离性
、持久性
。
AB之间的转账操作一定要满足数据库金额总额不变的条件。
因此,事务控制就很有必要。否则一整个转账操作只是多个Dao
操作,遇到异常基本导致后续无法执行,并且数据库已经遭到修改,这时候整个操作的事务回滚特别重要。还有想要控制事务,只能够向当前线程提供一个连接Connection
,我们应该准备一个事务控制类、一个获取连接工具类分离两个业务逻辑。
事务控制类TransactionManager
:
/**
* 事务管理工具类,开启事务、提交事务、回滚事务、释放连接
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
//还回连接池中
connectionUtils.getThreadConnection().close();
/**
* 服务器提供一个线程,该线程拥有一个Connection,当线程关闭Connection时
* Connection就不能用了,线程结束会还回服务器线程池,但是此时还回去的线程
* 还拥有一个已经关闭了的Connection,再次调用该线程获取连接时,一定存在Connection
* 但已经关闭还回连接池不能再使用,所以需要线程和连接解绑
*/
connectionUtils.remove();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
值得注意的是,释放Connection
的时候,不仅要归还连接池还是和线程解绑,不然释放后再次获取已经存在绑定的Connection
就不能用了。
连接工具类ConnectionUtils
:
/**
* 连接工具类,它用于从数据源获取一个连接,并且实现和线程绑定
*/
public class ConnectionUtils {
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
*/
public Connection getThreadConnection() {
try {
//1.先从ThreadLocal上获取
Connection conn = threadLocal.get();
//2.判断线程上有无连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
threadLocal.set(conn);
}
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void remove(){
threadLocal.remove();
}
}
ThreadLocal
中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal
为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。用来存储控制事务操作的Connection
最恰当不过。
Spring容器配置bean.xml
:
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<!--服务层AccountService-->
<bean id="accountService" class="com.spring_aop.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property >
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<!--持久层AccountDao-->
<bean id="accountDao" class="com.spring_aop.dao.impl.AccountDaoImpl">
<property name="runner" ref="runner"></property >
<property name="connectionUtils" ref="connectionUtils"></property >
</bean>
<!--持久层AccountDao成员属性QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--取消向QueryRunner注入连接-->
<!--<constructor-arg name="ds" ref="dataSource">