对 Smart 事务传播行为的一点想法

原创
2014/04/18 00:38
阅读数 1.5K

事务的传播行为(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;
    }
}

请大家评审以上解决方案,期待您的建议!

展开阅读全文
加载中
点击加入讨论🔥(8) 发布并加入讨论🔥
8 评论
3 收藏
1
分享
返回顶部
顶部