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);
}
相关推荐
有来技术5 小时前
Spring Boot 4 + Vue3 企业级多租户 SaaS:从共享 Schema 架构到商业化套餐设计
java·vue.js·spring boot·后端
东东5166 小时前
学院个人信息管理系统 (springboot+vue)
vue.js·spring boot·后端·个人开发·毕设
三水不滴7 小时前
Redis缓存更新策略
数据库·经验分享·redis·笔记·后端·缓存
小邓吖8 小时前
自己做了一个工具网站
前端·分布式·后端·中间件·架构·golang
大爱编程♡8 小时前
SpringBoot统一功能处理
java·spring boot·后端
马猴烧酒.9 小时前
【JAVA数据传输】Java 数据传输与转换详解笔记
java·数据库·笔记·tomcat·mybatis
好好研究11 小时前
总结SSM设置欢迎页的方式
xml·java·后端·mvc
小马爱打代码11 小时前
Spring Boot:第三方 API 调用的企业级容错设计
java·spring boot·后端
csdn2015_12 小时前
springboot task
java·spring boot·后端
czlczl2002092513 小时前
Spring Boot :如何高性能地在 Filter 中获取响应体(Response Body)
java·spring boot·后端