This is an automated email from the ASF dual-hosted git repository.

jimin pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git


The following commit(s) were added to refs/heads/2.x by this push:
     new 9998778ed8 test: improve test coverage for rm-datasource (#7788)
9998778ed8 is described below

commit 9998778ed80e8bc8d898ec60c759d86cd06fb3b4
Author: Eric Wang <[email protected]>
AuthorDate: Wed Nov 19 11:29:21 2025 +0300

    test: improve test coverage for rm-datasource (#7788)
---
 changes/en-us/2.x.md                               |   7 +
 changes/zh-cn/2.x.md                               |   8 +-
 .../MySQLInsertOnDuplicateUpdateExecutor.java      |   3 +-
 .../sql/struct/cache/AbstractTableMetaCache.java   |  13 +-
 .../seata/rm/datasource/ConnectionProxyTest.java   | 707 ++++++++++++++++++++-
 ...MariadbInsertOnDuplicateUpdateExecutorTest.java |   2 -
 .../MySQLInsertOnDuplicateUpdateExecutorTest.java  | 562 +++++++++++++++-
 ...olarDBXInsertOnDuplicateUpdateExecutorTest.java |   2 -
 .../struct/cache/KingbaseTableMetaCacheTest.java   | 335 ++++++++++
 .../sql/struct/cache/OracleTableMetaCacheTest.java | 269 ++++++++
 ...CacheTest.java => OscarTableMetaCacheTest.java} |  53 +-
 .../seata/rm/datasource/util/JdbcUtilsTest.java    | 266 ++++++++
 12 files changed, 2182 insertions(+), 45 deletions(-)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index aab175dbe3..5e511e0a3a 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -105,6 +105,13 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#7757](https://github.com/apache/incubator-seata/pull/7757)] add UT for 
undo module
 - [[#7763](https://github.com/apache/incubator-seata/pull/7763)] add UT for 
RegistryNamingServerProperties and RegistryMetadataProperties
 - [[#7764](https://github.com/apache/incubator-seata/pull/7764)] add some UT 
for server/coordinator module
+- [[#7788](https://github.com/apache/incubator-seata/pull/7788)] add some UT 
for rm-datasource module
+- [[#7774](https://github.com/apache/incubator-seata/pull/7774)] add some UT 
for server/console module
+- [[#7767](https://github.com/apache/incubator-seata/pull/7767)] add some UT 
for server/cluster module
+- [[#7750](https://github.com/apache/incubator-seata/pull/7750)] add some UT 
for server module
+- [[#7733](https://github.com/apache/incubator-seata/pull/7733)] add some UT 
for core module
+- [[#7728](https://github.com/apache/incubator-seata/pull/7728)] add some UT 
for compatible module
+- [[#7727](https://github.com/apache/incubator-seata/pull/7727)] add some UT 
for compatible module
 
 
 ### refactor:
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 3774714a66..112388d45e 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -104,7 +104,13 @@
 - [[#7757](https://github.com/apache/incubator-seata/pull/7757)] 为 undo 模块添加单测
 - [[#7763](https://github.com/apache/incubator-seata/pull/7763)] 为 
RegistryNamingServerProperties 和 RegistryMetadataProperties 添加单测
 - [[#7764](https://github.com/apache/incubator-seata/pull/7764)] 为 
server/coordinator 模块添加单测
-
+- [[#7788](https://github.com/apache/incubator-seata/pull/7788)] 为 
rm-datasource 模块添加单测
+- [[#7774](https://github.com/apache/incubator-seata/pull/7774)] 为 
server/console 模块添加单测
+- [[#7767](https://github.com/apache/incubator-seata/pull/7767)] 为 
server/cluster 模块添加单测
+- [[#7750](https://github.com/apache/incubator-seata/pull/7750)] 为 server 
模块添加单测
+- [[#7733](https://github.com/apache/incubator-seata/pull/7733)] 为 core 模块添加单测
+- [[#7728](https://github.com/apache/incubator-seata/pull/7728)] 为 compatible 
模块添加单测
+- [[#7727](https://github.com/apache/incubator-seata/pull/7727)] 为 compatible 
模块添加单测
 ### refactor:
 
 - [[#7615](https://github.com/seata/seata/pull/7615)] 重构 DataSourceProxy
diff --git 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/mysql/MySQLInsertOnDuplicateUpdateExecutor.java
 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/mysql/MySQLInsertOnDuplicateUpdateExecutor.java
index 53bc05831d..5c3b2533b1 100644
--- 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/mysql/MySQLInsertOnDuplicateUpdateExecutor.java
+++ 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/mysql/MySQLInsertOnDuplicateUpdateExecutor.java
@@ -365,7 +365,8 @@ public class MySQLInsertOnDuplicateUpdateExecutor extends 
MySQLInsertExecutor im
             }
         }
         StringJoiner selectSQLJoin = new StringJoiner(", ", prefix, 
suffix.toString());
-        return selectSQLJoin.toString();
+        selectSQL = selectSQLJoin.toString();
+        return selectSQL;
     }
 
     /**
diff --git 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/AbstractTableMetaCache.java
 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/AbstractTableMetaCache.java
index 3caac9e4e9..eff2a9ba05 100755
--- 
a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/AbstractTableMetaCache.java
+++ 
b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/AbstractTableMetaCache.java
@@ -87,13 +87,16 @@ public abstract class AbstractTableMetaCache implements 
TableMetaCache {
     public void refresh(final Connection connection, String resourceId) {
         ConcurrentMap<String, TableMeta> tableMetaMap = 
TABLE_META_CACHE.asMap();
         for (Map.Entry<String, TableMeta> entry : tableMetaMap.entrySet()) {
-            String key = getCacheKey(connection, 
entry.getValue().getOriginalTableName(), resourceId);
+            TableMeta meta = entry.getValue();
+            String tableNameForKey = 
StringUtils.isBlank(meta.getOriginalTableName())
+                    ? meta.getTableName()
+                    : meta.getOriginalTableName();
+
+            String key = getCacheKey(connection, tableNameForKey, resourceId);
             if (entry.getKey().equals(key)) {
                 try {
-                    String freshTableName = 
StringUtils.isBlank(entry.getValue().getOriginalTableName())
-                            ? entry.getValue().getTableName()
-                            : entry.getValue().getOriginalTableName();
-                    TableMeta tableMeta = fetchSchema(connection, 
freshTableName);
+                    // Reuse tableNameForKey directly, no need to check again
+                    TableMeta tableMeta = fetchSchema(connection, 
tableNameForKey);
                     if (!tableMeta.equals(entry.getValue())) {
                         TABLE_META_CACHE.put(entry.getKey(), tableMeta);
                         LOGGER.info("table meta change was found, update table 
meta cache automatically.");
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/ConnectionProxyTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/ConnectionProxyTest.java
index 415028e175..6f82a14c32 100644
--- 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/ConnectionProxyTest.java
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/ConnectionProxyTest.java
@@ -20,10 +20,12 @@ import org.apache.seata.common.LockStrategyMode;
 import org.apache.seata.core.context.GlobalLockConfigHolder;
 import org.apache.seata.core.exception.TransactionException;
 import org.apache.seata.core.exception.TransactionExceptionCode;
+import org.apache.seata.core.model.BranchStatus;
 import org.apache.seata.core.model.BranchType;
 import org.apache.seata.core.model.GlobalLockConfig;
 import org.apache.seata.rm.DefaultResourceManager;
 import org.apache.seata.rm.datasource.ConnectionProxy.LockRetryPolicy;
+import org.apache.seata.rm.datasource.exec.LockConflictException;
 import org.apache.seata.rm.datasource.exec.LockWaitTimeoutException;
 import org.apache.seata.rm.datasource.mock.MockConnection;
 import org.apache.seata.rm.datasource.mock.MockDriver;
@@ -39,6 +41,8 @@ import org.mockito.Mockito;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import java.sql.SQLException;
+import java.sql.Savepoint;
 
 /**
  * ConnectionProxy test
@@ -58,6 +62,7 @@ public class ConnectionProxyTest {
     private static final String DB_TYPE = "mysql";
 
     private Field branchRollbackFlagField;
+    private boolean originalBranchRollbackFlag;
 
     @BeforeEach
     public void initBeforeEach() throws Exception {
@@ -67,8 +72,7 @@ public class ConnectionProxyTest {
         modifiersField.setAccessible(true);
         modifiersField.setInt(branchRollbackFlagField, 
branchRollbackFlagField.getModifiers() & ~Modifier.FINAL);
         branchRollbackFlagField.setAccessible(true);
-        boolean branchRollbackFlag = (boolean) 
branchRollbackFlagField.get(null);
-        Assertions.assertTrue(branchRollbackFlag);
+        originalBranchRollbackFlag = (boolean) 
branchRollbackFlagField.get(null);
 
         dataSourceProxy = Mockito.mock(DataSourceProxy.class);
         
Mockito.when(dataSourceProxy.getResourceId()).thenReturn(TEST_RESOURCE_ID);
@@ -88,32 +92,35 @@ public class ConnectionProxyTest {
         DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
     }
 
+    @org.junit.jupiter.api.AfterEach
+    public void cleanupAfterEach() throws Exception {
+        branchRollbackFlagField.set(null, originalBranchRollbackFlag);
+    }
+
     @Test
     public void testLockRetryPolicyRollbackOnConflict() throws Exception {
-        boolean oldBranchRollbackFlag = (boolean) 
branchRollbackFlagField.get(null);
         branchRollbackFlagField.set(null, true);
         GlobalLockConfig preGlobalLockConfig = new GlobalLockConfig();
         preGlobalLockConfig.setLockRetryTimes(0);
         preGlobalLockConfig.setLockRetryInterval(10);
         preGlobalLockConfig.setLockStrategyMode(LockStrategyMode.PESSIMISTIC);
         GlobalLockConfig globalLockConfig = 
GlobalLockConfigHolder.setAndReturnPrevious(preGlobalLockConfig);
-        ConnectionProxy connectionProxy =
-                new ConnectionProxy(dataSourceProxy, new MockConnection(new 
MockDriver(), "", null));
-        connectionProxy.bind(TEST_XID);
-        SQLUndoLog sqlUndoLog = new SQLUndoLog();
-        TableRecords beforeImage = new TableRecords();
-        beforeImage.add(new Row());
-        sqlUndoLog.setBeforeImage(beforeImage);
-        connectionProxy.getContext().appendUndoItem(sqlUndoLog);
-        connectionProxy.appendUndoLog(new SQLUndoLog());
-        connectionProxy.appendLockKey(lockKey);
-        Assertions.assertThrows(LockWaitTimeoutException.class, 
connectionProxy::commit);
-        branchRollbackFlagField.set(null, oldBranchRollbackFlag);
+        try (ConnectionProxy connectionProxy =
+                new ConnectionProxy(dataSourceProxy, new MockConnection(new 
MockDriver(), "", null))) {
+            connectionProxy.bind(TEST_XID);
+            SQLUndoLog sqlUndoLog = new SQLUndoLog();
+            TableRecords beforeImage = new TableRecords();
+            beforeImage.add(new Row());
+            sqlUndoLog.setBeforeImage(beforeImage);
+            connectionProxy.getContext().appendUndoItem(sqlUndoLog);
+            connectionProxy.appendUndoLog(new SQLUndoLog());
+            connectionProxy.appendLockKey(lockKey);
+            Assertions.assertThrows(LockWaitTimeoutException.class, 
connectionProxy::commit);
+        }
     }
 
     @Test
     public void testLockRetryPolicyNotRollbackOnConflict() throws Exception {
-        boolean oldBranchRollbackFlag = (boolean) 
branchRollbackFlagField.get(null);
         branchRollbackFlagField.set(null, false);
         GlobalLockConfig preGlobalLockConfig = new GlobalLockConfig();
         preGlobalLockConfig.setLockRetryTimes(30);
@@ -130,6 +137,672 @@ public class ConnectionProxyTest {
         sqlUndoLog.setBeforeImage(beforeImage);
         connectionProxy.getContext().appendUndoItem(sqlUndoLog);
         Assertions.assertThrows(LockWaitTimeoutException.class, 
connectionProxy::commit);
-        branchRollbackFlagField.set(null, oldBranchRollbackFlag);
+    }
+
+    @Test
+    public void testGetContext() throws Exception {
+        try (ConnectionProxy connectionProxy =
+                new ConnectionProxy(dataSourceProxy, new MockConnection(new 
MockDriver(), "", null))) {
+            Assertions.assertNotNull(connectionProxy.getContext());
+        }
+    }
+
+    @Test
+    public void testBindXid() throws Exception {
+        try (ConnectionProxy connectionProxy =
+                new ConnectionProxy(dataSourceProxy, new MockConnection(new 
MockDriver(), "", null))) {
+            connectionProxy.bind(TEST_XID);
+            Assertions.assertEquals(TEST_XID, 
connectionProxy.getContext().getXid());
+        }
+    }
+
+    @Test
+    public void testSetGlobalLockRequire() throws Exception {
+        try (ConnectionProxy connectionProxy =
+                new ConnectionProxy(dataSourceProxy, new MockConnection(new 
MockDriver(), "", null))) {
+            connectionProxy.setGlobalLockRequire(true);
+            Assertions.assertTrue(connectionProxy.isGlobalLockRequire());
+            connectionProxy.setGlobalLockRequire(false);
+            Assertions.assertFalse(connectionProxy.isGlobalLockRequire());
+        }
+    }
+
+    @Test
+    public void testAppendUndoLog() throws Exception {
+        try (ConnectionProxy connectionProxy =
+                new ConnectionProxy(dataSourceProxy, new MockConnection(new 
MockDriver(), "", null))) {
+            SQLUndoLog undoLog = new SQLUndoLog();
+            connectionProxy.appendUndoLog(undoLog);
+            Assertions.assertEquals(
+                    1, connectionProxy.getContext().getUndoItems().size());
+        }
+    }
+
+    @Test
+    public void testAppendLockKey() throws Exception {
+        try (ConnectionProxy connectionProxy =
+                new ConnectionProxy(dataSourceProxy, new MockConnection(new 
MockDriver(), "", null))) {
+            connectionProxy.appendLockKey("test:1");
+            connectionProxy.appendLockKey("test:2");
+            Assertions.assertNotNull(connectionProxy.getContext());
+        }
+    }
+
+    @Test
+    public void testGetTargetConnection() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            Assertions.assertEquals(mockConnection, 
connectionProxy.getTargetConnection());
+        }
+    }
+
+    @Test
+    public void testGetDataSourceProxy() throws Exception {
+        try (ConnectionProxy connectionProxy =
+                new ConnectionProxy(dataSourceProxy, new MockConnection(new 
MockDriver(), "", null))) {
+            Assertions.assertEquals(dataSourceProxy, 
connectionProxy.getDataSourceProxy());
+        }
+    }
+
+    @Test
+    public void testCheckLockWithBlankLockKeys() throws Exception {
+        try (ConnectionProxy connectionProxy =
+                new ConnectionProxy(dataSourceProxy, new MockConnection(new 
MockDriver(), "", null))) {
+            connectionProxy.bind(TEST_XID);
+            connectionProxy.checkLock("");
+            connectionProxy.checkLock(null);
+        }
+    }
+
+    @Test
+    public void testLockQueryWithBlankLockKeys() throws Exception {
+        try (ConnectionProxy connectionProxy =
+                new ConnectionProxy(dataSourceProxy, new MockConnection(new 
MockDriver(), "", null))) {
+            connectionProxy.bind(TEST_XID);
+            boolean result = connectionProxy.lockQuery("");
+            Assertions.assertFalse(result);
+        }
+    }
+
+    @Test
+    public void commitInGlobalTransactionTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.branchRegister(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.anyString(),
+                            Mockito.isNull(),
+                            Mockito.anyString(),
+                            Mockito.anyString(),
+                            Mockito.anyString()))
+                    .thenReturn(123456L);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.setAutoCommit(false);
+            connectionProxy.bind(TEST_XID);
+            SQLUndoLog sqlUndoLog = new SQLUndoLog();
+            TableRecords beforeImage = new TableRecords();
+            beforeImage.add(new Row());
+            sqlUndoLog.setBeforeImage(beforeImage);
+            connectionProxy.appendUndoLog(sqlUndoLog);
+            connectionProxy.appendLockKey(lockKey);
+
+            connectionProxy.commit();
+
+            Mockito.verify(rm)
+                    .branchRegister(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.anyString(),
+                            Mockito.isNull(),
+                            Mockito.eq(TEST_XID),
+                            Mockito.anyString(),
+                            Mockito.eq(lockKey));
+        }
+    }
+
+    @Test
+    public void commitWithGlobalLockRequireTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.lockQuery(
+                            Mockito.eq(BranchType.AT), Mockito.anyString(), 
Mockito.isNull(), Mockito.anyString()))
+                    .thenReturn(true);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.setAutoCommit(false);
+            connectionProxy.setGlobalLockRequire(true);
+            connectionProxy.appendLockKey(lockKey);
+
+            connectionProxy.commit();
+
+            Mockito.verify(rm)
+                    .lockQuery(Mockito.eq(BranchType.AT), Mockito.anyString(), 
Mockito.isNull(), Mockito.eq(lockKey));
+        }
+    }
+
+    @Test
+    public void commitWithoutTransactionTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            connectionProxy.setAutoCommit(false);
+
+            connectionProxy.commit();
+
+            Assertions.assertNull(connectionProxy.getContext().getXid());
+        }
+    }
+
+    @Test
+    public void commitWithSQLExceptionTest() throws Exception {
+        MockConnection mockConnection = Mockito.mock(MockConnection.class);
+        Mockito.when(mockConnection.getAutoCommit()).thenReturn(false);
+        Mockito.doThrow(new SQLException("Commit 
failed")).when(mockConnection).commit();
+        Mockito.doNothing().when(mockConnection).rollback();
+
+        ConnectionProxy connectionProxy = new ConnectionProxy(dataSourceProxy, 
mockConnection);
+        connectionProxy.setAutoCommit(false);
+
+        Assertions.assertThrows(SQLException.class, connectionProxy::commit);
+
+        Mockito.verify(mockConnection).rollback();
+    }
+
+    @Test
+    public void rollbackTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            connectionProxy.setAutoCommit(false);
+
+            connectionProxy.rollback();
+
+            Assertions.assertNull(connectionProxy.getContext().getBranchId());
+        }
+    }
+
+    @Test
+    public void rollbackWithBranchRegisteredTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.branchRegister(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.anyString(),
+                            Mockito.isNull(),
+                            Mockito.anyString(),
+                            Mockito.anyString(),
+                            Mockito.anyString()))
+                    .thenReturn(123456L);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.setAutoCommit(false);
+            connectionProxy.bind(TEST_XID);
+            SQLUndoLog sqlUndoLog = new SQLUndoLog();
+            TableRecords beforeImage = new TableRecords();
+            beforeImage.add(new Row());
+            sqlUndoLog.setBeforeImage(beforeImage);
+            connectionProxy.appendUndoLog(sqlUndoLog);
+            connectionProxy.appendLockKey(lockKey);
+
+            connectionProxy.commit();
+
+            connectionProxy.bind(TEST_XID);
+            connectionProxy.getContext().setBranchId(123456L);
+            connectionProxy.rollback();
+
+            Mockito.verify(rm)
+                    .branchReport(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.eq(TEST_XID),
+                            Mockito.eq(123456L),
+                            Mockito.eq(BranchStatus.PhaseOne_Failed),
+                            Mockito.isNull());
+        }
+    }
+
+    @Test
+    public void registerSuccessTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.branchRegister(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.eq(TEST_RESOURCE_ID),
+                            Mockito.isNull(),
+                            Mockito.eq(TEST_XID),
+                            Mockito.anyString(),
+                            Mockito.eq(lockKey)))
+                    .thenReturn(789L);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.setAutoCommit(false);
+            connectionProxy.bind(TEST_XID);
+            SQLUndoLog sqlUndoLog = new SQLUndoLog();
+            TableRecords beforeImage = new TableRecords();
+            beforeImage.add(new Row());
+            sqlUndoLog.setBeforeImage(beforeImage);
+            connectionProxy.appendUndoLog(sqlUndoLog);
+            connectionProxy.appendLockKey(lockKey);
+
+            connectionProxy.commit();
+
+            Mockito.verify(rm)
+                    .branchRegister(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.eq(TEST_RESOURCE_ID),
+                            Mockito.isNull(),
+                            Mockito.eq(TEST_XID),
+                            Mockito.anyString(),
+                            Mockito.eq(lockKey));
+        }
+    }
+
+    @Test
+    public void registerWithNoUndoLogTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.setAutoCommit(false);
+            connectionProxy.bind(TEST_XID);
+            connectionProxy.appendLockKey(lockKey);
+
+            connectionProxy.commit();
+
+            Mockito.verify(rm, Mockito.never())
+                    .branchRegister(
+                            Mockito.any(),
+                            Mockito.anyString(),
+                            Mockito.any(),
+                            Mockito.anyString(),
+                            Mockito.anyString(),
+                            Mockito.anyString());
+        }
+    }
+
+    @Test
+    public void registerWithNoLockKeyTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.setAutoCommit(false);
+            connectionProxy.bind(TEST_XID);
+
+            connectionProxy.commit();
+
+            Mockito.verify(rm, Mockito.never())
+                    .branchRegister(
+                            Mockito.any(),
+                            Mockito.anyString(),
+                            Mockito.any(),
+                            Mockito.anyString(),
+                            Mockito.anyString(),
+                            Mockito.anyString());
+        }
+    }
+
+    @Test
+    public void reportSuccessTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.branchRegister(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.anyString(),
+                            Mockito.isNull(),
+                            Mockito.anyString(),
+                            Mockito.anyString(),
+                            Mockito.anyString()))
+                    .thenReturn(999L);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.setAutoCommit(false);
+            connectionProxy.bind(TEST_XID);
+            SQLUndoLog sqlUndoLog = new SQLUndoLog();
+            TableRecords beforeImage = new TableRecords();
+            beforeImage.add(new Row());
+            sqlUndoLog.setBeforeImage(beforeImage);
+            connectionProxy.appendUndoLog(sqlUndoLog);
+            connectionProxy.appendLockKey(lockKey);
+
+            connectionProxy.commit();
+
+            if (ConnectionProxy.IS_REPORT_SUCCESS_ENABLE) {
+                Mockito.verify(rm)
+                        .branchReport(
+                                Mockito.eq(BranchType.AT),
+                                Mockito.eq(TEST_XID),
+                                Mockito.eq(999L),
+                                Mockito.eq(BranchStatus.PhaseOne_Done),
+                                Mockito.isNull());
+            }
+        }
+    }
+
+    @Test
+    public void reportFailureTest() throws Exception {
+        MockConnection mockConnection = Mockito.mock(MockConnection.class);
+        Mockito.when(mockConnection.getAutoCommit()).thenReturn(false);
+        Mockito.doThrow(new SQLException("Commit 
failed")).when(mockConnection).commit();
+
+        ConnectionProxy connectionProxy = new ConnectionProxy(dataSourceProxy, 
mockConnection);
+
+        DefaultResourceManager rm = Mockito.mock(DefaultResourceManager.class);
+        Mockito.when(rm.branchRegister(
+                        Mockito.eq(BranchType.AT),
+                        Mockito.anyString(),
+                        Mockito.isNull(),
+                        Mockito.anyString(),
+                        Mockito.anyString(),
+                        Mockito.anyString()))
+                .thenReturn(888L);
+        DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+        connectionProxy.setAutoCommit(false);
+        connectionProxy.bind(TEST_XID);
+        SQLUndoLog sqlUndoLog = new SQLUndoLog();
+        TableRecords beforeImage = new TableRecords();
+        beforeImage.add(new Row());
+        sqlUndoLog.setBeforeImage(beforeImage);
+        connectionProxy.appendUndoLog(sqlUndoLog);
+        connectionProxy.appendLockKey(lockKey);
+
+        Assertions.assertThrows(SQLException.class, connectionProxy::commit);
+
+        Mockito.verify(rm, Mockito.times(2))
+                .branchReport(
+                        Mockito.eq(BranchType.AT),
+                        Mockito.eq(TEST_XID),
+                        Mockito.eq(888L),
+                        Mockito.eq(BranchStatus.PhaseOne_Failed),
+                        Mockito.isNull());
+    }
+
+    @Test
+    public void reportWithRetryTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.branchRegister(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.anyString(),
+                            Mockito.isNull(),
+                            Mockito.anyString(),
+                            Mockito.anyString(),
+                            Mockito.anyString()))
+                    .thenReturn(777L);
+            Mockito.doThrow(new TransactionException("Report failed"))
+                    .doNothing()
+                    .when(rm)
+                    .branchReport(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.eq(TEST_XID),
+                            Mockito.eq(777L),
+                            Mockito.eq(BranchStatus.PhaseOne_Done),
+                            Mockito.isNull());
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.setAutoCommit(false);
+            connectionProxy.bind(TEST_XID);
+            SQLUndoLog sqlUndoLog = new SQLUndoLog();
+            TableRecords beforeImage = new TableRecords();
+            beforeImage.add(new Row());
+            sqlUndoLog.setBeforeImage(beforeImage);
+            connectionProxy.appendUndoLog(sqlUndoLog);
+            connectionProxy.appendLockKey(lockKey);
+
+            connectionProxy.commit();
+
+            if (ConnectionProxy.IS_REPORT_SUCCESS_ENABLE) {
+                Mockito.verify(rm, Mockito.atLeast(1))
+                        .branchReport(
+                                Mockito.eq(BranchType.AT),
+                                Mockito.eq(TEST_XID),
+                                Mockito.eq(777L),
+                                Mockito.eq(BranchStatus.PhaseOne_Done),
+                                Mockito.isNull());
+            }
+        }
+    }
+
+    @Test
+    public void setAutoCommitToTrueInGlobalTransactionTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.branchRegister(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.anyString(),
+                            Mockito.isNull(),
+                            Mockito.anyString(),
+                            Mockito.anyString(),
+                            Mockito.anyString()))
+                    .thenReturn(555L);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.setAutoCommit(false);
+            connectionProxy.bind(TEST_XID);
+            SQLUndoLog sqlUndoLog = new SQLUndoLog();
+            TableRecords beforeImage = new TableRecords();
+            beforeImage.add(new Row());
+            sqlUndoLog.setBeforeImage(beforeImage);
+            connectionProxy.appendUndoLog(sqlUndoLog);
+            connectionProxy.appendLockKey(lockKey);
+
+            connectionProxy.setAutoCommit(true);
+
+            Mockito.verify(rm)
+                    .branchRegister(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.anyString(),
+                            Mockito.isNull(),
+                            Mockito.eq(TEST_XID),
+                            Mockito.anyString(),
+                            Mockito.eq(lockKey));
+            Assertions.assertTrue(mockConnection.getAutoCommit());
+        }
+    }
+
+    @Test
+    public void setAutoCommitToTrueWithGlobalLockTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.lockQuery(
+                            Mockito.eq(BranchType.AT), Mockito.anyString(), 
Mockito.isNull(), Mockito.anyString()))
+                    .thenReturn(true);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.setAutoCommit(false);
+            connectionProxy.setGlobalLockRequire(true);
+            connectionProxy.appendLockKey(lockKey);
+
+            connectionProxy.setAutoCommit(true);
+
+            Mockito.verify(rm)
+                    .lockQuery(Mockito.eq(BranchType.AT), Mockito.anyString(), 
Mockito.isNull(), Mockito.eq(lockKey));
+            Assertions.assertTrue(mockConnection.getAutoCommit());
+        }
+    }
+
+    @Test
+    public void checkLockWithRealLockKeysTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.lockQuery(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.eq(TEST_RESOURCE_ID),
+                            Mockito.eq(TEST_XID),
+                            Mockito.eq(lockKey)))
+                    .thenReturn(true);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.bind(TEST_XID);
+
+            connectionProxy.checkLock(lockKey);
+
+            Mockito.verify(rm)
+                    .lockQuery(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.eq(TEST_RESOURCE_ID),
+                            Mockito.eq(TEST_XID),
+                            Mockito.eq(lockKey));
+        }
+    }
+
+    @Test
+    public void checkLockWithConflictTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.lockQuery(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.eq(TEST_RESOURCE_ID),
+                            Mockito.eq(TEST_XID),
+                            Mockito.eq(lockKey)))
+                    .thenReturn(false);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.bind(TEST_XID);
+
+            Assertions.assertThrows(LockConflictException.class, () -> 
connectionProxy.checkLock(lockKey));
+        }
+    }
+
+    @Test
+    public void lockQueryReturnsTrueTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.lockQuery(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.eq(TEST_RESOURCE_ID),
+                            Mockito.eq(TEST_XID),
+                            Mockito.eq(lockKey)))
+                    .thenReturn(true);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.bind(TEST_XID);
+
+            boolean result = connectionProxy.lockQuery(lockKey);
+
+            Assertions.assertTrue(result);
+        }
+    }
+
+    @Test
+    public void lockQueryReturnsFalseTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.lockQuery(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.eq(TEST_RESOURCE_ID),
+                            Mockito.eq(TEST_XID),
+                            Mockito.eq(lockKey)))
+                    .thenReturn(false);
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.bind(TEST_XID);
+
+            boolean result = connectionProxy.lockQuery(lockKey);
+
+            Assertions.assertFalse(result);
+        }
+    }
+
+    @Test
+    public void setSavepointTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            Savepoint savepoint = connectionProxy.setSavepoint();
+
+            Assertions.assertNotNull(savepoint);
+        }
+    }
+
+    @Test
+    public void setSavepointWithNameTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            String savepointName = "sp1";
+            Savepoint savepoint = connectionProxy.setSavepoint(savepointName);
+
+            Assertions.assertNotNull(savepoint);
+        }
+    }
+
+    @Test
+    public void rollbackToSavepointTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            Savepoint savepoint = connectionProxy.setSavepoint();
+            Assertions.assertNotNull(savepoint);
+
+            connectionProxy.rollback(savepoint);
+
+            Assertions.assertNotNull(connectionProxy.getContext());
+        }
+    }
+
+    @Test
+    public void releaseSavepointTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            Savepoint savepoint = connectionProxy.setSavepoint();
+            Assertions.assertNotNull(savepoint);
+
+            connectionProxy.releaseSavepoint(savepoint);
+
+            Assertions.assertNotNull(connectionProxy.getContext());
+        }
+    }
+
+    @Test
+    public void recognizeLockKeyConflictExceptionTest() throws Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.lockQuery(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.eq(TEST_RESOURCE_ID),
+                            Mockito.eq(TEST_XID),
+                            Mockito.eq(lockKey)))
+                    .thenThrow(new 
TransactionException(TransactionExceptionCode.LockKeyConflict, "lock 
conflict"));
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.bind(TEST_XID);
+
+            LockConflictException exception =
+                    Assertions.assertThrows(LockConflictException.class, () -> 
connectionProxy.checkLock(lockKey));
+            Assertions.assertEquals(TransactionExceptionCode.LockKeyConflict, 
exception.getCode());
+        }
+    }
+
+    @Test
+    public void recognizeLockKeyConflictFailFastExceptionTest() throws 
Exception {
+        MockConnection mockConnection = new MockConnection(new MockDriver(), 
"", null);
+        try (ConnectionProxy connectionProxy = new 
ConnectionProxy(dataSourceProxy, mockConnection)) {
+            DefaultResourceManager rm = 
Mockito.mock(DefaultResourceManager.class);
+            Mockito.when(rm.lockQuery(
+                            Mockito.eq(BranchType.AT),
+                            Mockito.eq(TEST_RESOURCE_ID),
+                            Mockito.eq(TEST_XID),
+                            Mockito.eq(lockKey)))
+                    .thenThrow(new TransactionException(
+                            TransactionExceptionCode.LockKeyConflictFailFast, 
"lock conflict fail fast"));
+            DefaultResourceManager.mockResourceManager(BranchType.AT, rm);
+
+            connectionProxy.bind(TEST_XID);
+
+            LockConflictException exception =
+                    Assertions.assertThrows(LockConflictException.class, () -> 
connectionProxy.checkLock(lockKey));
+            
Assertions.assertEquals(TransactionExceptionCode.LockKeyConflictFailFast, 
exception.getCode());
+        }
     }
 }
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/MariadbInsertOnDuplicateUpdateExecutorTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/MariadbInsertOnDuplicateUpdateExecutorTest.java
index c0048bc84e..f64866d69f 100644
--- 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/MariadbInsertOnDuplicateUpdateExecutorTest.java
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/MariadbInsertOnDuplicateUpdateExecutorTest.java
@@ -43,8 +43,6 @@ import static org.mockito.Mockito.when;
 
 public class MariadbInsertOnDuplicateUpdateExecutorTest extends 
MySQLInsertOnDuplicateUpdateExecutorTest {
 
-    protected MariadbInsertOnDuplicateUpdateExecutor insertOrUpdateExecutor;
-
     @BeforeEach
     @Override
     public void init() {
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/MySQLInsertOnDuplicateUpdateExecutorTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/MySQLInsertOnDuplicateUpdateExecutorTest.java
index b18044beef..3d5919801b 100644
--- 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/MySQLInsertOnDuplicateUpdateExecutorTest.java
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/MySQLInsertOnDuplicateUpdateExecutorTest.java
@@ -18,12 +18,19 @@ package org.apache.seata.rm.datasource.exec;
 
 import com.google.common.collect.Lists;
 import org.apache.seata.common.exception.NotSupportYetException;
+import org.apache.seata.common.exception.ShouldNeverHappenException;
+import org.apache.seata.common.util.LowerCaseLinkHashMap;
 import org.apache.seata.rm.datasource.ConnectionProxy;
 import org.apache.seata.rm.datasource.PreparedStatementProxy;
 import org.apache.seata.rm.datasource.StatementProxy;
 import 
org.apache.seata.rm.datasource.exec.mysql.MySQLInsertOnDuplicateUpdateExecutor;
+import org.apache.seata.rm.datasource.sql.struct.Field;
+import org.apache.seata.rm.datasource.sql.struct.KeyType;
+import org.apache.seata.rm.datasource.sql.struct.Row;
 import org.apache.seata.rm.datasource.sql.struct.TableRecords;
+import org.apache.seata.rm.datasource.undo.SQLUndoLog;
 import org.apache.seata.sqlparser.SQLInsertRecognizer;
+import org.apache.seata.sqlparser.SQLType;
 import org.apache.seata.sqlparser.struct.ColumnMeta;
 import org.apache.seata.sqlparser.struct.IndexMeta;
 import org.apache.seata.sqlparser.struct.IndexType;
@@ -34,6 +41,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
+import java.lang.reflect.Method;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -43,8 +51,12 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 public class MySQLInsertOnDuplicateUpdateExecutorTest {
@@ -171,21 +183,150 @@ public class MySQLInsertOnDuplicateUpdateExecutorTest {
         });
     }
 
-    protected void mockAllIndexes() {
+    @Test
+    public void testBuildImageParametersWithUpdatePrimaryKey() {
+        mockParameters();
+        mockInsertColumns();
+        List<String> duplicateKeyUpdateColumns = new ArrayList<>();
+        duplicateKeyUpdateColumns.add("id");
+        
when(sqlInsertRecognizer.getDuplicateKeyUpdate()).thenReturn(duplicateKeyUpdateColumns);
+
+        mockAllIndexes();
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+
+        List<List<Object>> rows = new ArrayList<>();
+        rows.add(Arrays.asList("?", "?", "?", "?"));
+        
when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows);
+
+        Assertions.assertThrows(ShouldNeverHappenException.class, () -> {
+            insertOrUpdateExecutor.buildImageParameters(sqlInsertRecognizer);
+        });
+    }
+
+    @Test
+    public void testBuildImageParametersWithEmptyInsertColumns() {
+        mockParameters();
+        when(sqlInsertRecognizer.getInsertColumns()).thenReturn(null);
+
+        Map<String, ColumnMeta> allColumns = new LinkedHashMap<>();
+        allColumns.put("id", new ColumnMeta());
+        allColumns.put("user_id", new ColumnMeta());
+        allColumns.put("user_name", new ColumnMeta());
+        allColumns.put("user_status", new ColumnMeta());
+        when(tableMeta.getAllColumns()).thenReturn(allColumns);
+
+        List<List<Object>> rows = new ArrayList<>();
+        rows.add(Arrays.asList("?", "?", "?", "?"));
+        
when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows);
+
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+
+        Map<String, ArrayList<Object>> result = 
insertOrUpdateExecutor.buildImageParameters(sqlInsertRecognizer);
+        Assertions.assertNotNull(result);
+    }
+
+    @Test
+    public void testBuildImageSQLWithDefaultValue() {
+        mockParametersWithoutPkColumn();
+        List<String> columns = new ArrayList<>();
+        columns.add(USER_ID_COLUMN);
+        columns.add(USER_NAME_COLUMN);
+        columns.add(USER_STATUS_COLUMN);
+        when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns);
+        Map<String, Integer> pkIndexWithoutId = new HashMap<>();
+        doReturn(pkIndexWithoutId).when(insertOrUpdateExecutor).getPkIndex();
+
         Map<String, IndexMeta> allIndex = new HashMap<>();
         IndexMeta primary = new IndexMeta();
         primary.setIndextype(IndexType.PRIMARY);
         ColumnMeta columnMeta = new ColumnMeta();
         columnMeta.setColumnName("id");
+        columnMeta.setColumnDef("AUTO_INCREMENT");
         primary.setValues(Lists.newArrayList(columnMeta));
-        allIndex.put("id", primary);
+        allIndex.put("PRIMARY", primary);
+        when(tableMeta.getAllIndexes()).thenReturn(allIndex);
+
+        List<List<Object>> insertRows = new ArrayList<>();
+        insertRows.add(Arrays.asList("?", "?", "?"));
+        
when(sqlInsertRecognizer.getInsertRows(pkIndexWithoutId.values())).thenReturn(insertRows);
+
+        String sql = insertOrUpdateExecutor.buildImageSQL(tableMeta);
+        Assertions.assertTrue(sql.contains("DEFAULT"));
+    }
+
+    @Test
+    public void testBuildTableRecords2WithEmptyParamAppenderList() {
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+
+        Assertions.assertThrows(NotSupportYetException.class, () -> {
+            insertOrUpdateExecutor.buildTableRecords2(
+                    tableMeta, "SELECT * FROM test", new ArrayList<>(), 
Collections.emptyList());
+        });
+    }
+
+    @Test
+    public void testBuildImageParametersWithRowSizeMismatch() {
+        mockParameters();
+        mockInsertColumns();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
 
+        List<List<Object>> rows = new ArrayList<>();
+        rows.add(Arrays.asList("?", "?"));
+        
when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows);
+
+        Assertions.assertThrows(IllegalArgumentException.class, () -> {
+            insertOrUpdateExecutor.buildImageParameters(sqlInsertRecognizer);
+        });
+    }
+
+    @Test
+    public void testGetSelectSQL() {
+        mockParameters();
+        mockInsertColumns();
+        mockAllIndexes();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+
+        List<List<Object>> insertRows = new ArrayList<>();
+        insertRows.add(Arrays.asList("?", "?", "?", "?"));
+        
when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(insertRows);
+
+        insertOrUpdateExecutor.buildImageSQL(tableMeta);
+        Assertions.assertNotNull(insertOrUpdateExecutor.getSelectSQL());
+    }
+
+    @Test
+    public void testGetParamAppenderList() {
+        mockParameters();
+        mockInsertColumns();
+        mockAllIndexes();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+
+        List<List<Object>> insertRows = new ArrayList<>();
+        insertRows.add(Arrays.asList("?", "?", "?", "?"));
+        
when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(insertRows);
+
+        insertOrUpdateExecutor.buildImageSQL(tableMeta);
+        
Assertions.assertNotNull(insertOrUpdateExecutor.getParamAppenderList());
+        
Assertions.assertTrue(insertOrUpdateExecutor.getParamAppenderList().size() > 0);
+    }
+
+    protected void mockAllIndexes() {
+        Map<String, IndexMeta> allIndex = new LowerCaseLinkHashMap<>();
         IndexMeta unique = new IndexMeta();
-        unique.setIndextype(IndexType.PRIMARY);
+        unique.setIndextype(IndexType.UNIQUE);
         ColumnMeta columnMetaUnique = new ColumnMeta();
         columnMetaUnique.setColumnName("user_id");
         unique.setValues(Lists.newArrayList(columnMetaUnique));
-        allIndex.put("user_id", unique);
+        allIndex.put("user_id_unique", unique);
+
+        IndexMeta primary = new IndexMeta();
+        primary.setIndextype(IndexType.PRIMARY);
+        ColumnMeta columnMeta = new ColumnMeta();
+        columnMeta.setColumnName("id");
+        primary.setValues(Lists.newArrayList(columnMeta));
+        allIndex.put("PRIMARY", primary);
         when(tableMeta.getAllIndexes()).thenReturn(allIndex);
     }
 
@@ -296,4 +437,417 @@ public class MySQLInsertOnDuplicateUpdateExecutorTest {
         rows.add(Arrays.asList("?", "?", "?", "?"));
         
when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows);
     }
+
+    protected void mockParametersWithoutPkColumn() {
+        Map<Integer, ArrayList<Object>> parameters = new HashMap<>(4);
+        ArrayList<Object> userId1 = new ArrayList<>();
+        userId1.add("userId1");
+        ArrayList<Object> userName1 = new ArrayList<>();
+        userName1.add("userName1");
+        ArrayList<Object> userStatus1 = new ArrayList<>();
+        userStatus1.add("userStatus1");
+        parameters.put(1, userId1);
+        parameters.put(2, userName1);
+        parameters.put(3, userStatus1);
+
+        ArrayList<Object> userId2 = new ArrayList<>();
+        userId2.add("userId2");
+        ArrayList<Object> userName2 = new ArrayList<>();
+        userName2.add("userName2");
+        ArrayList<Object> userStatus2 = new ArrayList<>();
+        userStatus2.add("userStatus2");
+        parameters.put(4, userId2);
+        parameters.put(5, userName2);
+        parameters.put(6, userStatus2);
+        PreparedStatementProxy psp = (PreparedStatementProxy) 
this.statementProxy;
+        when(psp.getParameters()).thenReturn(parameters);
+    }
+
+    @Test
+    public void executeAutoCommitFalseInsertScenarioTest() throws Exception {
+        mockParameters();
+        mockInsertColumns();
+        mockAllIndexes();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+        
when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Collections.singletonList(ID_COLUMN));
+
+        TableRecords emptyBeforeImage = TableRecords.empty(tableMeta);
+        TableRecords afterImage = new TableRecords(tableMeta);
+        doReturn(emptyBeforeImage).when(insertOrUpdateExecutor).beforeImage();
+
+        PreparedStatementProxy psp = (PreparedStatementProxy) statementProxy;
+        when(psp.getUpdateCount()).thenReturn(2);
+
+        StatementCallback callback = mock(StatementCallback.class);
+        when(callback.execute(any(), any())).thenReturn(null);
+        MySQLInsertOnDuplicateUpdateExecutor executor =
+                spy(new MySQLInsertOnDuplicateUpdateExecutor(statementProxy, 
callback, sqlInsertRecognizer));
+        doReturn(pkIndexMap).when(executor).getPkIndex();
+        doReturn(tableMeta).when(executor).getTableMeta();
+        doReturn("SELECT * FROM test_table WHERE id = 
?").when(executor).buildImageSQL(any(TableMeta.class));
+        doReturn(emptyBeforeImage).when(executor).beforeImage();
+        doReturn(afterImage)
+                .when(executor)
+                .buildTableRecords2(any(TableMeta.class), any(String.class), 
any(ArrayList.class), any(List.class));
+        
doReturn("test_lock_key").when(executor).buildLockKey(any(TableRecords.class));
+
+        java.lang.reflect.Field selectSQLField =
+                
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredField("selectSQL");
+        selectSQLField.setAccessible(true);
+        selectSQLField.set(executor, "SELECT * FROM test_table WHERE id = ?");
+
+        java.lang.reflect.Field paramAppenderListField =
+                
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredField("paramAppenderList");
+        paramAppenderListField.setAccessible(true);
+        ArrayList<List<Object>> paramList = new ArrayList<>();
+        paramList.add(Arrays.asList(100));
+        paramAppenderListField.set(executor, paramList);
+
+        Method method =
+                
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredMethod("executeAutoCommitFalse",
 Object[].class);
+        method.setAccessible(true);
+        Object result = method.invoke(executor, (Object) new Object[] {});
+
+        Assertions.assertNull(result);
+        verify(executor, times(1)).beforeImage();
+    }
+
+    @Test
+    public void executeAutoCommitFalseUpdateScenarioTest() throws Exception {
+        mockParameters();
+        mockInsertColumns();
+        mockAllIndexes();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+        
when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Collections.singletonList(ID_COLUMN));
+
+        TableRecords beforeImage = new TableRecords(tableMeta);
+        TableRecords afterImage = new TableRecords(tableMeta);
+        doReturn(beforeImage).when(insertOrUpdateExecutor).beforeImage();
+
+        PreparedStatementProxy psp = (PreparedStatementProxy) statementProxy;
+        when(psp.getUpdateCount()).thenReturn(1);
+
+        StatementCallback callback = mock(StatementCallback.class);
+        when(callback.execute(any(), any())).thenReturn(null);
+        MySQLInsertOnDuplicateUpdateExecutor executor =
+                spy(new MySQLInsertOnDuplicateUpdateExecutor(statementProxy, 
callback, sqlInsertRecognizer));
+        doReturn(pkIndexMap).when(executor).getPkIndex();
+        doReturn(tableMeta).when(executor).getTableMeta();
+        doReturn("SELECT * FROM test_table WHERE id = 
?").when(executor).buildImageSQL(any(TableMeta.class));
+        doReturn(beforeImage).when(executor).beforeImage();
+        doReturn(afterImage)
+                .when(executor)
+                .buildTableRecords2(any(TableMeta.class), any(String.class), 
any(ArrayList.class), any(List.class));
+        
doReturn("test_lock_key").when(executor).buildLockKey(any(TableRecords.class));
+
+        java.lang.reflect.Field selectSQLField =
+                
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredField("selectSQL");
+        selectSQLField.setAccessible(true);
+        selectSQLField.set(executor, "SELECT * FROM test_table WHERE id = ?");
+
+        java.lang.reflect.Field paramAppenderListField =
+                
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredField("paramAppenderList");
+        paramAppenderListField.setAccessible(true);
+        ArrayList<List<Object>> paramList = new ArrayList<>();
+        paramList.add(Arrays.asList(100));
+        paramAppenderListField.set(executor, paramList);
+
+        Method method =
+                
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredMethod("executeAutoCommitFalse",
 Object[].class);
+        method.setAccessible(true);
+        Object result = method.invoke(executor, (Object) new Object[] {});
+
+        Assertions.assertNull(result);
+        verify(executor, times(1)).beforeImage();
+    }
+
+    @Test
+    public void executeAutoCommitFalseZeroUpdateCountTest() throws Exception {
+        mockParameters();
+        mockInsertColumns();
+        mockAllIndexes();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+        
when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Collections.singletonList(ID_COLUMN));
+
+        TableRecords emptyBeforeImage = TableRecords.empty(tableMeta);
+        doReturn(emptyBeforeImage).when(insertOrUpdateExecutor).beforeImage();
+
+        PreparedStatementProxy psp = (PreparedStatementProxy) statementProxy;
+        when(psp.getUpdateCount()).thenReturn(0);
+
+        StatementCallback callback = mock(StatementCallback.class);
+        when(callback.execute(any(), any())).thenReturn(null);
+        MySQLInsertOnDuplicateUpdateExecutor executor =
+                spy(new MySQLInsertOnDuplicateUpdateExecutor(statementProxy, 
callback, sqlInsertRecognizer));
+        doReturn(pkIndexMap).when(executor).getPkIndex();
+        doReturn(tableMeta).when(executor).getTableMeta();
+        doReturn("SELECT * FROM test_table WHERE id = 
?").when(executor).buildImageSQL(any(TableMeta.class));
+        doReturn(emptyBeforeImage).when(executor).beforeImage();
+
+        Method method =
+                
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredMethod("executeAutoCommitFalse",
 Object[].class);
+        method.setAccessible(true);
+        Object result = method.invoke(executor, (Object) new Object[] {});
+
+        Assertions.assertNull(result);
+        verify(executor, times(1)).beforeImage();
+    }
+
+    @Test
+    public void executeAutoCommitFalseMultiPkNonMySQLTest() throws Exception {
+        ConnectionProxy oracleConnectionProxy = mock(ConnectionProxy.class);
+        
when(oracleConnectionProxy.getDbType()).thenReturn(JdbcConstants.ORACLE);
+
+        StatementProxy oracleStatementProxy = 
mock(PreparedStatementProxy.class);
+        
when(oracleStatementProxy.getConnectionProxy()).thenReturn(oracleConnectionProxy);
+
+        TableMeta multiPkTableMeta = mock(TableMeta.class);
+        
when(multiPkTableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList("id", 
"user_id"));
+
+        StatementCallback callback = mock(StatementCallback.class);
+        MySQLInsertOnDuplicateUpdateExecutor executor =
+                spy(new 
MySQLInsertOnDuplicateUpdateExecutor(oracleStatementProxy, callback, 
sqlInsertRecognizer));
+        doReturn(multiPkTableMeta).when(executor).getTableMeta();
+        doReturn(JdbcConstants.ORACLE).when(executor).getDbType();
+
+        Method method =
+                
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredMethod("executeAutoCommitFalse",
 Object[].class);
+        method.setAccessible(true);
+
+        
Assertions.assertThrows(java.lang.reflect.InvocationTargetException.class, () 
-> {
+            method.invoke(executor, (Object) new Object[] {});
+        });
+    }
+
+    @Test
+    public void buildUndoItemInsertTypeTest() throws Exception {
+        mockParameters();
+        mockInsertColumns();
+        mockAllIndexes();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+        when(sqlInsertRecognizer.getTableName()).thenReturn("test_table");
+
+        TableRecords emptyBeforeImage = TableRecords.empty(tableMeta);
+        TableRecords afterImage = new TableRecords(tableMeta);
+
+        Method method = 
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredMethod(
+                "buildUndoItem", SQLType.class, TableRecords.class, 
TableRecords.class);
+        method.setAccessible(true);
+        SQLUndoLog result =
+                (SQLUndoLog) method.invoke(insertOrUpdateExecutor, 
SQLType.INSERT, emptyBeforeImage, afterImage);
+
+        Assertions.assertNotNull(result);
+        Assertions.assertEquals(SQLType.INSERT, result.getSqlType());
+        Assertions.assertEquals("test_table", result.getTableName());
+        Assertions.assertEquals(emptyBeforeImage, result.getBeforeImage());
+        Assertions.assertEquals(afterImage, result.getAfterImage());
+    }
+
+    @Test
+    public void buildUndoItemUpdateTypeTest() throws Exception {
+        mockParameters();
+        mockInsertColumns();
+        mockAllIndexes();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+        when(sqlInsertRecognizer.getTableName()).thenReturn("test_table");
+
+        TableRecords beforeImage = new TableRecords(tableMeta);
+        TableRecords afterImage = new TableRecords(tableMeta);
+
+        Method method = 
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredMethod(
+                "buildUndoItem", SQLType.class, TableRecords.class, 
TableRecords.class);
+        method.setAccessible(true);
+        SQLUndoLog result = (SQLUndoLog) method.invoke(insertOrUpdateExecutor, 
SQLType.UPDATE, beforeImage, afterImage);
+
+        Assertions.assertNotNull(result);
+        Assertions.assertEquals(SQLType.UPDATE, result.getSqlType());
+        Assertions.assertEquals("test_table", result.getTableName());
+        Assertions.assertEquals(beforeImage, result.getBeforeImage());
+        Assertions.assertEquals(afterImage, result.getAfterImage());
+    }
+
+    @Test
+    public void prepareUndoLogAllEmptyImagesTest() throws Exception {
+        mockParameters();
+        mockInsertColumns();
+        mockAllIndexes();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+
+        TableRecords emptyBeforeImage = TableRecords.empty(tableMeta);
+        TableRecords emptyAfterImage = TableRecords.empty(tableMeta);
+
+        ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
+
+        Method method = 
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredMethod(
+                "prepareUndoLogAll", TableRecords.class, TableRecords.class);
+        method.setAccessible(true);
+        method.invoke(insertOrUpdateExecutor, emptyBeforeImage, 
emptyAfterImage);
+
+        verify(connectionProxy, times(0)).appendLockKey(any());
+    }
+
+    @Test
+    public void buildUndoItemAllPureInsertTest() throws Exception {
+        mockParameters();
+        mockInsertColumns();
+        mockAllIndexes();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+        when(tableMeta.getTableName()).thenReturn("test_table");
+
+        TableRecords emptyBeforeImage = TableRecords.empty(tableMeta);
+        TableRecords afterImage = new TableRecords(tableMeta);
+        Row afterRow = new Row();
+        Field pkField = new Field("id", java.sql.Types.INTEGER, 100);
+        pkField.setKeyType(KeyType.PRIMARY_KEY);
+        afterRow.add(pkField);
+        afterImage.add(afterRow);
+
+        ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
+
+        java.lang.reflect.Field isUpdateFlagField =
+                
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredField("isUpdateFlag");
+        isUpdateFlagField.setAccessible(true);
+        isUpdateFlagField.setBoolean(insertOrUpdateExecutor, false);
+
+        Method method = 
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredMethod(
+                "buildUndoItemAll", ConnectionProxy.class, TableRecords.class, 
TableRecords.class);
+        method.setAccessible(true);
+        method.invoke(insertOrUpdateExecutor, connectionProxy, 
emptyBeforeImage, afterImage);
+
+        verify(connectionProxy, times(1)).appendUndoLog(any(SQLUndoLog.class));
+    }
+
+    @Test
+    public void buildUndoItemAllPureUpdateTest() throws Exception {
+        mockParameters();
+        mockInsertColumns();
+        mockAllIndexes();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+        when(tableMeta.getTableName()).thenReturn("test_table");
+
+        TableRecords beforeImage = new TableRecords(tableMeta);
+        Row beforeRow = new Row();
+        Field pkField1 = new Field("id", java.sql.Types.INTEGER, 100);
+        pkField1.setKeyType(KeyType.PRIMARY_KEY);
+        beforeRow.add(pkField1);
+        beforeImage.add(beforeRow);
+
+        TableRecords afterImage = new TableRecords(tableMeta);
+        Row afterRow = new Row();
+        Field pkField2 = new Field("id", java.sql.Types.INTEGER, 100);
+        pkField2.setKeyType(KeyType.PRIMARY_KEY);
+        afterRow.add(pkField2);
+        afterImage.add(afterRow);
+
+        ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
+
+        java.lang.reflect.Field isUpdateFlagField =
+                
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredField("isUpdateFlag");
+        isUpdateFlagField.setAccessible(true);
+        isUpdateFlagField.setBoolean(insertOrUpdateExecutor, true);
+
+        Method method = 
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredMethod(
+                "buildUndoItemAll", ConnectionProxy.class, TableRecords.class, 
TableRecords.class);
+        method.setAccessible(true);
+        method.invoke(insertOrUpdateExecutor, connectionProxy, beforeImage, 
afterImage);
+
+        verify(connectionProxy, times(1)).appendUndoLog(any(SQLUndoLog.class));
+    }
+
+    @Test
+    public void buildUndoItemAllMixedTest() throws Exception {
+        mockParameters();
+        mockInsertColumns();
+        mockAllIndexes();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+        when(tableMeta.getTableName()).thenReturn("test_table");
+
+        TableRecords beforeImage = new TableRecords(tableMeta);
+        Row beforeRow = new Row();
+        Field pkField1 = new Field("id", java.sql.Types.INTEGER, 100);
+        pkField1.setKeyType(KeyType.PRIMARY_KEY);
+        beforeRow.add(pkField1);
+        beforeImage.add(beforeRow);
+
+        TableRecords afterImage = new TableRecords(tableMeta);
+        Row afterRow1 = new Row();
+        Field pkField2 = new Field("id", java.sql.Types.INTEGER, 100);
+        pkField2.setKeyType(KeyType.PRIMARY_KEY);
+        afterRow1.add(pkField2);
+        afterImage.add(afterRow1);
+
+        Row afterRow2 = new Row();
+        Field pkField3 = new Field("id", java.sql.Types.INTEGER, 101);
+        pkField3.setKeyType(KeyType.PRIMARY_KEY);
+        afterRow2.add(pkField3);
+        afterImage.add(afterRow2);
+
+        ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
+
+        java.lang.reflect.Field isUpdateFlagField =
+                
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredField("isUpdateFlag");
+        isUpdateFlagField.setAccessible(true);
+        isUpdateFlagField.setBoolean(insertOrUpdateExecutor, true);
+
+        Method method = 
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredMethod(
+                "buildUndoItemAll", ConnectionProxy.class, TableRecords.class, 
TableRecords.class);
+        method.setAccessible(true);
+        method.invoke(insertOrUpdateExecutor, connectionProxy, beforeImage, 
afterImage);
+
+        verify(connectionProxy, times(2)).appendUndoLog(any(SQLUndoLog.class));
+    }
+
+    @Test
+    public void buildUndoItemAllSizeMismatchTest() throws Exception {
+        mockParameters();
+        mockInsertColumns();
+        mockAllIndexes();
+        doReturn(pkIndexMap).when(insertOrUpdateExecutor).getPkIndex();
+        doReturn(tableMeta).when(insertOrUpdateExecutor).getTableMeta();
+        when(tableMeta.getTableName()).thenReturn("test_table");
+
+        TableRecords beforeImage = new TableRecords(tableMeta);
+        Row beforeRow1 = new Row();
+        Field pkField1 = new Field("id", java.sql.Types.INTEGER, 100);
+        pkField1.setKeyType(KeyType.PRIMARY_KEY);
+        beforeRow1.add(pkField1);
+        beforeImage.add(beforeRow1);
+
+        Row beforeRow2 = new Row();
+        Field pkField2 = new Field("id", java.sql.Types.INTEGER, 101);
+        pkField2.setKeyType(KeyType.PRIMARY_KEY);
+        beforeRow2.add(pkField2);
+        beforeImage.add(beforeRow2);
+
+        TableRecords afterImage = new TableRecords(tableMeta);
+        Row afterRow1 = new Row();
+        Field pkField3 = new Field("id", java.sql.Types.INTEGER, 100);
+        pkField3.setKeyType(KeyType.PRIMARY_KEY);
+        afterRow1.add(pkField3);
+        afterImage.add(afterRow1);
+
+        ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
+
+        java.lang.reflect.Field isUpdateFlagField =
+                
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredField("isUpdateFlag");
+        isUpdateFlagField.setAccessible(true);
+        isUpdateFlagField.setBoolean(insertOrUpdateExecutor, true);
+
+        Method method = 
MySQLInsertOnDuplicateUpdateExecutor.class.getDeclaredMethod(
+                "buildUndoItemAll", ConnectionProxy.class, TableRecords.class, 
TableRecords.class);
+        method.setAccessible(true);
+
+        
Assertions.assertThrows(java.lang.reflect.InvocationTargetException.class, () 
-> {
+            method.invoke(insertOrUpdateExecutor, connectionProxy, 
beforeImage, afterImage);
+        });
+    }
 }
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/PolarDBXInsertOnDuplicateUpdateExecutorTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/PolarDBXInsertOnDuplicateUpdateExecutorTest.java
index e524ae1d21..eb4af03aaf 100644
--- 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/PolarDBXInsertOnDuplicateUpdateExecutorTest.java
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/PolarDBXInsertOnDuplicateUpdateExecutorTest.java
@@ -47,8 +47,6 @@ import static org.mockito.Mockito.when;
  */
 public class PolarDBXInsertOnDuplicateUpdateExecutorTest extends 
MySQLInsertOnDuplicateUpdateExecutorTest {
 
-    protected PolarDBXInsertOnDuplicateUpdateExecutor insertOrUpdateExecutor;
-
     @BeforeEach
     @Override
     public void init() {
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/KingbaseTableMetaCacheTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/KingbaseTableMetaCacheTest.java
new file mode 100644
index 0000000000..98850cd961
--- /dev/null
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/KingbaseTableMetaCacheTest.java
@@ -0,0 +1,335 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.rm.datasource.sql.struct.cache;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import org.apache.seata.common.exception.ShouldNeverHappenException;
+import org.apache.seata.rm.datasource.DataSourceProxy;
+import org.apache.seata.rm.datasource.DataSourceProxyTest;
+import org.apache.seata.rm.datasource.mock.MockDriver;
+import org.apache.seata.rm.datasource.sql.struct.TableMetaCacheFactory;
+import org.apache.seata.sqlparser.struct.IndexMeta;
+import org.apache.seata.sqlparser.struct.IndexType;
+import org.apache.seata.sqlparser.struct.TableMeta;
+import org.apache.seata.sqlparser.struct.TableMetaCache;
+import org.apache.seata.sqlparser.util.JdbcConstants;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.sql.SQLException;
+import java.sql.Types;
+
+public class KingbaseTableMetaCacheTest {
+
+    private static final Object[][] COLUMN_METAS = new Object[][] {
+        new Object[] {"", "", "kt1", "id", Types.INTEGER, "INTEGER", 64, 0, 
10, 1, "", "", 0, 0, 64, 1, "NO", "YES"},
+        new Object[] {"", "", "kt1", "name1", Types.VARCHAR, "VARCHAR", 64, 0, 
10, 0, "", "", 0, 0, 64, 2, "YES", "NO"},
+        new Object[] {"", "", "kt1", "name2", Types.VARCHAR, "VARCHAR", 64, 0, 
10, 0, "", "", 0, 0, 64, 3, "YES", "NO"},
+        new Object[] {"", "", "kt1", "name3", Types.VARCHAR, "VARCHAR", 64, 0, 
10, 0, "", "", 0, 0, 64, 4, "YES", "NO"}
+    };
+
+    private static final Object[][] INDEX_METAS = new Object[][] {
+        new Object[] {"idx_id", "id", false, "", 3, 0, "A", 34},
+        new Object[] {"idx_name1", "name1", false, "", 3, 1, "A", 34},
+        new Object[] {"idx_name2", "name2", true, "", 3, 2, "A", 34},
+    };
+
+    private static final Object[][] PK_METAS = new Object[][] {new Object[] 
{"id"}};
+
+    private static final Object[][] TABLE_METAS = new Object[][] {new Object[] 
{"", "kb", "kt1"}};
+
+    @Test
+    public void testGetTableMetaBasic() throws SQLException {
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, INDEX_METAS, 
PK_METAS, TABLE_METAS);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "kb.kt1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertEquals(4, tableMeta.getAllColumns().size());
+        Assertions.assertEquals(3, tableMeta.getAllIndexes().size());
+    }
+
+    @Test
+    public void testGetTableMetaWithSchemaPrefix() throws SQLException {
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, INDEX_METAS, 
PK_METAS, TABLE_METAS);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "kb.kt1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertEquals("KT1", tableMeta.getTableName());
+        Assertions.assertEquals("kb.kt1", tableMeta.getOriginalTableName());
+    }
+
+    @Test
+    public void testGetTableMetaWithQuotedIdentifiers() throws SQLException {
+        Object[][] quotedTableMetas = new Object[][] {new Object[] {"", "KB", 
"MixedCase"}};
+        Object[][] quotedColumnMetas = new Object[][] {
+            new Object[] {
+                "", "", "MixedCase", "Id", Types.INTEGER, "INTEGER", 64, 0, 
10, 1, "", "", 0, 0, 64, 1, "NO", "YES"
+            },
+            new Object[] {
+                "", "", "MixedCase", "Name", Types.VARCHAR, "VARCHAR", 64, 0, 
10, 0, "", "", 0, 0, 64, 2, "YES", "NO"
+            }
+        };
+        Object[][] quotedIndexMetas = new Object[][] {new Object[] {"idx_id", 
"Id", false, "", 3, 0, "A", 34}};
+        Object[][] quotedPKMetas = new Object[][] {new Object[] {"Id"}};
+
+        MockDriver mockDriver = new MockDriver(quotedColumnMetas, 
quotedIndexMetas, quotedPKMetas, quotedTableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        TableMeta tableMeta =
+                tableMetaCache.getTableMeta(proxy.getPlainConnection(), 
"KB.\"MixedCase\"", proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertEquals("MixedCase", tableMeta.getTableName());
+    }
+
+    @Test
+    public void testGetTableMetaThrowsExceptionWhenNoIndex() throws 
SQLException {
+        Object[][] emptyIndexMetas = new Object[][] {};
+        Object[][] emptyTableMetas = new Object[][] {new Object[] {"", "kb", 
"kt1"}};
+
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, emptyIndexMetas, 
PK_METAS, emptyTableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        Assertions.assertThrows(ShouldNeverHappenException.class, () -> {
+            tableMetaCache.getTableMeta(proxy.getPlainConnection(), "kt1", 
proxy.getResourceId());
+        });
+    }
+
+    @Test
+    public void testGetTableMetaWithCompositeIndex() throws SQLException {
+        Object[][] compositeIndexMetas = new Object[][] {
+            new Object[] {"idx_pk", "id", false, "", 3, 1, "A", 34},
+            new Object[] {"idx_composite", "name1", false, "", 3, 1, "A", 34},
+            new Object[] {"idx_composite", "name2", false, "", 3, 2, "A", 34}
+        };
+
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, 
compositeIndexMetas, PK_METAS, TABLE_METAS);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "kb.kt1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertTrue(tableMeta.getAllIndexes().size() >= 1);
+        Assertions.assertEquals(4, tableMeta.getAllColumns().size());
+    }
+
+    @Test
+    public void testGetTableMetaWithPrimaryKeyIndex() throws SQLException {
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, INDEX_METAS, 
PK_METAS, TABLE_METAS);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "kb.kt1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        IndexMeta pkIndex = tableMeta.getAllIndexes().get("idx_id");
+        Assertions.assertNotNull(pkIndex);
+        Assertions.assertEquals(IndexType.PRIMARY, pkIndex.getIndextype());
+    }
+
+    @Test
+    public void testGetTableMetaWithUniqueIndex() throws SQLException {
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, INDEX_METAS, 
PK_METAS, TABLE_METAS);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "kb.kt1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        IndexMeta uniqueIndex = tableMeta.getAllIndexes().get("idx_name1");
+        Assertions.assertNotNull(uniqueIndex);
+        Assertions.assertEquals(IndexType.UNIQUE, uniqueIndex.getIndextype());
+    }
+
+    @Test
+    public void testGetTableMetaWithNormalIndex() throws SQLException {
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, INDEX_METAS, 
PK_METAS, TABLE_METAS);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "kb.kt1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        IndexMeta normalIndex = tableMeta.getAllIndexes().get("idx_name2");
+        Assertions.assertNotNull(normalIndex);
+        Assertions.assertEquals(IndexType.NORMAL, normalIndex.getIndextype());
+    }
+
+    @Test
+    public void testGetTableMetaWithNullIndexName() throws SQLException {
+        Object[][] nullIndexMetas = new Object[][] {
+            new Object[] {"idx_id", "id", false, "", 3, 0, "A", 34},
+            new Object[] {null, "name1", false, "", 3, 1, "A", 34}
+        };
+
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, nullIndexMetas, 
PK_METAS, TABLE_METAS);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "kb.kt1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertTrue(tableMeta.getAllIndexes().containsKey("idx_id"));
+        Assertions.assertFalse(tableMeta.getAllIndexes().containsKey(null));
+    }
+
+    @Test
+    public void testGetTableMetaCaching() throws SQLException {
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, INDEX_METAS, 
PK_METAS, TABLE_METAS);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        TableMeta tableMeta1 = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "kb.kt1", 
proxy.getResourceId());
+        TableMeta tableMeta2 = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "KB.KT1", 
proxy.getResourceId());
+
+        Assertions.assertSame(tableMeta1, tableMeta2, "Cache should return 
same instance");
+    }
+
+    @Test
+    public void testGetTableMetaWithoutSchemaPrefix() throws SQLException {
+        Object[][] singleTableMetas = new Object[][] {new Object[] {"", 
"public", "kt1"}};
+
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, INDEX_METAS, 
PK_METAS, singleTableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "kt1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertEquals("KT1", tableMeta.getTableName());
+    }
+
+    @Test
+    public void testGetTableMetaWithLowerCaseTable() throws SQLException {
+        Object[][] lowerCaseTableMetas = new Object[][] {new Object[] {"", 
"kb", "lowertable"}};
+        Object[][] lowerCaseColumnMetas = new Object[][] {
+            new Object[] {
+                "", "", "lowertable", "id", Types.INTEGER, "INTEGER", 64, 0, 
10, 1, "", "", 0, 0, 64, 1, "NO", "YES"
+            }
+        };
+        Object[][] lowerCaseIndexMetas = new Object[][] {new Object[] 
{"idx_id", "id", false, "", 3, 0, "A", 34}};
+        Object[][] lowerCasePKMetas = new Object[][] {new Object[] {"id"}};
+
+        MockDriver mockDriver =
+                new MockDriver(lowerCaseColumnMetas, lowerCaseIndexMetas, 
lowerCasePKMetas, lowerCaseTableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        TableMeta tableMeta =
+                tableMetaCache.getTableMeta(proxy.getPlainConnection(), 
"lowertable", proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+    }
+
+    @Test
+    public void testGetTableMetaWithMultiplePrimaryKeys() throws SQLException {
+        Object[][] multiPKMetas = new Object[][] {new Object[] {"id"}, new 
Object[] {"name1"}};
+        Object[][] multiPKIndexMetas = new Object[][] {
+            new Object[] {"idx_composite_pk", "id", false, "", 3, 1, "A", 34},
+            new Object[] {"idx_composite_pk", "name1", false, "", 3, 2, "A", 
34}
+        };
+
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, 
multiPKIndexMetas, multiPKMetas, TABLE_METAS);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "kb.kt1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertTrue(tableMeta.getPrimaryKeyMap().size() >= 1);
+        Assertions.assertTrue(tableMeta.getAllIndexes().size() >= 1);
+    }
+
+    @Test
+    public void testGetTableMetaOriginalTableNamePreserved() throws 
SQLException {
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, INDEX_METAS, 
PK_METAS, TABLE_METAS);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:kingbase");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.KINGBASE);
+
+        String originalName = "kb.kt1";
+        TableMeta tableMeta =
+                tableMetaCache.getTableMeta(proxy.getPlainConnection(), 
originalName, proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertEquals(originalName, 
tableMeta.getOriginalTableName());
+    }
+}
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/OracleTableMetaCacheTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/OracleTableMetaCacheTest.java
index 1d04fa2e37..4c213ea4a0 100644
--- 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/OracleTableMetaCacheTest.java
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/OracleTableMetaCacheTest.java
@@ -17,10 +17,14 @@
 package org.apache.seata.rm.datasource.sql.struct.cache;
 
 import com.alibaba.druid.pool.DruidDataSource;
+import org.apache.seata.common.exception.ShouldNeverHappenException;
 import org.apache.seata.rm.datasource.DataSourceProxy;
 import org.apache.seata.rm.datasource.DataSourceProxyTest;
 import org.apache.seata.rm.datasource.mock.MockDriver;
 import org.apache.seata.rm.datasource.sql.struct.TableMetaCacheFactory;
+import org.apache.seata.sqlparser.struct.ColumnMeta;
+import org.apache.seata.sqlparser.struct.IndexMeta;
+import org.apache.seata.sqlparser.struct.IndexType;
 import org.apache.seata.sqlparser.struct.TableMeta;
 import org.apache.seata.sqlparser.struct.TableMetaCache;
 import org.apache.seata.sqlparser.util.JdbcConstants;
@@ -30,6 +34,8 @@ import org.junit.jupiter.api.Test;
 import java.sql.SQLException;
 import java.sql.Types;
 
+import static org.junit.jupiter.api.Assertions.*;
+
 /**
  */
 public class OracleTableMetaCacheTest {
@@ -72,4 +78,267 @@ public class OracleTableMetaCacheTest {
 
         Assertions.assertNotNull(tableMeta);
     }
+
+    @Test
+    public void testGetTableMetaCaching() throws SQLException {
+        MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, 
pkMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        TableMeta tableMeta1 = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
+        TableMeta tableMeta2 = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.OT1", 
proxy.getResourceId());
+
+        Assertions.assertSame(tableMeta1, tableMeta2, "Cache should return 
same instance");
+    }
+
+    @Test
+    public void testGetTableMetaWithNoIndexThrowsException() throws 
SQLException {
+        Object[][] emptyIndexMetas = new Object[][] {};
+        Object[][] tableMetasForTest = new Object[][] {new Object[] {"", "t", 
"ot2"}};
+
+        MockDriver mockDriver = new MockDriver(columnMetas, emptyIndexMetas, 
pkMetas, tableMetasForTest);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        Assertions.assertThrows(ShouldNeverHappenException.class, () -> {
+            tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot2", 
proxy.getResourceId());
+        });
+    }
+
+    @Test
+    public void testGetTableMetaWithLowerCaseColumns() throws SQLException {
+        MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, 
pkMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        ColumnMeta column = tableMeta.getColumnMeta("name1");
+        Assertions.assertNotNull(column);
+        Assertions.assertEquals("name1", column.getColumnName());
+    }
+
+    @Test
+    public void testGetTableMetaWithDuplicateColumnNames() throws SQLException 
{
+        MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, 
pkMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertEquals(4, tableMeta.getAllColumns().size());
+    }
+
+    @Test
+    public void testGetTableMetaWithCompositeIndex() throws SQLException {
+        Object[][] compositeIndexMetas = new Object[][] {
+            new Object[] {"idx_pk", "id", false, "", 3, 1, "A", 34},
+            new Object[] {"idx_composite", "name1", false, "", 3, 1, "A", 34},
+            new Object[] {"idx_composite", "name2", false, "", 3, 2, "A", 34}
+        };
+
+        MockDriver mockDriver = new MockDriver(columnMetas, 
compositeIndexMetas, pkMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertTrue(tableMeta.getAllIndexes().size() >= 1);
+        Assertions.assertEquals(4, tableMeta.getAllColumns().size());
+    }
+
+    @Test
+    public void testGetTableMetaWithPrimaryKeyIndex() throws SQLException {
+        MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, 
pkMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        IndexMeta pkIndex = tableMeta.getAllIndexes().get("id");
+        Assertions.assertNotNull(pkIndex);
+        Assertions.assertEquals(IndexType.PRIMARY, pkIndex.getIndextype());
+    }
+
+    @Test
+    public void testGetTableMetaWithUniqueIndex() throws SQLException {
+        MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, 
pkMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        IndexMeta uniqueIndex = tableMeta.getAllIndexes().get("name1");
+        Assertions.assertNotNull(uniqueIndex);
+        Assertions.assertEquals(IndexType.UNIQUE, uniqueIndex.getIndextype());
+    }
+
+    @Test
+    public void testGetTableMetaWithNormalIndex() throws SQLException {
+        MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, 
pkMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        IndexMeta normalIndex = tableMeta.getAllIndexes().get("name2");
+        Assertions.assertNotNull(normalIndex);
+        Assertions.assertEquals(IndexType.NORMAL, normalIndex.getIndextype());
+    }
+
+    @Test
+    public void testGetTableMetaWithNullIndexName() throws SQLException {
+        Object[][] nullIndexMetas = new Object[][] {
+            new Object[] {"idx_id", "id", false, "", 3, 0, "A", 34},
+            new Object[] {null, "name1", false, "", 3, 1, "A", 34}
+        };
+
+        MockDriver mockDriver = new MockDriver(columnMetas, nullIndexMetas, 
pkMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertTrue(tableMeta.getAllIndexes().size() >= 1);
+    }
+
+    @Test
+    public void testGetTableMetaWithPrimaryKeyConstraintDifferentName() throws 
SQLException {
+        MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, 
pkMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        ColumnMeta pkColumn = tableMeta.getPrimaryKeyMap().get("id");
+        Assertions.assertNotNull(pkColumn);
+        Assertions.assertEquals("id", pkColumn.getColumnName());
+        Assertions.assertTrue(tableMeta.getAllIndexes().size() >= 1);
+    }
+
+    @Test
+    public void testGetTableMetaOriginalTableNamePreserved() throws 
SQLException {
+        MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, 
pkMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        String originalName = "t.ot1";
+        TableMeta tableMeta =
+                tableMetaCache.getTableMeta(proxy.getPlainConnection(), 
originalName, proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertEquals(originalName, 
tableMeta.getOriginalTableName());
+    }
+
+    @Test
+    public void testGetTableMetaWithMultiplePrimaryKeys() throws SQLException {
+        Object[][] multiPKMetas = new Object[][] {new Object[] {"id"}, new 
Object[] {"name1"}};
+        Object[][] multiPKIndexMetas = new Object[][] {
+            new Object[] {"idx_composite_pk", "id", false, "", 3, 1, "A", 34},
+            new Object[] {"idx_composite_pk", "name1", false, "", 3, 2, "A", 
34}
+        };
+
+        MockDriver mockDriver = new MockDriver(columnMetas, multiPKIndexMetas, 
multiPKMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertTrue(tableMeta.getPrimaryKeyMap().size() >= 1);
+        Assertions.assertTrue(tableMeta.getAllIndexes().size() >= 1);
+    }
+
+    @Test
+    public void testGetTableMetaWithQuotedTableName() throws SQLException {
+        MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, 
pkMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        TableMeta tableMeta =
+                tableMetaCache.getTableMeta(proxy.getPlainConnection(), 
"t.\"ot1\"", proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertEquals(4, tableMeta.getAllColumns().size());
+    }
+
+    @Test
+    public void testGetTableMetaAllColumnsPresent() throws SQLException {
+        MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, 
pkMetas, tableMetas);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+
+        TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
+
+        Assertions.assertNotNull(tableMeta);
+        Assertions.assertEquals(4, tableMeta.getAllColumns().size());
+        Assertions.assertNotNull(tableMeta.getColumnMeta("id"));
+        Assertions.assertNotNull(tableMeta.getColumnMeta("name1"));
+        Assertions.assertNotNull(tableMeta.getColumnMeta("name2"));
+        Assertions.assertNotNull(tableMeta.getColumnMeta("name3"));
+    }
 }
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/OracleTableMetaCacheTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/OscarTableMetaCacheTest.java
similarity index 54%
copy from 
rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/OracleTableMetaCacheTest.java
copy to 
rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/OscarTableMetaCacheTest.java
index 1d04fa2e37..d34f6efb8b 100644
--- 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/OracleTableMetaCacheTest.java
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/OscarTableMetaCacheTest.java
@@ -30,46 +30,73 @@ import org.junit.jupiter.api.Test;
 import java.sql.SQLException;
 import java.sql.Types;
 
-/**
- */
-public class OracleTableMetaCacheTest {
+public class OscarTableMetaCacheTest {
 
-    private static Object[][] columnMetas = new Object[][] {
+    private static final Object[][] COLUMN_METAS = new Object[][] {
         new Object[] {"", "", "ot1", "id", Types.INTEGER, "INTEGER", 64, 0, 
10, 1, "", "", 0, 0, 64, 1, "NO", "YES"},
         new Object[] {"", "", "ot1", "name1", Types.VARCHAR, "VARCHAR", 64, 0, 
10, 0, "", "", 0, 0, 64, 2, "YES", "NO"},
         new Object[] {"", "", "ot1", "name2", Types.VARCHAR, "VARCHAR", 64, 0, 
10, 0, "", "", 0, 0, 64, 3, "YES", "NO"},
         new Object[] {"", "", "ot1", "name3", Types.VARCHAR, "VARCHAR", 64, 0, 
10, 0, "", "", 0, 0, 64, 4, "YES", "NO"}
     };
 
-    private static Object[][] indexMetas = new Object[][] {
+    private static final Object[][] INDEX_METAS = new Object[][] {
         new Object[] {"id", "id", false, "", 3, 0, "A", 34},
         new Object[] {"name1", "name1", false, "", 3, 1, "A", 34},
         new Object[] {"name2", "name2", true, "", 3, 2, "A", 34},
     };
 
-    private static Object[][] pkMetas = new Object[][] {new Object[] {"id"}};
+    private static final Object[][] PK_METAS = new Object[][] {new Object[] 
{"id"}};
 
-    private static Object[][] tableMetas = new Object[][] {new Object[] {"", 
"t", "ot1"}};
+    private static final Object[][] TABLE_METAS = new Object[][] {new Object[] 
{"", "t", "ot1"}};
 
     @Test
-    public void getTableMetaTest() throws SQLException {
-        MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, 
pkMetas, tableMetas);
+    public void testGetTableMetaWithSchemaAndTable() throws SQLException {
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, INDEX_METAS, 
PK_METAS, TABLE_METAS);
         DruidDataSource dataSource = new DruidDataSource();
         dataSource.setUrl("jdbc:mock:xxx");
         dataSource.setDriver(mockDriver);
 
         DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
 
-        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.OSCAR);
 
         TableMeta tableMeta = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
 
         Assertions.assertNotNull(tableMeta);
-        Assertions.assertEquals("OT1", tableMeta.getTableName());
-        Assertions.assertEquals("t.ot1", tableMeta.getOriginalTableName());
+        Assertions.assertEquals("t.ot1", tableMeta.getTableName());
+        Assertions.assertEquals(4, tableMeta.getAllColumns().size());
+    }
+
+    @Test
+    public void testGetTableMetaWithQuotedIdentifiers() throws SQLException {
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, INDEX_METAS, 
PK_METAS, TABLE_METAS);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.OSCAR);
 
-        tableMeta = tableMetaCache.getTableMeta(proxy.getPlainConnection(), 
"t.\"ot1\"", proxy.getResourceId());
+        TableMeta tableMeta =
+                tableMetaCache.getTableMeta(proxy.getPlainConnection(), 
"t.\"ot1\"", proxy.getResourceId());
 
         Assertions.assertNotNull(tableMeta);
     }
+
+    @Test
+    public void testGetTableMetaCaching() throws SQLException {
+        MockDriver mockDriver = new MockDriver(COLUMN_METAS, INDEX_METAS, 
PK_METAS, TABLE_METAS);
+        DruidDataSource dataSource = new DruidDataSource();
+        dataSource.setUrl("jdbc:mock:xxx");
+        dataSource.setDriver(mockDriver);
+
+        DataSourceProxy proxy = 
DataSourceProxyTest.getDataSourceProxy(dataSource);
+        TableMetaCache tableMetaCache = 
TableMetaCacheFactory.getTableMetaCache(JdbcConstants.OSCAR);
+
+        TableMeta tableMeta1 = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", 
proxy.getResourceId());
+        TableMeta tableMeta2 = 
tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.OT1", 
proxy.getResourceId());
+
+        Assertions.assertSame(tableMeta1, tableMeta2, "Cache should return 
same instance");
+    }
 }
diff --git 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/util/JdbcUtilsTest.java
 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/util/JdbcUtilsTest.java
index 0b1ae4134e..64a2003948 100644
--- 
a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/util/JdbcUtilsTest.java
+++ 
b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/util/JdbcUtilsTest.java
@@ -16,14 +16,280 @@
  */
 package org.apache.seata.rm.datasource.util;
 
+import org.apache.seata.rm.BaseDataSourceResource;
+import org.apache.seata.rm.DefaultResourceManager;
 import org.apache.seata.sqlparser.util.DbTypeParser;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import javax.sql.DataSource;
+import javax.sql.XAConnection;
+import javax.sql.XADataSource;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.Driver;
+import java.sql.SQLException;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
 
 public class JdbcUtilsTest {
+
+    private static final String MYSQL_URL = "jdbc:mysql://localhost:3306/test";
+    private static final String MYSQL_URL_WITH_PARAMS =
+            "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
+    private static final String ORACLE_URL = 
"jdbc:oracle:thin:@localhost:1521:orcl";
+    private static final String POSTGRESQL_URL = 
"jdbc:postgresql://localhost:5432/test";
+    private static final String H2_URL = "jdbc:h2:mem:test";
+
+    private MockedStatic<DefaultResourceManager> mockedResourceManager;
+    private DefaultResourceManager resourceManager;
+
+    @BeforeEach
+    public void setUp() {
+        resourceManager = mock(DefaultResourceManager.class);
+        mockedResourceManager = 
Mockito.mockStatic(DefaultResourceManager.class);
+        
mockedResourceManager.when(DefaultResourceManager::get).thenReturn(resourceManager);
+    }
+
+    @AfterEach
+    public void tearDown() {
+        if (mockedResourceManager != null) {
+            mockedResourceManager.close();
+        }
+    }
+
     @Test
     public void testDbTypeParserLoading() {
         DbTypeParser dbTypeParser = JdbcUtils.getDbTypeParser();
         Assertions.assertNotNull(dbTypeParser);
     }
+
+    @Test
+    public void testDbTypeParserSingletonPattern() {
+        DbTypeParser first = JdbcUtils.getDbTypeParser();
+        DbTypeParser second = JdbcUtils.getDbTypeParser();
+        assertSame(first, second, "DbTypeParser should be singleton");
+    }
+
+    @Test
+    public void testGetDbTypeWithMySQLUrl() {
+        String dbType = JdbcUtils.getDbType(MYSQL_URL);
+        assertEquals("mysql", dbType);
+    }
+
+    @Test
+    public void testGetDbTypeWithOracleUrl() {
+        String dbType = JdbcUtils.getDbType(ORACLE_URL);
+        assertEquals("oracle", dbType);
+    }
+
+    @Test
+    public void testGetDbTypeWithPostgreSQLUrl() {
+        String dbType = JdbcUtils.getDbType(POSTGRESQL_URL);
+        assertEquals("postgresql", dbType);
+    }
+
+    @Test
+    public void testGetDbTypeWithH2Url() {
+        String dbType = JdbcUtils.getDbType(H2_URL);
+        assertEquals("h2", dbType);
+    }
+
+    @Test
+    public void testBuildResourceIdWithQueryString() {
+        String resourceId = JdbcUtils.buildResourceId(MYSQL_URL_WITH_PARAMS);
+        assertEquals(MYSQL_URL, resourceId, "Should remove query string");
+    }
+
+    @Test
+    public void testBuildResourceIdWithoutQueryString() {
+        String resourceId = JdbcUtils.buildResourceId(MYSQL_URL);
+        assertEquals(MYSQL_URL, resourceId, "Should keep URL unchanged");
+    }
+
+    @Test
+    public void testBuildResourceIdWithEmptyQueryString() {
+        String urlWithEmptyQuery = "jdbc:mysql://localhost:3306/test?";
+        String resourceId = JdbcUtils.buildResourceId(urlWithEmptyQuery);
+        assertEquals(MYSQL_URL, resourceId, "Should remove trailing question 
mark");
+    }
+
+    @Test
+    public void testBuildResourceIdWithMultipleQuestionMarks() {
+        String urlWithMultiple = 
"jdbc:mysql://localhost:3306/test?param1=value1?param2=value2";
+        String resourceId = JdbcUtils.buildResourceId(urlWithMultiple);
+        assertEquals(MYSQL_URL, resourceId, "Should remove from first question 
mark");
+    }
+
+    @Test
+    public void testLoadDriverWithValidH2Driver() throws SQLException {
+        Driver driver = JdbcUtils.loadDriver("org.h2.Driver");
+        assertNotNull(driver, "Should load H2 driver successfully");
+        assertTrue(driver instanceof org.h2.Driver);
+    }
+
+    @Test
+    public void testLoadDriverWithInvalidDriverClass() {
+        SQLException exception = assertThrows(SQLException.class, () -> {
+            JdbcUtils.loadDriver("com.invalid.NonExistentDriver");
+        });
+        assertNotNull(exception.getCause());
+        assertTrue(exception.getCause() instanceof ClassNotFoundException);
+    }
+
+    @Test
+    public void testLoadDriverWithNullClassName() {
+        assertThrows(Exception.class, () -> {
+            JdbcUtils.loadDriver(null);
+        });
+    }
+
+    @Test
+    public void testLoadDriverWithEmptyClassName() {
+        assertThrows(Exception.class, () -> {
+            JdbcUtils.loadDriver("");
+        });
+    }
+
+    @Test
+    public void testInitDataSourceResourceSuccess() throws SQLException {
+        DataSource dataSource = mock(DataSource.class);
+        Connection connection = mock(Connection.class);
+        DatabaseMetaData metaData = mock(DatabaseMetaData.class);
+        BaseDataSourceResource resource = mock(BaseDataSourceResource.class);
+
+        when(dataSource.getConnection()).thenReturn(connection);
+        when(connection.getMetaData()).thenReturn(metaData);
+        when(metaData.getURL()).thenReturn(MYSQL_URL_WITH_PARAMS);
+
+        JdbcUtils.initDataSourceResource(resource, dataSource, "test-group");
+
+        verify(resource).setResourceGroupId("test-group");
+        verify(resource).setResourceId(MYSQL_URL);
+        verify(resource).setDbType("mysql");
+        verify(resource).setDriver(any(Driver.class));
+        verify(resourceManager).registerResource(resource);
+        verify(connection).close();
+    }
+
+    @Test
+    public void testInitDataSourceResourceWithSQLException() throws 
SQLException {
+        DataSource dataSource = mock(DataSource.class);
+        BaseDataSourceResource resource = mock(BaseDataSourceResource.class);
+
+        when(dataSource.getConnection()).thenThrow(new 
SQLException("Connection failed"));
+
+        IllegalStateException exception = 
assertThrows(IllegalStateException.class, () -> {
+            JdbcUtils.initDataSourceResource(resource, dataSource, 
"test-group");
+        });
+
+        assertTrue(exception.getMessage().contains("can not init 
DataSourceResource"));
+        assertNotNull(exception.getCause());
+        assertTrue(exception.getCause() instanceof SQLException);
+        verify(resource).setResourceGroupId("test-group");
+        verify(resourceManager, never()).registerResource(any());
+    }
+
+    @Test
+    public void testInitDataSourceResourceWithNullMetaData() throws 
SQLException {
+        DataSource dataSource = mock(DataSource.class);
+        Connection connection = mock(Connection.class);
+        BaseDataSourceResource resource = mock(BaseDataSourceResource.class);
+
+        when(dataSource.getConnection()).thenReturn(connection);
+        when(connection.getMetaData()).thenReturn(null);
+
+        assertThrows(Exception.class, () -> {
+            JdbcUtils.initDataSourceResource(resource, dataSource, 
"test-group");
+        });
+
+        verify(connection).close();
+    }
+
+    @Test
+    public void testInitXADataSourceResourceSuccess() throws SQLException {
+        XADataSource xaDataSource = mock(XADataSource.class);
+        XAConnection xaConnection = mock(XAConnection.class);
+        Connection connection = mock(Connection.class);
+        DatabaseMetaData metaData = mock(DatabaseMetaData.class);
+        BaseDataSourceResource resource = mock(BaseDataSourceResource.class);
+
+        when(xaDataSource.getXAConnection()).thenReturn(xaConnection);
+        when(xaConnection.getConnection()).thenReturn(connection);
+        when(connection.getMetaData()).thenReturn(metaData);
+        when(metaData.getURL()).thenReturn(H2_URL);
+
+        JdbcUtils.initXADataSourceResource(resource, xaDataSource, "xa-group");
+
+        verify(resource).setResourceGroupId("xa-group");
+        verify(resource).setResourceId(H2_URL);
+        verify(resource).setDbType("h2");
+        verify(resource).setDriver(any(Driver.class));
+        verify(resourceManager).registerResource(resource);
+        verify(connection).close();
+        verify(xaConnection).close();
+    }
+
+    @Test
+    public void testInitXADataSourceResourceWithSQLException() throws 
SQLException {
+        XADataSource xaDataSource = mock(XADataSource.class);
+        BaseDataSourceResource resource = mock(BaseDataSourceResource.class);
+
+        when(xaDataSource.getXAConnection()).thenThrow(new SQLException("XA 
connection failed"));
+
+        IllegalStateException exception = 
assertThrows(IllegalStateException.class, () -> {
+            JdbcUtils.initXADataSourceResource(resource, xaDataSource, 
"xa-group");
+        });
+
+        assertTrue(exception.getMessage().contains("can not get 
XAConnection"));
+        assertNotNull(exception.getCause());
+        assertTrue(exception.getCause() instanceof SQLException);
+        verify(resource).setResourceGroupId("xa-group");
+        verify(resourceManager, never()).registerResource(any());
+    }
+
+    @Test
+    public void testInitXADataSourceResourceWithNullXAConnection() throws 
SQLException {
+        XADataSource xaDataSource = mock(XADataSource.class);
+        BaseDataSourceResource resource = mock(BaseDataSourceResource.class);
+
+        when(xaDataSource.getXAConnection()).thenReturn(null);
+
+        assertThrows(Exception.class, () -> {
+            JdbcUtils.initXADataSourceResource(resource, xaDataSource, 
"xa-group");
+        });
+
+        verify(resource).setResourceGroupId("xa-group");
+        verify(resourceManager, never()).registerResource(any());
+    }
+
+    @Test
+    public void testInitXADataSourceResourceEnsuresConnectionClosed() throws 
SQLException {
+        XADataSource xaDataSource = mock(XADataSource.class);
+        XAConnection xaConnection = mock(XAConnection.class);
+        Connection connection = mock(Connection.class);
+        DatabaseMetaData metaData = mock(DatabaseMetaData.class);
+        BaseDataSourceResource resource = mock(BaseDataSourceResource.class);
+
+        when(xaDataSource.getXAConnection()).thenReturn(xaConnection);
+        when(xaConnection.getConnection()).thenReturn(connection);
+        when(connection.getMetaData()).thenReturn(metaData);
+        when(metaData.getURL()).thenReturn(H2_URL);
+        doThrow(new SQLException("Close failed")).when(xaConnection).close();
+
+        try {
+            JdbcUtils.initXADataSourceResource(resource, xaDataSource, 
"xa-group");
+        } catch (Exception e) {
+            // Expected
+        }
+
+        verify(connection).close();
+        verify(xaConnection).close();
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to