{"record":{"id":"1cac298168b422cc","repo":"mybatis/mybatis-3","slug":"cannot-commit-transaction-is-already-closed","errorCode":null,"errorMessage":"Cannot commit, transaction is already closed","messagePattern":"Cannot commit, transaction is already closed","errorType":"exception","errorClass":"ExecutorException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/executor/BaseExecutor.java","lineNumber":253,"sourceCode":"        cacheKey.update(value);\n      }\n    }\n    if (configuration.getEnvironment() != null) {\n      // issue #176\n      cacheKey.update(configuration.getEnvironment().getId());\n    }\n    return cacheKey;\n  }\n\n  @Override\n  public boolean isCached(MappedStatement ms, CacheKey key) {\n    return localCache.getObject(key) != null;\n  }\n\n  @Override\n  public void commit(boolean required) throws SQLException {\n    if (closed) {\n      throw new ExecutorException(\"Cannot commit, transaction is already closed\");\n    }\n    clearLocalCache();\n    flushStatements();\n    if (required) {\n      transaction.commit();\n    }\n  }\n\n  @Override\n  public void rollback(boolean required) throws SQLException {\n    if (!closed) {\n      try {\n        clearLocalCache();\n        flushStatements(true);\n      } finally {\n        if (required) {\n          transaction.rollback();\n        }","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/executor/BaseExecutor.java#L235-L271","documentation":"BaseExecutor.commit(required) throws ExecutorException 'Cannot commit, transaction is already closed' when the executor's closed flag is set. Committing flushes the local cache and statements then delegates to the Transaction; after close(), the transaction and statements are released, so commit is rejected. Note this fires even for commit(false), and note that rollback(boolean) deliberately does NOT throw when closed.","triggerScenarios":"Calling sqlSession.commit() after sqlSession.close(); double-commit where a framework (Spring tx manager) commits and manual code commits again on the closed executor; commit inside a finally block that runs after an earlier close; commit on a session whose executor was closed by close(forceRollback).","commonSituations":"finally { session.commit(); } after an exception path already closed the session; mixing Spring transaction management with manual commit; session-per-request code where close happens in a filter before an interceptor commits.","solutions":["Commit before closing, exactly once: open -> work -> commit -> close","In exception paths, close without committing (close(true) forces rollback); never commit in a finally after close","With Spring, let the transaction manager own commit/rollback — remove manual sqlSession.commit() calls","Guard with !session.getConnection()... isClosed checks or track session state if lifecycle is complex"],"exampleFix":"// before\ntry (SqlSession s = factory.openSession()) {\n  s.update(...);\n} catch (Exception e) {\n  ...\n} finally {\n  session.commit(); // session already closed by try-with-resources\n}\n\n// after\ntry (SqlSession s = factory.openSession()) {\n  s.update(...);\n  s.commit(); // commit while open\n}","handlingStrategy":"validation","validationCode":"// Commit exactly once, before close:\ntry (SqlSession s = factory.openSession()) {\n  s.update(\"...\", obj);\n  s.commit(); // inside the scope\n} // close happens after commit","typeGuard":null,"tryCatchPattern":"try {\n  sqlSession.commit();\n} catch (ExecutorException e) {\n  if (\"Cannot commit, transaction is already closed\".equals(e.getMessage())) {\n    // session already closed and its transaction finalized: nothing to do; log and continue\n  } else throw e;\n}","preventionTips":["Structure every session as open -> work -> commit -> close, once","Never commit in a finally block that can run after close","With Spring, remove manual commit; the platform transaction manager owns it"],"tags":["mybatis","transaction","commit","session-closed","lifecycle"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}