简体   繁体   中英

Auto-commit changing at runtime with Hibernate and MySQL

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:

  1. Get Current Session from Hibernate session factory
  2. Begin transaction
  3. Execute update
  4. Commit transaction
  5. Steps 3 & 4 are in a try/catch and if there is an exception the transaction is rolled back.

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.

Ie

  1. Thread 1, method 1
    1. DAO 1: auto-commit false
    2. DAO 2: auto-commit false
    3. DAO 3: auto-commit true
    4. DAO ...n: auto-commit true
  2. Thread !1, method auto-commit false
  3. Thread 1, method
    1. DAO x: auto-commit true
    2. Exception
    3. DAO y: auto-commit false

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...

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM