事务的传播行为(Propagation Behavior)是事务控制中非常重要的概念,说简单的,可以这样理解:
有两个带有事务的方法 A 与 B,若 A 方法调用 B 方法, 那么事务应该如何处理呢?
Spring 给我们提供了 7 种事务传播行为,它们分别是:
1. Required:支持当前事务,若当前不存在事务,则创建一个新事务。这是 Spring 使用的默认方式。
2. Supports:支持当前事务,若 当前不存在事务,则以非事务方式执行。
3. Mandatory:支持当前事务, 若 当前不存在事务,则抛出异常。
4. Requires New:创建一个新事务,若当前存在事务,则挂起当前事务。
5. Not Supported:以非事务方式执行, 若当前存在事务,则挂起当前事务。
6. Never: 以非事务方式执行, 若当前存在事务,则抛出异常。
7. Nested: 创建一个新事务, 若当前存在事务,则将新创建的事务作为“子事务”,与已有事务构成父子关系。
在此非常感谢 Dead_knight 为事务传播行为做出的贡献!他已成功将 J odd 开源项目中的 JTX 模块给剥离出来,为大家提供了一个史上最轻量级的事务管理框架,该项目命名为 jtxer,地址是: http://git.oschina.net/yuqs/jtxer ,欢迎大家使用!
此外,也非常感谢 哈库纳 提供的 Hasor-JDBC 开源项目,其中也包括了一个事务管理框架,核心代码来自于 Spring,地址是: http://git.oschina.net/zycgit/hasor,还有一些关于该框架的博文: http://my.oschina.net/u/1166271/blog?catalog=448293,欢迎大家阅读!
虽然以上框架都能实现 Smart 的事务传播行为,但我个人认为,Smart 不应该朝着复杂的应用场景而发展,它应该是一个简单并易于扩展的框架,而不是一个大而全的框架,这也是我对 Smart 的定位。
所以, 打算使用 Supports 作为 Smart 的事务传播行为,这也是唯一的选择。 也就是说,当前有事务就用当前事务,当前没事务就不用事务,完全取决于当前是否有事务。更进一步说,A 方法调用 B 方法,若 A 方法上有事务,则不用管 B 方法上是否有事务,因为即便有事务也会将其忽略掉。
好的,目标清晰了,下一步就是如何来实现这个目标?
目前,在 Smart 中如果有 A、B 两个方法都带有 Transaction 注解,若用 A 方法去调用 B 方法,则会出现一个异常:
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Can't call rollback when autocommit=true
原因很简单,由于在 B 方法中事务提交后,已将 Connection 的 AutoCommit 属性设置为 true 了,导致 A 方法无法提交 事务。
怎样才能解决这个问题呢?
我们不妨看看 Smart 现在的 TransactionProxy 源码:
public class TransactionProxy implements Proxy {
private static final Logger logger = LoggerFactory.getLogger(TransactionProxy.class);
@Override
public Object doProxy(ProxyChain proxyChain) throws Throwable {
Object result;
// 获取目标方法
Method method = proxyChain.getTargetMethod();
// 若在目标方法上定义了 Transaction 注解,则说明该方法需要进行事务处理
if (method.isAnnotationPresent(Transaction.class)) {
try {
// 开启事务
DatabaseHelper.beginTransaction();
logger.debug("[Smart] begin transaction");
// 执行目标方法
result = proxyChain.doProxyChain();
// 提交事务
DatabaseHelper.commitTransaction();
logger.debug("[Smart] commit transaction");
} catch (Exception e) {
// 回滚事务
DatabaseHelper.rollbackTransaction();
logger.debug("[Smart] rollback transaction");
throw e;
}
} else {
// 执行目标方法
result = proxyChain.doProxyChain();
}
return result;
}
}
每个请求线程中都会创建一个或多个 TransactionProxy 实例,这些实例是通过一个 ProxyChain 来实现链式调用的。如果能保证每个线程只处理最外层方法所对应的事务,而忽略其中被调用的带有事务的方法,那么这个问题就解决了。
我使用了一个 static 的 ThreadLocal 变量,里面只需放置一个 Boolean 类型的 flag,在第一次处理事务时将 flag 设置为 true,下一次需要首先判断 flag 的值,来决定是否进行事务处理。
以下是更新后的 TransactionProxy 源码:
public class TransactionProxy implements Proxy {
private static final Logger logger = LoggerFactory.getLogger(TransactionProxy.class);
// 定义一个线程局部变量,用于保存当前线程中是否进行了事务处理,默认为 false(未处理)
private static final ThreadLocal<Boolean> flagContainer = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return false;
}
};
@Override
public Object doProxy(ProxyChain proxyChain) throws Throwable {
Object result;
// 判断当前线程是否进行了事务处理
boolean flag = flagContainer.get();
// 获取目标方法
Method method = proxyChain.getTargetMethod();
// 若当前线程未进行事务处理,且在目标方法上定义了 Transaction 注解,则说明该方法需要进行事务处理
if (!flag && method.isAnnotationPresent(Transaction.class)) {
// 设置当前线程已进行事务处理
flagContainer.set(true);
try {
// 开启事务
DatabaseHelper.beginTransaction();
logger.debug("[Smart] begin transaction");
// 执行目标方法
result = proxyChain.doProxyChain();
// 提交事务
DatabaseHelper.commitTransaction();
logger.debug("[Smart] commit transaction");
} catch (Exception e) {
// 回滚事务
DatabaseHelper.rollbackTransaction();
logger.debug("[Smart] rollback transaction");
throw e;
} finally {
// 移除线程局部变量
flagContainer.remove();
}
} else {
// 执行目标方法
result = proxyChain.doProxyChain();
}
return result;
}
}
请大家评审以上解决方案,期待您的建议!