diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java index c6b22f7a2bde..5b1d39163697 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -42,6 +42,7 @@ import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.LockAcquisitionException; +import org.hibernate.exception.TransactionSerializationException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; @@ -1088,6 +1089,10 @@ public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() { return null; } return switch (sqlState) { + // Serialization Exception + case "40001" -> message.contains("WriteTooOldError") + ? new TransactionSerializationException( message, sqlException, sql ) + : null; // DEADLOCK DETECTED case "40P01" -> new LockAcquisitionException( message, sqlException, sql ); // LOCK NOT AVAILABLE diff --git a/hibernate-core/src/main/java/org/hibernate/exception/TransactionSerializationException.java b/hibernate-core/src/main/java/org/hibernate/exception/TransactionSerializationException.java new file mode 100644 index 000000000000..866036ea3cd8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/exception/TransactionSerializationException.java @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.exception; + +import org.hibernate.JDBCException; + +import java.sql.SQLException; +/** + * A {@link JDBCException} indicating a transaction failed because it could not be placed into a serializable ordering + * among all of the currently-executing transactions + * + * @author Karel Maesen + */ +public class TransactionSerializationException extends JDBCException { + public TransactionSerializationException(String message, SQLException cause) { + super( message, cause ); + } + + public TransactionSerializationException(String message, SQLException cause, String sql) { + super( message, cause, sql ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java index 995d58dd083f..dbbf1b721d0b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ExceptionConverterImpl.java @@ -22,6 +22,7 @@ import org.hibernate.engine.spi.ExceptionConverter; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.exception.LockAcquisitionException; +import org.hibernate.exception.TransactionSerializationException; import org.hibernate.loader.MultipleBagFetchException; import org.hibernate.query.IllegalQueryOperationException; import org.hibernate.query.SemanticException; @@ -139,6 +140,11 @@ else if ( exception instanceof TransientObjectException ) { //Spec 3.2.3 Synchronization rules return new IllegalStateException( exception ); } + else if ( exception instanceof TransactionSerializationException ) { + final PersistenceException converted = new RollbackException( exception.getMessage(), exception ); + rollbackIfNecessary( converted ); + return converted; + } else { rollbackIfNecessary( exception ); return exception; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/batch/BatchUpdateAndVersionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/batch/BatchUpdateAndVersionTest.java index c6d7d952ed38..0a0a59282c8b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/batch/BatchUpdateAndVersionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/batch/BatchUpdateAndVersionTest.java @@ -9,9 +9,11 @@ import java.util.List; import jakarta.persistence.OptimisticLockException; +import jakarta.persistence.RollbackException; import org.hibernate.StaleObjectStateException; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.exception.TransactionSerializationException; import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.ServiceRegistry; @@ -136,6 +138,10 @@ public void testFailedUpdate(SessionFactoryScope scope) { catch (OptimisticLockException ole) { assertTrue( ole.getCause() instanceof StaleObjectStateException ); } + //CockroachDB errors with a Serialization Exception + catch (RollbackException rbe) { + assertTrue( rbe.getCause() instanceof TransactionSerializationException ); + } } @Entity(name = "EntityA")