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);
}
相关推荐
zyplayer-doc1 小时前
VuePress类静态文档站和动态知识库怎么选:两种技术路线的适用场景
javascript·人工智能·后端·安全·智能手机
用户938515635073 小时前
从前后端分离到前端接口工程:React + MockJS + Vite 解析
前端·后端·全栈
码事漫谈4 小时前
世界由什么构成——这个问题比你想的更缠人
后端
摇滚侠4 小时前
《SpringBoot 3:入门与应用实战》第 2 章 IOC 思想与实现 阅读笔记 1
spring boot·笔记·后端
山荷枝4 小时前
03-框架--Spring
java·后端·spring
ChaHae-In5 小时前
SpringBoot统一功能处理
java·spring boot·后端
65岁退休Coder7 小时前
LangChain v1.3.4 笔记 - 07 补充:链式调用 LCEL
后端·python·langchain
卷无止境7 小时前
FastAPI 部署在 Nginx 后面到底该怎么配
后端·python