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);
}
相关推荐
B1118521Y463 小时前
flask的使用
后端·python·flask
xuxie135 小时前
SpringBoot文件下载(多文件以zip形式,单文件格式不变)
java·spring boot·后端
重生成为编程大王5 小时前
Java中的多态有什么用?
java·后端
Funcy6 小时前
XxlJob 源码分析03:执行器启动流程
后端
豌豆花下猫7 小时前
Python 潮流周刊#118:Python 异步为何不够流行?(摘要)
后端·python·ai
秋难降8 小时前
SQL 索引突然 “罢工”?快来看看为什么
数据库·后端·sql
Access开发易登软件9 小时前
Access开发导出PDF的N种姿势,你get了吗?
后端·低代码·pdf·excel·vba·access·access开发
中国胖子风清扬10 小时前
Rust 序列化技术全解析:从基础到实战
开发语言·c++·spring boot·vscode·后端·中间件·rust
bobz96510 小时前
分析 docker.service 和 docker.socket 这两个服务各自的作用
后端
野犬寒鸦10 小时前
力扣hot100:旋转图像(48)(详细图解以及核心思路剖析)
java·数据结构·后端·算法·leetcode