diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/HapiFhirHibernateJpaDialect.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/HapiFhirHibernateJpaDialect.java index c1bcacfe176..311e838eaae 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/HapiFhirHibernateJpaDialect.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/HapiFhirHibernateJpaDialect.java @@ -26,10 +26,12 @@ import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import org.hibernate.HibernateException; +import org.hibernate.StaleStateException; import org.hibernate.exception.ConstraintViolationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; +import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.orm.jpa.vendor.HibernateJpaDialect; @@ -68,10 +70,7 @@ public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect { if (isNotBlank(theMessageToPrepend)) { messageToPrepend = theMessageToPrepend + " - "; } - if (theException.toString().contains("Batch update")) { - theException.toString(); - } - // + if (theException instanceof ConstraintViolationException) { String constraintName = ((ConstraintViolationException) theException).getConstraintName(); switch (defaultString(constraintName)) { @@ -83,7 +82,25 @@ public class HapiFhirHibernateJpaDialect extends HibernateJpaDialect { throw new ResourceVersionConflictException(messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "forcedIdConstraintFailure")); } } - // + + /* + * It would be nice if we could be more precise here, since technically any optimistic lock + * failure could result in a StaleStateException, but with the error message we're returning + * we're basically assuming it's an optimistic lock failure on HFJ_RESOURCE. + * + * That said, I think this is an OK trade-off. There is a high probability that if this happens + * it is a failure on HFJ_RESOURCE (there aren't many other tables in our schema that + * use @Version at all) and this error message is infinitely more comprehensible + * than the one we'd otherwise return. + * + * The actual StaleStateException is thrown in hibernate's Expectations + * class in a method called "checkBatched" currently. This can all be tested using the + * StressTestR4Test method testMultiThreadedUpdateSameResourceInTransaction() + */ + if (theException instanceof StaleStateException) { + String msg = messageToPrepend + myLocalizer.getMessage(HapiFhirHibernateJpaDialect.class, "resourceVersionConstraintFailure"); + throw new ResourceVersionConflictException(msg); + } return super.convertHibernateAccessException(theException); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index 4e0f1dc3759..475413da990 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.config; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; import net.ttddyy.dsproxy.listener.SingleQueryCountHolder; -import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.context.annotation.Bean; @@ -25,7 +24,7 @@ import static org.junit.Assert.fail; @EnableTransactionManagement() public class TestR4Config extends BaseJavaConfigR4 { - static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestR4Config.class); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestR4Config.class); private static int ourMaxThreads; static { @@ -96,7 +95,7 @@ public class TestR4Config extends BaseJavaConfigR4 { DataSource dataSource = ProxyDataSourceBuilder .create(retVal) - .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") +// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") // .logSlowQueryBySlf4j(10, TimeUnit.SECONDS) // .countQuery(new ThreadQueryCountHolder()) .beforeQuery(new BlockLargeNumbersOfParamsListener())