问题
由于客户需要适配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);
}