不同数据源的多个 JdbcTemplate 之间的事务一致性支持

不同数据源的多个 JdbcTemplate 之间的事务一致性支持

最近遇到了这种需求,使用下面这种方式编写测试代码时发现无效。

jdbcTemplate.getDataSource().getConnection().setAutoCommit(false); 

使用 TransactionTemplate 吧,又不满足我的需求。

没办法,只能使用 PlatformTransactionManager 手动来做了,刚开始代码如下:

@Data
public class JdbcTemplateTransactionWrap {
    private List<Operate> operates = new ArrayList<>();

    public void addOperate(String sql, JdbcTemplate jdbcTemplate) {
        this.operates.add(new Operate(sql, jdbcTemplate));
    }

    public void startTransaction() {
        this.operates.forEach(Operate::startTransaction);
    }

    public void commit() {
        this.operates.forEach(Operate::commit);
    }

    public void execute() {
        this.operates.forEach(Operate::execute);
    }

    public void rollback() {
        for (Operate operate : this.operates) {
            try {
                operate.rollback();
            } catch (Exception e) {
                log.error("JdbcTemplateTransactionWrap rollback error !", e);
            }
        }
    }

    private static class Operate {
        private final String sql;
        private final JdbcTemplate jdbcTemplate;

        private PlatformTransactionManager transactionManager;
        private TransactionStatus transactionStatus;

        public Operate(String sql, JdbcTemplate jdbcTemplate) {
            this.sql = sql;
            this.jdbcTemplate = jdbcTemplate;
        }

        public void startTransaction() {
            this.transactionManager = new DataSourceTransactionManager(jdbcTemplate.getDataSource());
            this.transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
        }

        public void execute() {
            jdbcTemplate.execute(sql);
        }

        public void commit() {
            if(transactionManager != null && transactionStatus != null) {
                this.transactionManager.commit(this.transactionStatus);
            }
            transactionManager = null;
            transactionStatus = null;
        }

        public void rollback() {
            if(transactionManager != null && transactionStatus != null) {
                this.transactionManager.rollback(this.transactionStatus);
            }
            transactionManager = null;
            transactionStatus = null;
        }
    }
}

发现事务还是无效,直到我看到了这篇文章:Spring + Jta +JDBCTemplate 分布式事物实现方式

他用的是切面,但是切面的代码和我写的差不多,为什么他的事务可以生效而我的却无效呢?
经过我的仔细对比,让我发现了一句至关重要的注释:

/**
 * 其中为什么要用Stack来保存TransactionManager和TransactionStatus呢?
 * 那是因为Spring的事务处理是按照LIFO/stack behavior的方式进行的。
 * 如若顺序有误,则会报错:
 */
transactionStatuStack.push(transactionStatus);
dataSourceTransactionManagerStack.push(dataSourceTransactionManager);

然后我把代码中 List 改为 Deque 之后,果然事务生效了!
最终代码如下:

package com.kfyty.support.transaction;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 描述: JdbcTemplate 事务包装,实现多个 JdbcTemplate 的事务一致性
 *
 * @author kfyty725
 * @date 2021/5/11 18:00
 * @email kfyty725@hotmail.com
 */
@Slf4j
public class JdbcTemplateTransactionWrap {
    /**
     * 必须使用 Stack
     */
    private final Deque<Operate> operates = new ArrayDeque<>();

    public static void invokeWithTransaction(JdbcTemplateTransactionWrap transactionWrap) {
        Objects.requireNonNull(transactionWrap);
        try {
            transactionWrap.execute();
            transactionWrap.commit();
        } catch (Exception e) {
            transactionWrap.rollback();
            throw e;
        }
    }

    public static List<Object> invokeWithCallback(JdbcTemplateTransactionWrap transactionWrap) {
        Objects.requireNonNull(transactionWrap);
        try {
            List<Object> result = transactionWrap.executeCallback();
            transactionWrap.commit();
            return result;
        } catch (Exception e) {
            transactionWrap.rollback();
            throw e;
        }
    }

    public void addOperate(JdbcTemplate jdbcTemplate, Function<JdbcTemplate, Object> callback) {
        Operate operate = new Operate(jdbcTemplate, callback);
        operate.startTransaction();
        this.operates.push(operate);
    }

    public void addOperate(JdbcTemplate jdbcTemplate, String ... sql) {
        this.addOperate(jdbcTemplate, Arrays.asList(sql));
    }

    public void addOperate(JdbcTemplate jdbcTemplate, List<String> sql) {
        Operate operate = new Operate(jdbcTemplate, sql);
        operate.startTransaction();
        this.operates.push(operate);
    }

    public void commit() {
        while (!operates.isEmpty()) {
            operates.pop().commit();
        }
    }

    public void execute() {
        operates.forEach(Operate::execute);
    }

    public List<Object> executeCallback() {
        return operates.stream().map(Operate::executeCallback).collect(Collectors.toList());
    }

    public void rollback() {
        while (!operates.isEmpty()) {
            try {
                operates.pop().rollback();
            } catch (Exception e) {
                log.error("JdbcTemplateTransactionWrap rollback error !", e);
            }
        }
    }

    @RequiredArgsConstructor
    private static class Operate {
        private final List<String> sql;
        private final JdbcTemplate jdbcTemplate;
        private final Function<JdbcTemplate, Object> callback;

        private PlatformTransactionManager transactionManager;
        private TransactionStatus transactionStatus;

        public Operate(JdbcTemplate jdbcTemplate, List<String> sql) {
            this(sql, jdbcTemplate, null);
        }

        public Operate(JdbcTemplate jdbcTemplate, Function<JdbcTemplate, Object> callback) {
            this(null, jdbcTemplate, callback);
        }

        public void startTransaction() {
            this.transactionManager = new DataSourceTransactionManager(jdbcTemplate.getDataSource());
            this.transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
        }

        public void execute() {
            Objects.requireNonNull(sql);
            sql.forEach(jdbcTemplate::execute);
        }

        public Object executeCallback() {
            Objects.requireNonNull(callback);
            return callback.apply(this.jdbcTemplate);
        }

        public void commit() {
            if(transactionManager != null && transactionStatus != null) {
                this.transactionManager.commit(this.transactionStatus);
            }
            transactionManager = null;
            transactionStatus = null;
        }

        public void rollback() {
            if(transactionManager != null && transactionStatus != null) {
                this.transactionManager.rollback(this.transactionStatus);
            }
            transactionManager = null;
            transactionStatus = null;
        }
    }
}

注意:此方案未考虑执行 commit() 时发生异常的情况!!!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值