This is an automated email from the ASF dual-hosted git repository.
zhangliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new a248c525dd2 Add more test cases on ProxyBackendTransactionManagerTest
(#37974)
a248c525dd2 is described below
commit a248c525dd26a611cdfbdd9fc090ca80164f291f
Author: Liang Zhang <[email protected]>
AuthorDate: Fri Feb 6 19:54:53 2026 +0800
Add more test cases on ProxyBackendTransactionManagerTest (#37974)
* Add more test cases on ProxyBackendTransactionManagerTest
* Add more test cases on ProxyBackendTransactionManagerTest
* Add more test cases on ProxyBackendTransactionManagerTest
* Add more test cases on ProxyBackendTransactionManagerTest
* Add more test cases on ProxyBackendTransactionManagerTest
---
.../ProxyBackendTransactionManagerTest.java | 400 ++++++++++++++++-----
1 file changed, 302 insertions(+), 98 deletions(-)
diff --git
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/jdbc/transaction/ProxyBackendTransactionManagerTest.java
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/jdbc/transaction/ProxyBackendTransactionManagerTest.java
index ffad8752776..be6d8d658e9 100644
---
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/jdbc/transaction/ProxyBackendTransactionManagerTest.java
+++
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/jdbc/transaction/ProxyBackendTransactionManagerTest.java
@@ -17,14 +17,20 @@
package org.apache.shardingsphere.proxy.backend.connector.jdbc.transaction;
-import com.google.common.collect.HashMultimap;
+import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import lombok.SneakyThrows;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
+import org.apache.shardingsphere.infra.rule.ShardingSphereRule;
import org.apache.shardingsphere.infra.session.connection.ConnectionContext;
import
org.apache.shardingsphere.infra.session.connection.transaction.TransactionConnectionContext;
+import org.apache.shardingsphere.infra.spi.type.ordered.OrderedSPILoader;
+import org.apache.shardingsphere.mode.exclusive.ExclusiveOperatorEngine;
+import
org.apache.shardingsphere.mode.exclusive.callback.ExclusiveOperationVoidCallback;
import org.apache.shardingsphere.mode.manager.ContextManager;
import
org.apache.shardingsphere.proxy.backend.connector.ProxyDatabaseConnectionManager;
+import
org.apache.shardingsphere.proxy.backend.connector.jdbc.connection.ConnectionPostProcessor;
import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
import
org.apache.shardingsphere.proxy.backend.session.transaction.TransactionStatus;
@@ -33,10 +39,17 @@ import
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockS
import
org.apache.shardingsphere.transaction.ShardingSphereTransactionManagerEngine;
import org.apache.shardingsphere.transaction.api.TransactionType;
import org.apache.shardingsphere.transaction.rule.TransactionRule;
+import
org.apache.shardingsphere.transaction.savepoint.ConnectionSavepointManager;
import
org.apache.shardingsphere.transaction.spi.ShardingSphereDistributedTransactionManager;
+import org.apache.shardingsphere.transaction.spi.TransactionHook;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Answers;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.junit.jupiter.MockitoSettings;
@@ -44,22 +57,37 @@ import org.mockito.quality.Strictness;
import java.sql.Connection;
import java.sql.SQLException;
+import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.stream.Stream;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyCollection;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(AutoMockExtension.class)
-@StaticMockSettings(ProxyContext.class)
+@StaticMockSettings({ProxyContext.class, OrderedSPILoader.class,
ConnectionSavepointManager.class})
@MockitoSettings(strictness = Strictness.LENIENT)
class ProxyBackendTransactionManagerTest {
- @Mock
- private ConnectionSession connectionSession;
+ private final TransactionConnectionContext transactionContext = new
TransactionConnectionContext();
+
+ private final Collection<ConnectionPostProcessor> connectionPostProcessors
= new LinkedList<>();
@Mock
private ProxyDatabaseConnectionManager databaseConnectionManager;
@@ -76,142 +104,318 @@ class ProxyBackendTransactionManagerTest {
@Mock
private Connection connection;
- private ProxyBackendTransactionManager transactionManager;
+ @Mock
+ private ConnectionContext connectionContext;
+
+ @Mock
+ private ExclusiveOperatorEngine exclusiveOperatorEngine;
@BeforeEach
void setUp() {
+ ConnectionSession connectionSession = mock(ConnectionSession.class);
when(connectionSession.getTransactionStatus()).thenReturn(transactionStatus);
when(databaseConnectionManager.getConnectionSession()).thenReturn(connectionSession);
-
when(databaseConnectionManager.getCachedConnections()).thenReturn(mockCachedConnections());
- ConnectionContext connectionContext = mock(ConnectionContext.class);
when(connectionSession.getConnectionContext()).thenReturn(connectionContext);
- TransactionConnectionContext context = new
TransactionConnectionContext();
- when(connectionContext.getTransactionContext()).thenReturn(context);
- }
-
- private Multimap<String, Connection> mockCachedConnections() {
- Multimap<String, Connection> result = HashMultimap.create();
- result.putAll("ds1", Collections.singleton(connection));
- return result;
+
when(connectionContext.getTransactionContext()).thenReturn(transactionContext);
+
when(databaseConnectionManager.getConnectionPostProcessors()).thenReturn(connectionPostProcessors);
+
when(databaseConnectionManager.getCachedConnections()).thenReturn(mockCachedConnections(connection));
}
+ @SneakyThrows(ReflectiveOperationException.class)
@Test
- void assertBeginForLocalTransaction() {
- ContextManager contextManager =
mockContextManager(TransactionType.LOCAL);
-
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
- newTransactionManager(TransactionType.LOCAL, false);
- transactionManager.begin();
- verify(transactionStatus).setInTransaction(true);
- verify(databaseConnectionManager).closeHandlers(true);
- verify(databaseConnectionManager).closeConnections(false);
- verify(localTransactionManager).begin();
+ void assertConstructorUseTransactionManagerFromContext() {
+ transactionContext.beginTransaction(TransactionType.XA.name(),
distributedTransactionManager);
+ mockProxyContext(TransactionType.XA, null, Collections.emptyMap());
+ ProxyBackendTransactionManager transactionManager = new
ProxyBackendTransactionManager(databaseConnectionManager);
+
assertThat(Plugins.getMemberAccessor().get(ProxyBackendTransactionManager.class.getDeclaredField("distributedTransactionManager"),
transactionManager), is(distributedTransactionManager));
}
- @Test
- void assertBeginForDistributedTransaction() {
- ContextManager contextManager = mockContextManager(TransactionType.XA);
-
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
- newTransactionManager(TransactionType.XA, false);
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("provideBeginScenarios")
+ void assertBeginScenarios(final String name, final TransactionType
defaultType, final boolean initialInTransaction, final boolean
hasDistributedManager,
+ final boolean expectLocalBegin, final boolean
expectDistributedBegin, final boolean expectCloseResources,
+ final boolean expectSetInTransaction, final
boolean expectTransactionTypeSet, final boolean hasHook) {
+
when(transactionStatus.isInTransaction()).thenReturn(initialInTransaction);
+ TransactionHook transactionHook = hasHook ?
mock(TransactionHook.class) : null;
+ Map<ShardingSphereRule, TransactionHook> transactionHooks = hasHook ?
Collections.singletonMap(mock(ShardingSphereRule.class), transactionHook) :
Collections.emptyMap();
+ ShardingSphereTransactionManagerEngine transactionManagerEngine =
hasDistributedManager ? mock(ShardingSphereTransactionManagerEngine.class) :
null;
+ if (hasDistributedManager) {
+
when(transactionManagerEngine.getTransactionManager(TransactionType.XA)).thenReturn(distributedTransactionManager);
+ }
+ mockProxyContext(defaultType, transactionManagerEngine,
transactionHooks);
+ ProxyBackendTransactionManager transactionManager = new
ProxyBackendTransactionManager(databaseConnectionManager);
+ setLocalTransactionManager(transactionManager);
transactionManager.begin();
- verify(transactionStatus, times(1)).setInTransaction(true);
- verify(databaseConnectionManager, times(1)).closeConnections(false);
- verify(distributedTransactionManager).begin();
+ if (expectCloseResources) {
+ verify(databaseConnectionManager).closeHandlers(true);
+ verify(databaseConnectionManager).closeConnections(false);
+ } else {
+ verify(databaseConnectionManager, never()).closeHandlers(true);
+ verify(databaseConnectionManager, never()).closeConnections(false);
+ }
+ if (expectSetInTransaction) {
+ verify(transactionStatus).setInTransaction(true);
+ } else {
+ verify(transactionStatus, never()).setInTransaction(true);
+ }
+ if (hasHook) {
+ verify(transactionHook).beforeBegin(any(),
any(DatabaseType.class), eq(transactionContext));
+ verify(transactionHook).afterBegin(any(), any(DatabaseType.class),
eq(transactionContext));
+ }
+ if (expectLocalBegin) {
+ verify(localTransactionManager).begin();
+ } else {
+ verify(localTransactionManager, never()).begin();
+ }
+ if (expectDistributedBegin) {
+ verify(distributedTransactionManager).begin();
+ } else {
+ verify(distributedTransactionManager, never()).begin();
+ }
+ if (expectTransactionTypeSet) {
+ assertTrue(transactionContext.isTransactionStarted());
+ assertThat(transactionContext.getTransactionType().get(),
is(defaultType.name()));
+ } else {
+ assertFalse(transactionContext.isTransactionStarted());
+ }
}
@Test
- void assertCommitForLocalTransaction() throws SQLException {
- ContextManager contextManager =
mockContextManager(TransactionType.LOCAL);
-
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
- newTransactionManager(TransactionType.LOCAL, true);
+ void assertCommitWithoutTransaction() throws SQLException {
+ mockProxyContext(TransactionType.LOCAL, null, Collections.emptyMap());
+ ProxyBackendTransactionManager transactionManager = new
ProxyBackendTransactionManager(databaseConnectionManager);
+ setLocalTransactionManager(transactionManager);
transactionManager.commit();
- verify(transactionStatus).setInTransaction(false);
- verify(localTransactionManager).commit();
+ verify(localTransactionManager, never()).commit();
+ verify(transactionStatus, never()).setInTransaction(false);
}
- @Test
- void assertCommitForDistributedTransaction() throws SQLException {
- ContextManager contextManager = mockContextManager(TransactionType.XA);
-
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
- newTransactionManager(TransactionType.XA, true);
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("provideCommitScenarios")
+ void assertCommitScenarios(final String name, final TransactionType
transactionType, final boolean needLock, final boolean hasDistributedManager,
+ final boolean exceptionOccurred, final boolean
hasHook, final boolean expectDistributedCommit) throws SQLException {
+ when(transactionStatus.isInTransaction()).thenReturn(true);
+ transactionContext.setExceptionOccur(exceptionOccurred);
+ ConnectionSavepointManager savepointManager =
mock(ConnectionSavepointManager.class);
+
when(ConnectionSavepointManager.getInstance()).thenReturn(savepointManager);
+ TransactionHook transactionHook = hasHook ?
mock(TransactionHook.class) : null;
+ if (hasHook) {
+
when(transactionHook.isNeedLockWhenCommit(any())).thenReturn(needLock);
+ }
+ Map<ShardingSphereRule, TransactionHook> transactionHooks = hasHook ?
Collections.singletonMap(mock(ShardingSphereRule.class), transactionHook) :
Collections.emptyMap();
+ ShardingSphereTransactionManagerEngine transactionManagerEngine =
hasDistributedManager ? mock(ShardingSphereTransactionManagerEngine.class) :
null;
+ if (hasDistributedManager) {
+
when(transactionManagerEngine.getTransactionManager(TransactionType.XA)).thenReturn(distributedTransactionManager);
+ }
+ if (needLock) {
+ doAnswer(invocation -> {
+ ExclusiveOperationVoidCallback callback =
invocation.getArgument(2);
+ callback.execute();
+ return null;
+ }).when(exclusiveOperatorEngine).operate(any(), anyLong(), any());
+ }
+ mockProxyContext(transactionType, transactionManagerEngine,
transactionHooks);
+ ProxyBackendTransactionManager transactionManager = new
ProxyBackendTransactionManager(databaseConnectionManager);
+ setLocalTransactionManager(transactionManager);
transactionManager.commit();
+ if (needLock) {
+ verify(exclusiveOperatorEngine).operate(any(), anyLong(), any());
+ } else {
+ verify(exclusiveOperatorEngine, never()).operate(any(), anyLong(),
any());
+ }
+ if (hasHook) {
+ verify(transactionHook).beforeCommit(any(),
any(DatabaseType.class), anyCollection(), eq(transactionContext));
+ }
+ if (expectDistributedCommit) {
+ verify(distributedTransactionManager).commit(exceptionOccurred);
+ } else {
+ verify(localTransactionManager).commit();
+ verify(distributedTransactionManager,
never()).commit(anyBoolean());
+ }
+ verify(savepointManager).transactionFinished(connection);
verify(transactionStatus).setInTransaction(false);
- verify(distributedTransactionManager).commit(false);
+ verify(connectionContext).close();
}
@Test
- void assertCommitWithoutTransaction() throws SQLException {
- ContextManager contextManager =
mockContextManager(TransactionType.LOCAL);
-
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
- newTransactionManager(TransactionType.LOCAL, false);
- transactionManager.commit();
+ void assertRollbackWithoutTransaction() throws SQLException {
+ mockProxyContext(TransactionType.LOCAL, null, Collections.emptyMap());
+ ProxyBackendTransactionManager transactionManager = new
ProxyBackendTransactionManager(databaseConnectionManager);
+ setLocalTransactionManager(transactionManager);
+ transactionManager.rollback();
+ verify(localTransactionManager, never()).rollback();
verify(transactionStatus, never()).setInTransaction(false);
- verify(localTransactionManager, never()).commit();
- verify(distributedTransactionManager, never()).commit(false);
}
- @Test
- void assertRollbackForLocalTransaction() throws SQLException {
- ContextManager contextManager =
mockContextManager(TransactionType.LOCAL);
-
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
- newTransactionManager(TransactionType.LOCAL, true);
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("provideRollbackScenarios")
+ void assertRollbackScenarios(final String name, final TransactionType
transactionType, final boolean secondInTransaction,
+ final boolean hasDistributedManager, final
boolean expectDistributedRollback, final boolean hasHook,
+ final boolean expectAfterHook, final boolean
expectClear) throws SQLException {
+ when(transactionStatus.isInTransaction()).thenReturn(true,
secondInTransaction);
+ ConnectionSavepointManager savepointManager =
mock(ConnectionSavepointManager.class);
+
when(ConnectionSavepointManager.getInstance()).thenReturn(savepointManager);
+ TransactionHook transactionHook = hasHook ?
mock(TransactionHook.class) : null;
+ Map<ShardingSphereRule, TransactionHook> transactionHooks = hasHook ?
Collections.singletonMap(mock(ShardingSphereRule.class), transactionHook) :
Collections.emptyMap();
+ ShardingSphereTransactionManagerEngine transactionManagerEngine =
hasDistributedManager ? mock(ShardingSphereTransactionManagerEngine.class) :
null;
+ if (hasDistributedManager) {
+
when(transactionManagerEngine.getTransactionManager(TransactionType.XA)).thenReturn(distributedTransactionManager);
+ }
+ mockProxyContext(transactionType, transactionManagerEngine,
transactionHooks);
+ ProxyBackendTransactionManager transactionManager = new
ProxyBackendTransactionManager(databaseConnectionManager);
+ setLocalTransactionManager(transactionManager);
transactionManager.rollback();
- verify(transactionStatus).setInTransaction(false);
- verify(localTransactionManager).rollback();
+ if (hasHook) {
+ verify(transactionHook).beforeRollback(any(),
any(DatabaseType.class), anyCollection(), eq(transactionContext));
+ }
+ if (expectDistributedRollback) {
+ verify(distributedTransactionManager).rollback();
+ } else if (expectClear) {
+ verify(localTransactionManager).rollback();
+ verify(distributedTransactionManager, never()).rollback();
+ } else {
+ verify(localTransactionManager, never()).rollback();
+ verify(distributedTransactionManager, never()).rollback();
+ }
+ if (hasHook) {
+ if (expectAfterHook) {
+ verify(transactionHook).afterRollback(any(),
any(DatabaseType.class), anyCollection(), eq(transactionContext));
+ } else {
+ verify(transactionHook, never()).afterRollback(any(),
any(DatabaseType.class), anyCollection(), eq(transactionContext));
+ }
+ }
+ if (expectClear) {
+ verify(savepointManager).transactionFinished(connection);
+ verify(transactionStatus).setInTransaction(false);
+ verify(connectionContext).close();
+ } else {
+ verify(savepointManager, never()).transactionFinished(any());
+ verify(transactionStatus, never()).setInTransaction(false);
+ verify(connectionContext, never()).close();
+ }
}
@Test
- void assertRollbackForDistributedTransaction() throws SQLException {
- ContextManager contextManager = mockContextManager(TransactionType.XA);
-
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
- newTransactionManager(TransactionType.XA, true);
- transactionManager.rollback();
- verify(transactionStatus).setInTransaction(false);
- verify(distributedTransactionManager).rollback();
+ void assertSetSavepointAddsPostProcessor() throws SQLException {
+ ConnectionSavepointManager savepointManager =
mock(ConnectionSavepointManager.class);
+
when(ConnectionSavepointManager.getInstance()).thenReturn(savepointManager);
+ mockProxyContext(TransactionType.LOCAL, null, Collections.emptyMap());
+ ProxyBackendTransactionManager transactionManager = new
ProxyBackendTransactionManager(databaseConnectionManager);
+ transactionManager.setSavepoint("sp");
+ verify(savepointManager).setSavepoint(connection, "sp");
+ Connection targetConnection = mock(Connection.class);
+ for (ConnectionPostProcessor each : connectionPostProcessors) {
+ each.process(targetConnection);
+ }
+ verify(savepointManager).setSavepoint(targetConnection, "sp");
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("provideRollbackToScenarios")
+ void assertRollbackToScenarios(final String name, final boolean
initialExceptionFlag, final int exceptionCount, final boolean expectFlagCleared,
+ final boolean expectThrows) throws
SQLException {
+ ConnectionSavepointManager savepointManager =
mock(ConnectionSavepointManager.class);
+
when(ConnectionSavepointManager.getInstance()).thenReturn(savepointManager);
+ transactionContext.setExceptionOccur(initialExceptionFlag);
+ if (exceptionCount == 2) {
+ Connection anotherConnection = mock(Connection.class);
+
when(databaseConnectionManager.getCachedConnections()).thenReturn(mockCachedConnections(connection,
anotherConnection));
+ doThrow(new
SQLException("first")).when(savepointManager).rollbackToSavepoint(connection,
"sp");
+ doThrow(new
SQLException("second")).when(savepointManager).rollbackToSavepoint(anotherConnection,
"sp");
+ } else {
+
when(databaseConnectionManager.getCachedConnections()).thenReturn(mockCachedConnections(connection));
+ }
+ mockProxyContext(TransactionType.LOCAL, null, Collections.emptyMap());
+ ProxyBackendTransactionManager transactionManager = new
ProxyBackendTransactionManager(databaseConnectionManager);
+ if (expectThrows) {
+ SQLException actualException = assertThrows(SQLException.class, ()
-> transactionManager.rollbackTo("sp"));
+ assertThat(actualException.getMessage(), is("first"));
+ assertThat(actualException.getNextException().getMessage(),
is("second"));
+ } else {
+ transactionManager.rollbackTo("sp");
+ verify(savepointManager).rollbackToSavepoint(connection, "sp");
+ if (exceptionCount == 0) {
+ assertFalse(transactionContext.isExceptionOccur());
+ }
+ if (expectFlagCleared) {
+ assertFalse(transactionContext.isExceptionOccur());
+ }
+ }
}
@Test
- void assertRollbackWithoutTransaction() throws SQLException {
- ContextManager contextManager =
mockContextManager(TransactionType.LOCAL);
-
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
- newTransactionManager(TransactionType.LOCAL, false);
- transactionManager.rollback();
- verify(transactionStatus, never()).setInTransaction(false);
- verify(localTransactionManager, never()).rollback();
- verify(distributedTransactionManager, never()).rollback();
+ void assertReleaseSavepointThrowsCombinedSQLException() throws
SQLException {
+ Connection anotherConnection = mock(Connection.class);
+
when(databaseConnectionManager.getCachedConnections()).thenReturn(mockCachedConnections(connection,
anotherConnection));
+ ConnectionSavepointManager savepointManager =
mock(ConnectionSavepointManager.class);
+
when(ConnectionSavepointManager.getInstance()).thenReturn(savepointManager);
+ doThrow(new
SQLException("first")).when(savepointManager).releaseSavepoint(connection,
"sp");
+ doThrow(new
SQLException("second")).when(savepointManager).releaseSavepoint(anotherConnection,
"sp");
+ mockProxyContext(TransactionType.LOCAL, null, Collections.emptyMap());
+ SQLException actualException = assertThrows(SQLException.class, () ->
new
ProxyBackendTransactionManager(databaseConnectionManager).releaseSavepoint("sp"));
+ assertThat(actualException.getMessage(), is("first"));
+ assertThat(actualException.getNextException().getMessage(),
is("second"));
}
- private void newTransactionManager(final TransactionType transactionType,
final boolean inTransaction) {
-
when(ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaData().getGlobalRuleMetaData().getSingleRule(TransactionRule.class).getDefaultType())
- .thenReturn(transactionType);
- when(transactionStatus.isInTransaction()).thenReturn(inTransaction);
- transactionManager = new
ProxyBackendTransactionManager(databaseConnectionManager);
- setLocalTransactionManager();
- setTransactionHooks();
+ @Test
+ void assertReleaseSavepointWithoutErrors() throws SQLException {
+ ConnectionSavepointManager savepointManager =
mock(ConnectionSavepointManager.class);
+
when(ConnectionSavepointManager.getInstance()).thenReturn(savepointManager);
+ mockProxyContext(TransactionType.LOCAL, null, Collections.emptyMap());
+ new
ProxyBackendTransactionManager(databaseConnectionManager).releaseSavepoint("sp");
+ verify(savepointManager).releaseSavepoint(connection, "sp");
+ }
+
+ private Multimap<String, Connection> mockCachedConnections(final
Connection... connections) {
+ Multimap<String, Connection> result = LinkedHashMultimap.create();
+ for (Connection each : connections) {
+ result.put("ds1", each);
+ }
+ return result;
+ }
+
+ private void mockProxyContext(final TransactionType defaultType, final
ShardingSphereTransactionManagerEngine engine, final Map<ShardingSphereRule,
TransactionHook> transactionHooks) {
+ TransactionRule transactionRule = mock(TransactionRule.class);
+ when(transactionRule.getDefaultType()).thenReturn(defaultType);
+ when(transactionRule.getResource()).thenReturn(engine);
+ ContextManager contextManager = mock(ContextManager.class,
Answers.RETURNS_DEEP_STUBS);
+
when(contextManager.getMetaDataContexts().getMetaData().getGlobalRuleMetaData()).thenReturn(new
RuleMetaData(Collections.singleton(transactionRule)));
+
when(contextManager.getExclusiveOperatorEngine()).thenReturn(exclusiveOperatorEngine);
+
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+ when(OrderedSPILoader.getServices(eq(TransactionHook.class),
ArgumentMatchers.<Collection<ShardingSphereRule>>any())).thenReturn(transactionHooks);
}
@SneakyThrows(ReflectiveOperationException.class)
- private void setLocalTransactionManager() {
+ private void setLocalTransactionManager(final
ProxyBackendTransactionManager transactionManager) {
Plugins.getMemberAccessor().set(ProxyBackendTransactionManager.class.getDeclaredField("localTransactionManager"),
transactionManager, localTransactionManager);
}
- @SneakyThrows(ReflectiveOperationException.class)
- private void setTransactionHooks() {
-
Plugins.getMemberAccessor().set(ProxyBackendTransactionManager.class.getDeclaredField("transactionHooks"),
transactionManager, Collections.emptyMap());
+ private static Stream<Arguments> provideBeginScenarios() {
+ return Stream.of(
+ Arguments.of("LOCAL start initializes transaction context",
TransactionType.LOCAL, false, false, true, false, true, true, true, true),
+ Arguments.of("Distributed begin when already in transaction",
TransactionType.XA, true, true, false, true, false, false, false, true),
+ Arguments.of("XA default falls back to local when manager
missing", TransactionType.XA, false, false, true, false, true, true, true,
false));
}
- private ContextManager mockContextManager(final TransactionType
transactionType) {
- ContextManager result = mock(ContextManager.class, RETURNS_DEEP_STUBS);
- RuleMetaData globalRuleMetaData =
mockGlobalRuleMetaData(transactionType);
-
when(result.getMetaDataContexts().getMetaData().getGlobalRuleMetaData()).thenReturn(globalRuleMetaData);
- return result;
+ private static Stream<Arguments> provideCommitScenarios() {
+ return Stream.of(
+ Arguments.of("Local commit without lock",
TransactionType.LOCAL, false, false, false, true, false),
+ Arguments.of("Distributed commit with lock",
TransactionType.XA, true, true, true, true, true),
+ Arguments.of("XA fallback commit uses local manager",
TransactionType.XA, false, false, false, false, false));
}
- private RuleMetaData mockGlobalRuleMetaData(final TransactionType
transactionType) {
- ShardingSphereTransactionManagerEngine transactionManagerEngine =
mock(ShardingSphereTransactionManagerEngine.class);
-
when(transactionManagerEngine.getTransactionManager(TransactionType.XA)).thenReturn(distributedTransactionManager);
- TransactionRule transactionRule = mock(TransactionRule.class);
- when(transactionRule.getDefaultType()).thenReturn(transactionType);
-
when(transactionRule.getResource()).thenReturn(transactionManagerEngine);
- return new RuleMetaData(Collections.singleton(transactionRule));
+ private static Stream<Arguments> provideRollbackScenarios() {
+ return Stream.of(
+ Arguments.of("Local rollback with hooks",
TransactionType.LOCAL, true, false, false, true, true, true),
+ Arguments.of("Distributed rollback skipped after status
cleared", TransactionType.XA, false, true, false, true, false, false),
+ Arguments.of("XA rollback falls back to local without
manager", TransactionType.XA, true, false, false, false, false, true),
+ Arguments.of("Distributed rollback executes manager",
TransactionType.XA, true, true, true, true, true, true));
+ }
+
+ private static Stream<Arguments> provideRollbackToScenarios() {
+ return Stream.of(
+ Arguments.of("Rollback to clears exception flag after
success", true, 0, true, false),
+ Arguments.of("Rollback to keeps flag when no exception
occurred", false, 0, false, false),
+ Arguments.of("Rollback to aggregates SQLException across
connections", false, 2, false, true));
}
}