Skip to content

MySQL Error 149 (Galera/WSREP conflict) not translated to ConcurrencyFailureException in Spring JDBC/ORM #2268

@ThomasVerhoeven1998

Description

@ThomasVerhoeven1998

Description

When using Spring Framework 7.0.2 with a MySQL/Percona Galera Cluster (v8.0.41), transactions failing due to a certification conflict during the COMMIT phase result in a generic org.springframework.orm.jpa.JpaSystemException instead of the expected org.springframework.dao.ConcurrencyFailureException.

In Galera-based environments, a certification conflict returns vendor error code 149 (ER_LOCK_DEADLOCK). Unlike the standard InnoDB deadlock code (1213), code 149 is not currently present in sql-error-codes.xml.

Currently, it falls through to JpaSystemException. Adding it to deadlockLoserCodes will ensure it is translated to PessimisticLockingFailureException (which replaced the deprecated DeadlockLoserDataAccessException).

Furthermore, in some environments (including the one observed), the driver/server returns this exception with an empty SQLState string. This prevents the SQLErrorCodeSQLExceptionTranslator from falling back to standard SQLState-based translation (e.g., mapping 40001 to a deadlock).

MySQL Error 149 (ER_LOCK_DEADLOCK) specifically indicates a deadlock where the transaction should be retried. While Spring typically handles MySQL error 1213 for deadlocks, error 149—which often appears during COMMIT operations in specific MySQL configurations or storage engines—is currently missed by the default translation logic.

Root cause

org.springframework.orm.jpa.JpaSystemException: could not execute statement [Got error 149 - 'Lock deadlock; Retry transaction' during COMMIT]
...
Caused by: java.sql.SQLException: Got error 149 - 'Lock deadlock; Retry transaction' during COMMIT
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:121)
    ... 
    (Note: SQLState is "" and VendorCode is 149)

Steps to Reproduce

  1. Configure a Spring Data JPA application using Spring Framework 7.0.2.
  2. Connect to a Percona XtraDB Cluster or MySQL Galera Cluster.
  3. Simulate a high-concurrency scenario causing a WSREP certification conflict (detected at commit time).
  4. The database returns error 149.
  5. Spring throws JpaSystemException (via Hibernate's GenericJDBCException) instead of a ConcurrencyFailureException subclass.

Expected Behavior

The exception should be translated to org.springframework.dao.DeadlockLoserDataAccessException to allow for consistent exception handling and to trigger @retryable mechanisms that look for transient data access failures.

Possible Solution

The MySQL error code 149 should be added to the deadlockCodes list in Spring JDBC's sql-error-codes.xml for the MySQL entry.

Location: spring-jdbc/src/main/resources/org/springframework/jdbc/support/sql-error-codes.xml

<bean id="MySQL" class="org.springframework.jdbc.support.SQLErrorCodes">
    <property name="deadlockCodes">
        <value>1213,149</value> </property>
    ...
</bean>

Possible workaround

    @Bean
    public SQLErrorCodes sqlErrorCodes() {
        SQLErrorCodes mysqlCodes = SQLErrorCodesFactory.getInstance().getErrorCodes("MySQL");

        List<String> deadlockCodes = new ArrayList<>(Arrays.asList(mysqlCodes.getDeadlockLoserCodes()));

        if (!deadlockCodes.contains("149")) {
            deadlockCodes.add("149");
        }

        mysqlCodes.setDeadlockLoserCodes(deadlockCodes.toArray(new String[0]));

        return mysqlCodes;
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    for: external-projectFor an external project and not something we can fix

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions