diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java index cd45d0d847..41192f88e0 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java @@ -589,6 +589,10 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager } } catch (PersistenceException ex) { + DataAccessException dae = getJpaDialect().translateExceptionIfPossible(ex); + if (dae != null) { + throw dae; + } throw new TransactionSystemException("Could not roll back JPA transaction", ex); } finally { diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java index ae1f787509..f3fcc74bd2 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java @@ -59,6 +59,7 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.dao.PessimisticLockingFailureException; import org.springframework.jdbc.datasource.ConnectionHandle; import org.springframework.jdbc.datasource.DataSourceUtils; +import org.springframework.jdbc.support.SQLExceptionSubclassTranslator; import org.springframework.jdbc.support.SQLExceptionTranslator; import org.springframework.lang.Nullable; import org.springframework.orm.ObjectOptimisticLockingFailureException; @@ -74,7 +75,7 @@ import org.springframework.util.ReflectionUtils; /** * {@link org.springframework.orm.jpa.JpaDialect} implementation for Hibernate. - * Compatible with Hibernate ORM 5.5/5.6 as well as 6.0/6.1/6.2. + * Compatible with Hibernate ORM 5.5/5.6 as well as 6.0/6.1/6.2/6.3. * * @author Juergen Hoeller * @author Costin Leau @@ -91,6 +92,9 @@ public class HibernateJpaDialect extends DefaultJpaDialect { @Nullable private SQLExceptionTranslator jdbcExceptionTranslator; + @Nullable + private SQLExceptionTranslator transactionExceptionTranslator = new SQLExceptionSubclassTranslator(); + /** * Set whether to prepare the underlying JDBC Connection of a transactional @@ -121,14 +125,22 @@ public class HibernateJpaDialect extends DefaultJpaDialect { *
Applied to any detected {@link java.sql.SQLException} root cause of a Hibernate * {@link JDBCException}, overriding Hibernate's own {@code SQLException} translation * (which is based on a Hibernate Dialect for a specific target database). + *
As of 6.1, also applied to {@link org.hibernate.TransactionException} translation + * with a {@link SQLException} root cause (where Hibernate does not translate itself + * at all), overriding Spring's default {@link SQLExceptionSubclassTranslator} there. + * @param exceptionTranslator the {@link SQLExceptionTranslator} to delegate to, or + * {@code null} for none. By default, a {@link SQLExceptionSubclassTranslator} will + * be used for {@link org.hibernate.TransactionException} translation as of 6.1; + * this can be reverted to pre-6.1 behavior through setting {@code null} here. * @since 5.1 * @see java.sql.SQLException * @see org.hibernate.JDBCException + * @see org.springframework.jdbc.support.SQLExceptionSubclassTranslator * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator - * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator */ - public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) { - this.jdbcExceptionTranslator = jdbcExceptionTranslator; + public void setJdbcExceptionTranslator(@Nullable SQLExceptionTranslator exceptionTranslator) { + this.jdbcExceptionTranslator = exceptionTranslator; + this.transactionExceptionTranslator = exceptionTranslator; } @@ -245,7 +257,16 @@ public class HibernateJpaDialect extends DefaultJpaDialect { DataAccessException dae = this.jdbcExceptionTranslator.translate( "Hibernate operation: " + jdbcEx.getMessage(), jdbcEx.getSQL(), jdbcEx.getSQLException()); if (dae != null) { - throw dae; + return dae; + } + } + if (this.transactionExceptionTranslator != null && ex instanceof org.hibernate.TransactionException) { + if (ex.getCause() instanceof SQLException sqlEx) { + DataAccessException dae = this.transactionExceptionTranslator.translate( + "Hibernate transaction: " + ex.getMessage(), null, sqlEx); + if (dae != null) { + return dae; + } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java index e9678742d4..35e34636f5 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java @@ -45,7 +45,7 @@ import org.springframework.util.ClassUtils; /** * {@link org.springframework.orm.jpa.JpaVendorAdapter} implementation for Hibernate. - * Compatible with Hibernate ORM 5.5/5.6 as well as 6.0/6.1/6.2. + * Compatible with Hibernate ORM 5.5/5.6 as well as 6.0/6.1/6.2/6.3. * *
Exposes Hibernate's persistence provider and Hibernate's Session as extended * EntityManager interface, and adapts {@link AbstractJpaVendorAdapter}'s common