不同数据源的多个 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() 时发生异常的情况!!!