From 2058b9b2652d1c2e29f4e8254fe472f1f3eed4cb Mon Sep 17 00:00:00 2001 From: Aliothmoon Date: Fri, 25 Oct 2024 14:19:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BA=8B=E5=8A=A1?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E6=97=B6=E9=97=B4=E9=97=AE=E9=A2=98=20&=20?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=E6=9B=B4=E5=AE=8C=E5=96=84=E7=9A=84Spring?= =?UTF-8?q?=E4=BA=8B=E5=8A=A1=E5=AE=9A=E4=B9=89=E4=B8=8A=E4=B8=8B=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/datasource/FlexDataSource.java | 3 +- .../spring/FlexSpringTransaction.java | 5 + .../spring/FlexTransactionManager.java | 7 + .../spring/TransactionDefinitionManager.java | 139 ++++++++++++++++++ .../test/service/AccountService.java | 13 +- 5 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 mybatis-flex-spring/src/main/java/com/mybatisflex/spring/TransactionDefinitionManager.java diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/FlexDataSource.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/FlexDataSource.java index ff1f0e5a..94edf97b 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/FlexDataSource.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/datasource/FlexDataSource.java @@ -176,7 +176,8 @@ public class FlexDataSource extends AbstractDataSource { public Connection proxy(Connection connection, String xid) { return (Connection) Proxy.newProxyInstance(FlexDataSource.class.getClassLoader() , new Class[]{Connection.class} - , new ConnectionHandler(connection, xid)); + , new ConnectionHandler(connection, xid) + ); } /** diff --git a/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/FlexSpringTransaction.java b/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/FlexSpringTransaction.java index f72e7ef7..6470c5ff 100644 --- a/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/FlexSpringTransaction.java +++ b/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/FlexSpringTransaction.java @@ -19,6 +19,7 @@ import com.mybatisflex.core.datasource.FlexDataSource; import com.mybatisflex.core.transaction.TransactionContext; import com.mybatisflex.core.util.StringUtil; import org.apache.ibatis.transaction.Transaction; +import org.springframework.transaction.TransactionDefinition; import java.sql.Connection; import java.sql.SQLException; @@ -82,6 +83,10 @@ public class FlexSpringTransaction implements Transaction { @Override public Integer getTimeout() throws SQLException { + TransactionDefinition definition = TransactionDefinitionManager.getTransactionDefinition(); + if (definition != null) { + return TransactionDefinitionManager.getTimeToLiveInSeconds(); + } return null; } } diff --git a/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/FlexTransactionManager.java b/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/FlexTransactionManager.java index 727ee868..3ec3e079 100644 --- a/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/FlexTransactionManager.java +++ b/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/FlexTransactionManager.java @@ -31,6 +31,7 @@ import org.springframework.transaction.support.DefaultTransactionStatus; */ public class FlexTransactionManager extends AbstractPlatformTransactionManager { + @Override protected Object doGetTransaction() throws TransactionException { return new TransactionObject(TransactionContext.getXID()); @@ -58,6 +59,7 @@ public class FlexTransactionManager extends AbstractPlatformTransactionManager { @Override protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { TransactionObject transactionObject = (TransactionObject) transaction; + TransactionDefinitionManager.setTransactionDefinition(definition); transactionObject.currentXid = TransactionalManager.startTransactional(); } @@ -84,9 +86,14 @@ public class FlexTransactionManager extends AbstractPlatformTransactionManager { transactionObject.setRollbackOnly(); } + @Override + protected void doCleanupAfterCompletion(Object transaction) { + TransactionDefinitionManager.clear(); + } static class TransactionObject extends JdbcTransactionObjectSupport { + private static final ThreadLocal ROLLBACK_ONLY_XIDS = new ThreadLocal<>(); private final String prevXid; diff --git a/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/TransactionDefinitionManager.java b/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/TransactionDefinitionManager.java new file mode 100644 index 00000000..444261d5 --- /dev/null +++ b/mybatis-flex-spring/src/main/java/com/mybatisflex/spring/TransactionDefinitionManager.java @@ -0,0 +1,139 @@ +package com.mybatisflex.spring; + +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionTimedOutException; + +import java.util.Date; + +/** + * 事务定义管理器 用于更完整的实现Spring事务 + * 仅支持传统事务不支持R2DBC事务 + * + * @author Aliothmoon + * @since 2024/10/25 + */ +public final class TransactionDefinitionManager { + private static final ThreadLocal TRANSACTION_DEFINITION = new ThreadLocal<>(); + private static final ThreadLocal TRANSACTION_DEADLINE = new ThreadLocal<>(); + + public static TransactionDefinition getTransactionDefinition() { + return TRANSACTION_DEFINITION.get(); + } + + public static void setTransactionDefinition(TransactionDefinition definition) { + if (definition == null) { + return; + } + int timeout = definition.getTimeout(); + + + Definition def = new Definition(); + def.setTimeout(timeout); + def.setIsolationLevel(definition.getIsolationLevel()); + def.setPropagationBehavior(definition.getPropagationBehavior()); + def.setIsolationLevel(definition.getIsolationLevel()); + def.setName(definition.getName()); + TRANSACTION_DEFINITION.set(def); + + if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { + Date deadline = new Date(System.currentTimeMillis() + timeout * 1000L); + TRANSACTION_DEADLINE.set(deadline); + } + } + + + /** + * 清除事务上下文 + * + */ + public static void clear() { + TRANSACTION_DEFINITION.remove(); + TRANSACTION_DEADLINE.remove(); + } + + /** + * 获取当前事务可用TTL + * + * @return int + */ + public static int getTimeToLiveInSeconds() { + Date deadline = TRANSACTION_DEADLINE.get(); + if (deadline == null) { + return 0; + } + double diff = ((double) getTimeToLiveInMillis(deadline)) / 1000; + int secs = (int) Math.ceil(diff); + checkTransactionTimeout(secs <= 0, deadline); + return secs; + } + + private static void checkTransactionTimeout(boolean deadlineReached, Date deadline) throws TransactionTimedOutException { + if (deadlineReached) { + throw new TransactionTimedOutException("Transaction timed out: deadline was " + deadline); + } + } + + private static long getTimeToLiveInMillis(Date deadline) throws TransactionTimedOutException { + if (deadline == null) { + throw new IllegalStateException("No timeout specified for this resource holder"); + } + long timeToLive = deadline.getTime() - System.currentTimeMillis(); + checkTransactionTimeout(timeToLive <= 0, deadline); + return timeToLive; + } + + private static class Definition implements TransactionDefinition { + private Integer propagationBehavior; + private Integer isolationLevel; + private Integer timeout; + private String name; + private Boolean readOnly; + + public void setReadOnly(Boolean readOnly) { + this.readOnly = readOnly; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } + + public void setIsolationLevel(Integer isolationLevel) { + this.isolationLevel = isolationLevel; + } + + public void setPropagationBehavior(Integer propagationBehavior) { + this.propagationBehavior = propagationBehavior; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int getPropagationBehavior() { + return propagationBehavior == null ? TransactionDefinition.super.getPropagationBehavior() : propagationBehavior; + } + + @Override + public int getIsolationLevel() { + return isolationLevel == null ? TransactionDefinition.super.getIsolationLevel() : isolationLevel; + } + + @Override + public int getTimeout() { + return timeout == null ? TransactionDefinition.super.getTimeout() : timeout; + } + + @Override + public boolean isReadOnly() { + return readOnly == null ? TransactionDefinition.super.isReadOnly() : readOnly; + } + + @Override + public String getName() { + return name == null ? TransactionDefinition.super.getName() : name; + } + } + + +} diff --git a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/service/AccountService.java b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/service/AccountService.java index 4d48e213..8c861642 100644 --- a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/service/AccountService.java +++ b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/main/java/com/mybatisflex/test/service/AccountService.java @@ -22,6 +22,7 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; +import java.util.concurrent.TimeUnit; @Component public class AccountService { @@ -33,11 +34,21 @@ public class AccountService { @Transactional public void update2() { - int x = 1/0; + int x = 1 / 0; Account account = new Account(); account.setId(2L); account.setUserName("haha"); accountMapper.update(account); } + @Transactional(rollbackFor = Exception.class, timeout = 3) + public void transactionTimeTest() throws InterruptedException { + Account account = new Account(); + account.setId(100L); + account.setUserName("aliothmoon"); + accountMapper.insert(account); + TimeUnit.SECONDS.sleep(5); + accountMapper.selectOneById(account.getId()); + } + }