From 08101a290e6ce93c9a7378f2f4624728f9ad0c9e Mon Sep 17 00:00:00 2001 From: "Crane.z" <1481445951@qq.com> Date: Sun, 20 Sep 2026 14:44:41 +0800 Subject: [PATCH] fix(dameng): correct transaction state probing --- .../DamengTransactionStateProbe.java | 99 +++++++++++++++++-- 1 file changed, 92 insertions(+), 7 deletions(-) diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/DamengTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/DamengTransactionStateProbe.java index b3ecac41..59936c29 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/DamengTransactionStateProbe.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/DamengTransactionStateProbe.java @@ -1,11 +1,13 @@ package org.jumpserver.chen.framework.console.transaction; +import java.lang.reflect.Method; import java.sql.Connection; import java.sql.SQLException; import java.util.Locale; final class DamengTransactionStateProbe implements TransactionStateProbe { private static final String DRIVER_CONNECTION = "dm.jdbc.driver.DmdbConnection"; + private static final int MAX_UNWRAP_DEPTH = 8; private static final int STATE_MASK = 0x0FFF; private static final int NOT_STARTED = 0; private static final int COMMITTED = 32; @@ -16,16 +18,23 @@ final class DamengTransactionStateProbe implements TransactionStateProbe { @Override public QueryTransactionState inspect(Connection connection) { try { + Object driverConnection = resolveDriverConnection(connection); Class driverClass = Class.forName( DRIVER_CONNECTION, false, - connection.getClass().getClassLoader() + driverConnection.getClass().getClassLoader() ); - Object driverConnection = driverClass.isInstance(connection) - ? connection - : connection.unwrap(driverClass); int transactionStatus = driverClass.getField("trxStatus").getInt(driverConnection); - QueryTransactionState driverState = map(transactionStatus, connection.getAutoCommit()); + boolean autoCommit = connection.getAutoCommit(); + boolean transactionFinished = (boolean) driverClass + .getMethod("getTransFinish") + .invoke(driverConnection); + if (transactionFinished) { + // DM 8.1.3.140 leaves trxStatus at ACTIVE after commit and marks completion here. + // Avoid querying V$TRX in that state: the query itself can start a transaction. + return map(COMMITTED, autoCommit); + } + QueryTransactionState driverState = map(transactionStatus, autoCommit); if (driverState != QueryTransactionState.TRANSACTION_ACTIVE) { return driverState; } @@ -36,12 +45,88 @@ public QueryTransactionState inspect(Connection connection) { } return mapServerStatus(resultSet.getString(1)); } - } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | - SQLException e) { + } catch (ReflectiveOperationException | SQLException e) { throw new TransactionStateProbeException(e); } } + /** + * QueryConsole can expose Druid's statement connection while the Dameng driver lives in an + * isolated DriverClassLoader. Peel wrappers before loading DmdbConnection so reflection uses + * the same loader as the physical connection. + */ + static Object resolveDriverConnection(Connection connection) throws SQLException, ClassNotFoundException { + if (connection == null) { + throw new ClassNotFoundException(DRIVER_CONNECTION + " from null connection"); + } + Connection current = connection; + ClassNotFoundException lastNotFound = null; + for (int depth = 0; depth < MAX_UNWRAP_DEPTH; depth++) { + ClassLoader classLoader = current.getClass().getClassLoader(); + if (classLoader != null) { + try { + Class driverClass = Class.forName(DRIVER_CONNECTION, false, classLoader); + if (driverClass.isInstance(current)) { + return current; + } + if (current.isWrapperFor(driverClass)) { + return current.unwrap(driverClass); + } + } catch (ClassNotFoundException e) { + lastNotFound = e; + } catch (SQLException ignored) { + // This wrapper cannot unwrap with the driver class visible to its loader. + } + } + Connection next = peel(current); + if (next == null || next == current) { + break; + } + current = next; + } + throw lastNotFound != null + ? lastNotFound + : new ClassNotFoundException( + DRIVER_CONNECTION + " from " + connection.getClass().getName() + + " classLoader=" + classLoaderName(connection)); + } + + private static Connection peel(Connection connection) { + Connection viaGetter = invokeConnectionGetter(connection, "getConnection"); + if (viaGetter != null) { + return viaGetter; + } + Connection viaPhysical = invokeConnectionGetter(connection, "getPhysicalConnection"); + if (viaPhysical != null) { + return viaPhysical; + } + try { + Connection unwrapped = connection.unwrap(Connection.class); + if (unwrapped != null && unwrapped != connection) { + return unwrapped; + } + } catch (SQLException ignored) { + } + return connection; + } + + private static Connection invokeConnectionGetter(Connection connection, String methodName) { + try { + Method method = connection.getClass().getMethod(methodName); + Object value = method.invoke(connection); + if (value instanceof Connection next && next != connection) { + return next; + } + } catch (ReflectiveOperationException ignored) { + } + return null; + } + + private static String classLoaderName(Connection connection) { + ClassLoader classLoader = connection.getClass().getClassLoader(); + return classLoader == null ? "null" : classLoader.getClass().getName(); + } + static QueryTransactionState map(int transactionStatus, boolean autoCommit) { int state = transactionStatus & STATE_MASK; if (state == NOT_STARTED || state == COMMITTED || state == ROLLED_BACK) {