1.理解事务:
事务概念:* 如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败。
关于事务的解决方案:
方案一:
如果我们单纯只想每一次进行一次增删改查的话,获取连接在用完之后会自动提交。
但是如果我们在业务层要进行组合操作,比如我们要进行转账操作:涉及到持久层的操作有(查询、更新)
eg:A给B转账
具体操作逻辑:
(1)查询操作:查A账户金额
(2)查询操作:查B账户金额
(3)操作账户:查到A账户后按要求给A账户减钱。对A账户的实体类进行操作。
(4)操作账户:查到B账户后按要求给B账户加钱。对B账户的实体类进行操作。
(5)更新操作:A账户更新操作
(6)更新操作:B账户更新操作
整个过程涉及到与是持久层交互的地方有:查询两次(A\B),更新两次(A/B)
每一次操作相当于都是获取一个新的连接,连接与连接之间并不相关。
问题是:
一旦在转账过程中发生某种异常中断,上一个连接已经转钱过来了,下一个连接转钱失败。
这样就会造成钱的总数不对的情况。有悖我们常理。
所以我们就需要事务管理。也即是,我们需要用一个连接处理这一连串的事情。而不是四个连接。
于是就想到使用ThreadLocal,它可以把连接和当前线程进行绑定,保证一个线程里只有一个控制事务的对象。
获取连接就从ThreadLocal来。
获取连接的过程:
详解:把连接装进ThreadLocal中,如果ThreadLocal里面有,就直接从ThreadLocal里面拿;如果ThreadLocal里面没有,就从数据源里获取连接,再把连接放进ThreadLocal中
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
//一个装着连接的线程池
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try{
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
再写一个事务管理的类,控制事务的开始、回滚、提交和释放。
接着在业务层,对每一个业务,都引入完整的事务管理,就拿我们的转账操作来说。
一开始开始业务、最后完成提交业务、中间有任何异常都回滚业务,最后