我在Glassfish 3.1.2.2中运行的Hibernate(3)和MySQL(5.5)遇到了一个奇怪的问题,其中自动提交标志的状态在运行时在单个服务器调用的上下文中发生变化并在同一个线程上。我没有明确地改变它,它最近已经开始了。此区域的代码没有发生重大变化。
我正在使用的代码有各种数据访问方法,每种方法都遵循相同的模式:
在前两个这样的方法调用期间,自动更新标志为false(getDefaultSession().connection().getAutoCommit()
),但是在第3个,并且对于后面的每一个,标志突然变为真,没有指示如何/为什么它改变了。如果标志为true,我必须向某些方法添加变通代码以跳过提交,但这是刚刚开始发生的问题,我可以更新所有方法。
同样的问题是自动提交标志在当前会话中肯定保存为true,因为下次该线程ID获得会话时,状态将从true开始。如果该线程上接下来使用的数据访问方法碰巧没有变通方法代码,则抛出异常并且状态恢复为false。同样,我们没有明确的代码。
即
我发现很多关于如何在Hibernate配置中启用/禁用自动提交的线程,我确实有配置项:
<property name="hibernate.connection.autocommit">false</property>
我还发现一些线程表明当使用多个线程时该标志可能会变为true,但是所有这些方法(在发生更改时)都是从同一个初始方法和同一个线程中调用的(已确认)基于Glassfish日志中的ThreadID。)
我已经阅读了有关JDBC网址上的放宽自动提交标志的内容,但我更愿意深入了解为什么会开始这样做。作为最后的手段,我可能必须这样做,以避免更改我们的所有代码。
严重困惑......
答案 0 :(得分:0)
与Glassfish 4.0有同样的问题,也没有深入到底。大多数人似乎只是设置了relaxAutoCommit
标志而忘了它。我们已经完成的测试表明,即使我们setAutoCommit(false)
不仅getAutoCommit()
返回的状态不正确,但如果实际查询MySQL数据库是这样的:SELECT @@session.autocommit
我们看到事实上,会话仍然启用了自动提交。
我认为设置relaxAutoCommit
是危险的,应该避免!如果你真的懒得阅读Connector/J的文档,你会发现relaxAutoCommit
有明确的目的:
如果驱动程序连接的MySQL版本不支持事务,仍然允许调用commit(),rollback()和setAutoCommit()(true / false,默认为'false')?
设置它只是为了抑制异常是一个错误,IMO。
最后,我们实现了com.mysql.jdbc.ConnectionLifecycleInterceptor
并发现由于某种原因,从池返回的连接上调用setAutoCommit(false)
偶尔会无法在驱动程序的连接上实际设置自动提交,因此无法在会议上设置它。
我们通过在连接进入此不一致状态时重试操作来解决此问题。
使用ConnectionLifecycleInterceptor并启用其他日志记录,这是应用程序日志中生成的一些输出:
18 Feb 16:47:07 ERROR [http-listener-1(11)] - auto-commit set to: false
18 Feb 16:47:07 INFO [http-listener-1(11)] - Autocommit before command: SetActionCommand
18 Feb 16:47:07 INFO [http-listener-1(11)] - Autocommit state is: connAutocommit=true, sessAutocommit=true
18 Feb 16:47:07 INFO [http-listener-1(11)] - SetActionCommand: host: bat424211win64, action: Checking, comment: Checking:20160218.164706.EST: Stuck Windows Check
18 Feb 16:47:07 INFO [http-listener-1(11)] - Autocommit after command: SetActionCommand
18 Feb 16:47:07 INFO [http-listener-1(11)] - Autocommit state is: connAutocommit=true, sessAutocommit=true
18 Feb 16:47:07 ERROR [http-listener-1(11)] - commit called
18 Feb 16:47:07 ERROR [http-listener-1(11)] - auto-commit should be false
18 Feb 16:47:07 ERROR [http-listener-1(11)] - Exception while executing: SetActionCommand
18 Feb 16:47:07 ERROR [http-listener-1(11)] - rollback called
18 Feb 16:47:07 ERROR [http-listener-1(11)] - auto-commit should be false
18 Feb 16:47:07 WARN [http-listener-1(11)] - Connection failure
18 Feb 16:47:07 ERROR [http-listener-1(11)] -
ConnectionLifecycleInterceptor记录了我们调用setAutoCommit(false)的事实,并且在调用SetActionCommand之前和之后的其他日志记录报告了getAutoCommit()和session.autocommit的结果。
Connector / J的相关部分以及我自己的一些附加注释:
com.mysql.jdbc.ConnectionImpl:
public void setAutoCommit(final boolean autoCommitFlag) throws SQLException {
synchronized (getConnectionMutex()) { // Mutex is 'this'
checkClosed();
if (this.connectionLifecycleInterceptors != null) { // Normally should be 'null'
IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) {
@Override
void forEach(Extension each) throws SQLException {
if (!((ConnectionLifecycleInterceptor) each).setAutoCommit(autoCommitFlag)) { // Logged 'auto-commit set to: false'
this.stopIterating = true;
}
}
};
iter.doForAll();
if (!iter.fullIteration()) { // Only one listener (ours) AFAIK, unlikely to return here
return;
}
}
if (getAutoReconnectForPools()) { // Default for autoReconnectForPools is 'false'
setHighAvailability(true);
}
try {
if (this.transactionsSupported) { // This is 'true'
boolean needsSetOnServer = true;
if (this.getUseLocalSessionState() && this.autoCommit == autoCommitFlag) { // Default for useLocalSessionState is 'false'
needsSetOnServer = false;
} else if (!this.getHighAvailability()) { // This is 'false'
needsSetOnServer = this.getIO().isSetNeededForAutoCommitMode(autoCommitFlag); // Looks like this is always 'true'?
}
// this internal value must be set first as failover depends on it being set to true to fail over (which is done by most app servers and
// connection pools at the end of a transaction), and the driver issues an implicit set based on this value when it (re)-connects to a
// server so the value holds across connections
this.autoCommit = autoCommitFlag; // Updated value is not reflected in getAutoCommit()! We never get here?
if (needsSetOnServer) {
execSQL(null, autoCommitFlag ? "SET autocommit=1" : "SET autocommit=0", -1, null, DEFAULT_RESULT_SET_TYPE,
DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false);
}
} else {
if ((autoCommitFlag == false) && !getRelaxAutoCommit()) {
throw SQLError.createSQLException("MySQL Versions Older than 3.23.15 do not support transactions",
SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor());
}
this.autoCommit = autoCommitFlag;
}
} finally {
if (this.getAutoReconnectForPools()) {
setHighAvailability(false);
}
}
return;
}
}
com.mysql.jdbc.MysqlIO:
protected boolean isSetNeededForAutoCommitMode(boolean autoCommitFlag) {
if (this.use41Extensions && this.connection.getElideSetAutoCommits()) { // Default for elideSetAutoCommits is 'false'
boolean autoCommitModeOnServer = ((this.serverStatus & SERVER_STATUS_AUTOCOMMIT) != 0);
if (!autoCommitFlag && versionMeetsMinimum(5, 0, 0)) {
// Just to be safe, check if a transaction is in progress on the server....
// if so, then we must be in autoCommit == false
// therefore return the opposite of transaction status
boolean inTransactionOnServer = ((this.serverStatus & SERVER_STATUS_IN_TRANS) != 0);
return !inTransactionOnServer;
}
return autoCommitModeOnServer != autoCommitFlag;
}
return true; // Should always be returning 'true'
}
我很困惑为什么看起来setAutoCommit()在实际做任何事情之前都会返回,除非有什么东西导致iter.fullIteration()返回false。