SqlSession污染连接池

问题

由于客户需要适配gbase数据库存储元数据,在适配过程中gbase抛出异常不能在事务交易过程中改变事物交易隔绝等级。其主要原因是当前准备开启事务的连接处于事务中而gbase driver判定后会直接抛出异常。代码如下:

java 复制代码
// com.gbase8c.jdbc.PgConnection#setTransactionIsolation
public void setTransactionIsolation(int level) throws SQLException {
    this.checkClosed();
    if (this.queryExecutor.getTransactionState() != TransactionState.IDLE) {
        throw new PSQLException(GT.tr("Cannot change transaction isolation level in the middle of a transaction.", new Object[0]), PSQLState.ACTIVE_SQL_TRANSACTION);
    } else {

排查

首先对异常堆栈进行排查,看是否存在嵌套事务。但实际上并没有嵌套事务。于是通过复现路径追踪什么动作会触发该异常,相关的调用有没有特殊之处。最终发现业务代码中存在手动使用openSqlSession的行为,如下:

ini 复制代码
try (SqlSession session = sqlSessionFactory.openSession()) {
       // 业务逻辑
       session.commit();
}

追踪Mybatis源码可以发现,openSession默认是会开启事务的,如下:

kotlin 复制代码
public SqlSession openSession() {
    // 最后的false为autoCommit设置为false,从而开启事务
    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}

而在DefaultSqlSession中,close、commit、rollback都有可能不提交事务:

typescript 复制代码
private boolean isCommitOrRollbackRequired(boolean force) {
  return (!autoCommit && dirty) || force;
}

@Override
public void rollback(boolean force) {
  try {
    executor.rollback(isCommitOrRollbackRequired(force));
    dirty = false;
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

public void commit() {
  commit(false);
}

public void rollback() {
  rollback(false);
}

public void commit(boolean force) {
  try {
    executor.commit(isCommitOrRollbackRequired(force));
    dirty = false;
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

@Override
public void close() {
  try {
    executor.close(isCommitOrRollbackRequired(false));
    closeCursors();
    dirty = false;
  } finally {
    ErrorContext.instance().reset();
  }
}

isCommitOrRollbackRequired方法除了判定当前开启事务之外,还会判定当前dirty(执行过DML),只有都满足才会要求提交或者回滚。而如果调用无参的commit或者rollback的时候,force都为false,即如果没有DML不会提交事务。

是否可能引起问题

  • 如本文遇到的问题,若driver对当前事务状态进行校验,则可能导致业务异常
  • 但是如果没有上述问题,使用SqlSession,则不会对业务有影响 假设当前连接池中存在一个已经开启事务的连接被某一线程获取后,如果使用SqlSession,则SqlSession会根据当前是否开启事务重新设置autoCommit,因此不会产生影响。
  • 而若是使用Spring的事务,且当前线程获取到已经开启事务的连接,本线程原本业务并不开启事务,那么该DML就会进入事务而不会被提交,直到碰到某个业务开启事务才会真正进行回滚或者提交。具体可以参考如下代码:
kotlin 复制代码
// org.apache.kylin.metadata.transaction.SpringManagedTransactionFactory#newTransaction(javax.sql.DataSource, org.apache.ibatis.session.TransactionIsolationLevel, boolean)
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
    return new SpringManagedTransaction(dataSource);
}


//org.apache.kylin.metadata.transaction.SpringManagedTransaction#openConnection
private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

    log.trace(
            "JDBC Connection [{}] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring",
            this.connection);
}
相关推荐
LucianaiB9 小时前
参加高德 AI 发布会的一点感受:地图,正在变成 AI 的行动入口
后端
属于自己的天空9 小时前
一个文件让 Claude Code 理解你的项目:CLAUDE.md 从入门到精通
后端
jiangbo_dev9 小时前
还在手搓分布式事务?我把 Saga + Outbox 模板化后,新服务接入从 5 天压到 1 天
后端
BING_Algorithm9 小时前
深入理解JVM垃圾回收
jvm·后端·面试
RainCity9 小时前
Java Swing 自定义组件库分享(六)
java·笔记·后端
techdashen9 小时前
深入 Rust enum 的内存世界
开发语言·后端·rust
龙码精神10 小时前
TimescaleDB 物联网设备属性历史数据表设计及常用SQL文档
后端
小小小小宇10 小时前
Go 后端锁机制详解
后端
挖坑的张师傅10 小时前
你的仓库 Agent Ready 了吗?
后端
客场消音器10 小时前
如何使用codex进行UI重构,让AI开发的前端页面不再千篇一律
前端·后端·微信小程序