Skip to content

Commit

Permalink
新增多数据源事务传播机制 (#406)
Browse files Browse the repository at this point in the history
本地多数据源事务传播机制
  • Loading branch information
zhaohaoh authored Dec 26, 2022
1 parent 638b2dc commit e2e176d
Show file tree
Hide file tree
Showing 14 changed files with 328 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
*/
package com.baomidou.dynamic.datasource.annotation;



import com.baomidou.dynamic.datasource.tx.DsPropagation;

import java.lang.annotation.*;

/**
Expand All @@ -26,4 +30,9 @@
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DSTransactional {
Class<? extends Throwable>[] rollbackFor() default {Exception.class};

Class<? extends Throwable>[] noRollbackFor() default {};

DsPropagation propagation() default DsPropagation.REQUIRED;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,41 @@
*/
package com.baomidou.dynamic.datasource.aop;

import com.baomidou.dynamic.datasource.tx.LocalTxUtil;
import com.baomidou.dynamic.datasource.tx.TransactionContext;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.dynamic.datasource.tx.*;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;

/**
* @author funkye
*/
@Slf4j
public class DynamicLocalTransactionInterceptor implements MethodInterceptor {
private final TransactionalTemplate transactionalTemplate = new TransactionalTemplate();

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
if (!StringUtils.isEmpty(TransactionContext.getXID())) {
return methodInvocation.proceed();
}
boolean state = true;
Object o;
LocalTxUtil.startTransaction();
try {
o = methodInvocation.proceed();
} catch (Exception e) {
state = false;
throw e;
} finally {
if (state) {
LocalTxUtil.commit();
} else {
LocalTxUtil.rollback();
public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
Method method = methodInvocation.getMethod();
final DSTransactional dsTransactional = method.getAnnotation(DSTransactional.class);

TransactionalExecutor transactionalExecutor = new TransactionalExecutor() {
@Override
public Object execute() throws Throwable {
return methodInvocation.proceed();
}

@Override
public TransactionalInfo getTransactionInfo() {
TransactionalInfo transactionInfo = new TransactionalInfo();
transactionInfo.setPropagation(dsTransactional.propagation());
transactionInfo.setNoRollbackFor(dsTransactional.noRollbackFor());
transactionInfo.setRollbackFor(dsTransactional.rollbackFor());
return transactionInfo;
}
}
return o;
};
return transactionalTemplate.execute(transactionalExecutor);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ public Connection getConnection() throws SQLException {
} else {
String ds = DynamicDataSourceContextHolder.peek();
ds = StringUtils.isEmpty(ds) ? getPrimary() : ds;
ConnectionProxy connection = ConnectionFactory.getConnection(ds);
return connection == null ? getConnectionProxy(ds, determineDataSource().getConnection()) : connection;
ConnectionProxy connection = ConnectionFactory.getConnection(xid, ds);
return connection == null ? getConnectionProxy(xid, ds, determineDataSource().getConnection()) : connection;
}
}

Expand All @@ -69,15 +69,15 @@ public Connection getConnection(String username, String password) throws SQLExce
} else {
String ds = DynamicDataSourceContextHolder.peek();
ds = StringUtils.isEmpty(ds) ? getPrimary() : ds;
ConnectionProxy connection = ConnectionFactory.getConnection(ds);
return connection == null ? getConnectionProxy(ds, determineDataSource().getConnection(username, password))
ConnectionProxy connection = ConnectionFactory.getConnection(xid, ds);
return connection == null ? getConnectionProxy(xid, ds, determineDataSource().getConnection(username, password))
: connection;
}
}

private Connection getConnectionProxy(String ds, Connection connection) {
private Connection getConnectionProxy(String xid, String ds, Connection connection) {
ConnectionProxy connectionProxy = new ConnectionProxy(connection, ds);
ConnectionFactory.putConnection(ds, connectionProxy);
ConnectionFactory.putConnection(xid, ds, connectionProxy);
return connectionProxy;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.baomidou.dynamic.datasource.exception;

public class TransactionException extends RuntimeException {
public TransactionException(String message) {
super(message);
}

public TransactionException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.spring.boot.autoconfigure;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,4 @@ public class DynamicDataSourceProperties {
*/
@NestedConfigurationProperty
private DynamicDatasourceAopProperties aop = new DynamicDatasourceAopProperties();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.baomidou.dynamic.datasource.tx;

import org.springframework.util.CollectionUtils;

import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -24,43 +26,60 @@
*/
public class ConnectionFactory {

private static final ThreadLocal<Map<String, ConnectionProxy>> CONNECTION_HOLDER =
new ThreadLocal<Map<String, ConnectionProxy>>() {
private static final ThreadLocal<Map<String, Map<String, ConnectionProxy>>> CONNECTION_HOLDER =
new ThreadLocal<Map<String, Map<String, ConnectionProxy>>>() {
@Override
protected Map<String, ConnectionProxy> initialValue() {
return new ConcurrentHashMap<>(8);
protected Map<String, Map<String, ConnectionProxy>> initialValue() {
return new ConcurrentHashMap<>();
}
};

public static void putConnection(String ds, ConnectionProxy connection) {
Map<String, ConnectionProxy> concurrentHashMap = CONNECTION_HOLDER.get();
if (!concurrentHashMap.containsKey(ds)) {
public static void putConnection(String xid, String ds, ConnectionProxy connection) {
Map<String, Map<String, ConnectionProxy>> concurrentHashMap = CONNECTION_HOLDER.get();
Map<String, ConnectionProxy> connectionProxyMap = concurrentHashMap.get(xid);
if (connectionProxyMap == null) {
connectionProxyMap = new ConcurrentHashMap<>();
concurrentHashMap.put(xid, connectionProxyMap);
}
if (!connectionProxyMap.containsKey(ds)) {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
concurrentHashMap.put(ds, connection);
connectionProxyMap.put(ds, connection);
}
}

public static ConnectionProxy getConnection(String ds) {
return CONNECTION_HOLDER.get().get(ds);
public static ConnectionProxy getConnection(String xid, String ds) {
Map<String, Map<String, ConnectionProxy>> concurrentHashMap = CONNECTION_HOLDER.get();
Map<String, ConnectionProxy> connectionProxyMap = concurrentHashMap.get(xid);
if (CollectionUtils.isEmpty(connectionProxyMap)) {
return null;
}
return connectionProxyMap.get(ds);
}

public static void notify(Boolean state) throws Exception {
public static void notify(String xid, Boolean state) throws Exception {
Exception exception = null;
Map<String, Map<String, ConnectionProxy>> concurrentHashMap = CONNECTION_HOLDER.get();
try {
Map<String, ConnectionProxy> concurrentHashMap = CONNECTION_HOLDER.get();
for (ConnectionProxy connectionProxy : concurrentHashMap.values()) {
if (CollectionUtils.isEmpty(concurrentHashMap)) {
return;
}
Map<String, ConnectionProxy> connectionProxyMap = concurrentHashMap.get(xid);
for (ConnectionProxy connectionProxy : connectionProxyMap.values()) {
try {
connectionProxy.notify(state);
if (connectionProxy != null) {
connectionProxy.notify(state);
}
} catch (SQLException e) {
exception = e;
}

}
} finally {
CONNECTION_HOLDER.remove();
concurrentHashMap.remove(xid);
if (exception != null) {
throw exception;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package com.baomidou.dynamic.datasource.tx;

import lombok.extern.slf4j.Slf4j;

import java.sql.*;
import java.util.Map;
import java.util.Properties;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

package com.baomidou.dynamic.datasource.tx;


public enum DsPropagation {
//支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
REQUIRED,
//新建事务,如果当前存在事务,把当前事务挂起。
REQUIRES_NEW,
//以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NOT_SUPPORTED,
//支持当前事务,如果当前没有事务,就以非事务方式执行。
SUPPORTS,
//以非事务方式执行,如果当前存在事务,则抛出异常。
NEVER,
//支持当前事务,如果当前没有事务,就抛出异常。
MANDATORY
}
18 changes: 10 additions & 8 deletions src/main/java/com/baomidou/dynamic/datasource/tx/LocalTxUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,24 @@ public final class LocalTxUtil {
/**
* 手动开启事务
*/
public static void startTransaction() {
if (!StringUtils.isEmpty(TransactionContext.getXID())) {
log.debug("dynamic-datasource exist local tx [{}]", TransactionContext.getXID());
public static String startTransaction() {
String xid = TransactionContext.getXID();
if (!StringUtils.isEmpty(xid)) {
log.debug("dynamic-datasource exist local tx [{}]", xid);
} else {
String xid = UUID.randomUUID().toString();
xid = UUID.randomUUID().toString();
TransactionContext.bind(xid);
log.debug("dynamic-datasource start local tx [{}]", xid);
}
return xid;
}

/**
* 手动提交事务
*/
public static void commit() throws Exception {
public static void commit(String xid) throws Exception {
try {
ConnectionFactory.notify(true);
ConnectionFactory.notify(xid, true);
} finally {
log.debug("dynamic-datasource commit local tx [{}]", TransactionContext.getXID());
TransactionContext.remove();
Expand All @@ -57,9 +59,9 @@ public static void commit() throws Exception {
/**
* 手动回滚事务
*/
public static void rollback() throws Exception {
public static void rollback(String xid) throws Exception {
try {
ConnectionFactory.notify(false);
ConnectionFactory.notify(xid, false);
} finally {
log.debug("dynamic-datasource rollback local tx [{}]", TransactionContext.getXID());
TransactionContext.remove();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.baomidou.dynamic.datasource.tx;

import javax.annotation.Nonnull;

public class SuspendedResourcesHolder {
/**
* The xid
*/
private String xid;

public SuspendedResourcesHolder(String xid) {
if (xid == null) {
throw new IllegalArgumentException("xid must be not null");
}
this.xid = xid;
}

@Nonnull
public String getXid() {
return xid;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.baomidou.dynamic.datasource.tx;



public interface TransactionalExecutor {

Object execute() throws Throwable;

TransactionalInfo getTransactionInfo();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.baomidou.dynamic.datasource.tx;

public class TransactionalInfo {

Class<? extends Throwable>[] rollbackFor;

Class<? extends Throwable>[] noRollbackFor;

DsPropagation propagation;

public Class<? extends Throwable>[] getRollbackFor() {
return rollbackFor;
}

public void setRollbackFor(Class<? extends Throwable>[] rollbackFor) {
this.rollbackFor = rollbackFor;
}

public Class<? extends Throwable>[] getNoRollbackFor() {
return noRollbackFor;
}

public void setNoRollbackFor(Class<? extends Throwable>[] noRollbackFor) {
this.noRollbackFor = noRollbackFor;
}

public DsPropagation getPropagation() {
return propagation;
}

public void setPropagation(DsPropagation propagation) {
this.propagation = propagation;
}
}
Loading

0 comments on commit e2e176d

Please sign in to comment.