-
Notifications
You must be signed in to change notification settings - Fork 383
Description
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
- Configure a Spring Data JPA application using Spring Framework 7.0.2.
- Connect to a Percona XtraDB Cluster or MySQL Galera Cluster.
- Simulate a high-concurrency scenario causing a WSREP certification conflict (detected at commit time).
- The database returns error 149.
- 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;
}