本文是<实现 Spring 的事务控制>系列文章中一篇。本文假设读者已经阅读并理解《实现 Spring 的事务控制,之一(必要的概念)》文中所涉及的概念(当前连接、引用计数),以及数据库连接的(new状态)
PROPAGATION_REQUIRED(加入已有事务)
定义:
尝试加入已经存在的事务中,如果没有则开启一个新的事务。解释:
解释 REQUIRED 行为并不困难。它是指如果当前连接中如果已经开启了数据库事务,并且有程序在控制这个事务。那么在处理 REQUIRED 行为时将会忽略 commit/rollback 操作,这是加入已有事务的特征。
时间 | 事务1 | 事务2 |
T1 | 开始事务 | |
T2 | 操作1... | |
T3 | | 加入事务1 |
T4 | 操作2... | |
T5 | 操作3... | |
T6 | 递交事务 | |
定义中有一句“如果没有则开启一个新的事务”这句话怎么理解?
所谓“没有”指的是当前连接中不存在事务(事务状态为false)。“则开启新的事务”这里的可以理解为,如果没有事务那么才开启新的事务。而且新开启的事务是需要事务管理器进行维护的。
工作原理
开启事务
事务管理器在创建 REQUIRED 类型事务时,会取得当前连接这一过程会持有当前连接(引用计数+1)。
然后通过判断当前连接是否存在事务状态,来决定是否通过 doBegin 方法开启事务。这个规则为:如果存在事务状态则忽略,否则开启。
一般情况下在调用 doBegin 方法之前事务都是尚未开启的。我们知道没有开启事务的连接特征是 autoCommit 属性为 true。在这种情况下每一次SQL操作都是独立成为一个事务,因此多条SQL语句之间是不存在事务关系的。换句话说只要进入 doBegin 方法的数据库连接都是没有开启事务的连接,它们都满足 new 状态的特征。
事务中的数据库操作
无论在开启事务的时候Connection 此时此刻,可以直接使用 Connection 接口畅快的使用数据库操作。由于每次进行数据库操作都要反复的申请和释放数据库连接。这会反复的使引用计数 +1,-1。
递交/回滚事务
REQUIRED 行为告诉我们,如果环境中有事务控制,那么该行为下的事务管理器将不作为。所有事务控制操作交给环境中的事务控制来处理。
但是如果环境中不存在事务,那么事务管理器是需要负责 commit & rollback 的。
这时候在前面确定的 new 状态就可以发挥作用了。因为根据 new 状态的特征描述我们知道凡是持有 new 状态的数据库连接在开启事务之前是不存在先前事务的。作为后来者的事务管理器而言,可以放心大胆的去递交事务。
如果不具备 new 特征则正好说明外部在事务管理器之外有其它程序代码代为控制了事务。
例如下面这个业务逻辑(伪代码):
public static void main(){
DataSource ds= ......;
Connection conn = DataSourceUtil.getConnection(ds);//取得数据库连接,会导致引用计数+1
conn.setAutoCommit(false);//开启事务
conn.execute("update ...");//预先执行的 update 语句
insertData();//执行数据库插入
if (test< 20){
conn.rollback();//测试状态不满足的条件下,将插入的测试数据连通之前的 update 一同回滚。
}
DataSourceUtil.releaseConnection(conn,ds);//释放连接,引用计数-1
}
public static void insertData(){
TransactionStatus status = tm.getTransaction(PROPAGATION_REQUIRED);//引用计数+1
jdbc.execute("insert into ...");//执行插入语句,在执行过程中引用计数会 +1,然后在-1
tm.commit(status);//引用计数-1
}
在上面这个例子中,第四行中开启了事务。因此在执行 insertData 的时候事务管理器会认为当前连接中已经存在事务,新的事务只需要加入到当前的事务中即可。并不需要独立控制事务。因此在最后 insertData 方法结束的时,其插入的数据还未真正的插入到数据库。随后外部事务控制代码又将所有数据都回滚了。
上面的代码修改一下如下:
public static void main(){
DataSource ds= ......;
Connection conn = DataSourceUtil.getConnection(ds);//取得数据库连接,会导致引用计数+1
conn.execute("update ...");//预先执行的 update 语句
insertData();//执行数据库插入,数据会进入数据库。
jdbc.execute("insert into ...");//这条插入的数据会被插入
DataSourceUtil.releaseConnection(conn,ds);//释放连接,引用计数-1
}
public static void insertData(){
TransactionStatus status = tm.getTransaction(PROPAGATION_REQUIRED);//引用计数+1
jdbc.execute("insert into ...");//执行插入语句,在执行过程中引用计数会 +1,然后在-1
tm.commit(status);//引用计数-1
}
上面的代码中我们可以看出,insertData 方法之外的程序并没有控制事务。 insertData 中的事务管理器在创建事务对象的时候,由于当前连接满足 new 状态特征。所以在最后 tm.commit(status) 时会执行事务递交操作。
而上面第8行,所使用的连接由于也没有开启事务。所以它会以自动递交事务的方式去运行。
关于 afterClear
afterClear 阶段的作用是将当前连接的状态恢复到 getTransaction 方法之前。并且由于 getTransaction 方法会持有当前连接(引用计数++),因此 afterClear 方法也会释放掉这个连接的持有(引用计数--)。