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);
}
相关推荐
GetcharZp6 小时前
GitHub 49K+ Star!C++ 开发者必知的 JSON 神级库:从零到精通全指北
后端
xujinwei_gingko6 小时前
SpringBoot整合WebSocket
spring boot·后端·websocket
智码看视界6 小时前
现代Web开发基础:全栈工程师的起航点
前端·后端·c5全栈
程序员cxuan6 小时前
Claude Fable 5 来了
人工智能·后端·程序员
JS菌7 小时前
手写一个 AI Agent 全栈项目:从沙箱执行到子智能体的完整实现
前端·人工智能·后端
wang09077 小时前
自己动手写一个spring之IOC_2
java·后端·spring
来杯@Java7 小时前
学生选课管理系统(基于springboot+vue前后端分离的项目)计算机毕业设计java
java·spring boot·spring·vue·毕业设计·maven·mybatis
ltl8 小时前
推理退化:为什么大模型会输出乱码、死循环和无意义文本
后端
ltl8 小时前
架构视图与文档:C4 模型从入门到实战
后端
IT_陈寒11 小时前
Redis持久化这个坑,我爬了一整天才出来
前端·人工智能·后端