{"record":{"id":"3800d6b0300b95f0","repo":"t8y2/dbx","slug":"the-manual-transaction-belongs-to-a-different-jdbc","errorCode":null,"errorMessage":"The manual transaction belongs to a different JDBC connection","messagePattern":"The manual transaction belongs to a different JDBC connection","errorType":"exception","errorClass":"SQLException","httpStatus":null,"severity":"error","filePath":"plugins/jdbc/src/main/java/app/dbx/jdbc/DbxJdbcPlugin.java","lineNumber":1093,"sourceCode":"        conn.setAutoCommit(true);\n        manualTransactionActive = false;\n        return okResult();\n    }\n\n    private static ObjectNode rollbackManualTransaction() throws SQLException {\n        Connection conn = activeManualTransactionConnection(null);\n        conn.rollback();\n        conn.setAutoCommit(true);\n        manualTransactionActive = false;\n        return okResult();\n    }\n\n    private static Connection activeManualTransactionConnection(JsonNode connection) throws SQLException {\n        if (!manualTransactionActive || sharedConnection == null || sharedConnection.isClosed()) {\n            throw new SQLException(\"No manual transaction is active\");\n        }\n        if (connection != null && !connectionKey(connection).equals(sharedConnectionKey)) {\n            throw new SQLException(\"The manual transaction belongs to a different JDBC connection\");\n        }\n        return sharedConnection;\n    }\n\n    private static ObjectNode okResult() {\n        ObjectNode result = MAPPER.createObjectNode();\n        result.put(\"ok\", true);\n        return result;\n    }\n\n    private record ExecutedStatement(ResultSet resultSet, int updateCount) {\n    }\n\n    private static final class QuerySession {\n        private final String id;\n        private final Statement statement;\n        private final ResultSet resultSet;\n        private final ResultSetMetaData meta;","sourceCodeStart":1075,"sourceCodeEnd":1111,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/plugins/jdbc/src/main/java/app/dbx/jdbc/DbxJdbcPlugin.java#L1075-L1111","documentation":"When a manual transaction is active, activeManualTransactionConnection additionally checks that the connection config passed by the caller matches the connection the transaction was opened on, comparing connectionKey(connection) with sharedConnectionKey. A mismatch means the caller is trying to commit/extend a transaction that belongs to a different database connection.","triggerScenarios":"Beginning a manual transaction on connection A, then calling commit/rollback or executing a transactional statement while passing connection B's config (different url/credentials yielding a different connectionKey).","commonSituations":"Copy-pasted connection configs where host/db/credentials differ slightly (producing a different key), scripts iterating over multiple connections and reusing one transaction handle, or editing connection details mid-transaction.","solutions":["Pass the exact same connection config object/values used at begin time for all subsequent transaction operations.","Track which connection config started the transaction in your script and use it consistently until commit/rollback.","Check for stray differences in the config (extra/missing properties change the connectionKey) — normalize the config source.","If you genuinely need a different connection, commit or roll back the current transaction first, then begin a new one on the other connection."],"exampleFix":"// before\nbeginTx(connA);                 // jdbc:db://host/app\nexecWithinTx(connB, stmt);      // different key -> error\n// after\nbeginTx(connA);\nexecWithinTx(connA, stmt);      // same connection\ncommitTx(connA);","handlingStrategy":"validation","validationCode":"// Java: pin one connection config for the whole transaction\nfinal java.util.Map<String,Object> txConn = java.util.Map.copyOf(connCfg); // immutable snapshot\nplugin.beginManualTransaction(txConn);\n// every later call MUST pass txConn:\nplugin.execute(txConn, sql);\nplugin.commitManualTransaction(txConn);","typeGuard":null,"tryCatchPattern":"try {\n    plugin.commitManualTransaction(connCfg);\n} catch (java.sql.SQLException e) {\n    if (e.getMessage().contains(\"different JDBC connection\")) {\n        throw new IllegalStateException(\"commit called with wrong connection config; use the one passed to begin\", e);\n    }\n    throw e;\n}","preventionTips":["Capture the begin-time connection config and reuse the identical object until commit/rollback","Avoid mutating connection config (adding properties) mid-transaction — it changes the connectionKey","Commit before switching to another connection in loops","Centralize transaction handling in one helper that takes and reuses a single config"],"tags":["jdbc","transactions","connection-identity"],"backgroundTag":"transaction-connection-mismatch","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}