Reputation: 31
I'm having a weird issue with Hibernate (3) and MySQL (5.5) running in Glassfish 3.1.2.2, where the state of the auto-commit flag is changing at run-time within the context of a single server call and on the same thread. I'm not explicitly changing it and it has started recently out of the blue. No code in this area has changed significantly.
The code I'm working uses has various data access methods, each of which follow the same pattern:
During the first two such method calls, the auto-update flag is false (getDefaultSession().connection().getAutoCommit()
), but on the 3rd one, and for every one after, the flag is suddenly true, with no indication how/why it changed. I've had to add workaround code to some methods to skip committing if the flag is true, but this is an issue that has just recently started happening and I can;t go update all methods.
The knock on as well is that the auto-commit flag is definitely saved as true on the current session, as the next time that thread ID gets a session, the state starts as true. If the data access method that is next used on that thread happens to not have the workaround code, an exception is thrown and the state reverts to false. Again, with no explicit code on our part.
I.e.
I've found plenty of threads about how to enable/disable auto-commit in the Hibernate config, and I do have the config item:
<property name="hibernate.connection.autocommit">false</property>
I've also found some threads indicating that the flag might change to true when using multiple threads, but all of these methods (around when the change happens) are being called from the same initial method and within the same thread (confirmed based on ThreadID in Glassfish logs).
I have read about the relax auto-commit flag on the JDBC url, but I'd prefer to get to the bottom of why it's happening to begin with. As a last resort I might have to do that to avoid having to change all our code.
Seriously confused...
Upvotes: 3
Views: 3603
Reputation: 73
Been having the same issue with Glassfish 4.0 and have not gotten to the bottom of it either. Most people just seem to set the relaxAutoCommit
flag and forget about it. The testing that we have done has shown that even though we setAutoCommit(false)
not only is the state returned by getAutoCommit()
incorrect, but if one actually queries the MySQL database like so: SELECT @@session.autocommit
we see that indeed auto-commit is still enabled for the session.
I believe that setting relaxAutoCommit
is dangerous and should be avoided! If you actually bother to read the doc for Connector/J you find that relaxAutoCommit
has a clear purpose:
If the version of MySQL the driver connects to does not support transactions, still allow calls to commit(), rollback() and setAutoCommit() (true/false, defaults to 'false')?
Setting it just to suppress exceptions is a mistake, IMO.
Eventually, we implemented a com.mysql.jdbc.ConnectionLifecycleInterceptor
and found that for some reason, calling setAutoCommit(false)
on the connection returned from the pool would occasionally fail to actually set auto-commit on the driver's connection and therefore fail to set it on the session.
We have worked-around the issue by retrying the operation when the connection gets into this inconsistent state.
With the ConnectionLifecycleInterceptor and additional logging enabled, this is what some of output generated in the application's logs looks like:
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)] -
The ConnectionLifecycleInterceptor is logging the fact that we are calling setAutoCommit(false) and additional logging before and after the call to SetActionCommand is reporting the result of getAutoCommit() and session.autocommit.
Relevant bits of Connector/J with some of my own additional comments:
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'
}
I'm befuddled as to why it appears that setAutoCommit() is returning before actually doing anything unless something is causing the iter.fullIteration() to return false.
Upvotes: 1