From f669c4bcdfbb1fa1cf669d241e6086b83109fd35 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 29 Nov 2017 21:09:45 -0600 Subject: [PATCH] HHH-12139 - Allow Hibernate's Transaction act like JPA's EntityTransaction --- .../hibernate/boot/SessionFactoryBuilder.java | 23 +++ .../internal/SessionFactoryBuilderImpl.java | 42 ++++++ .../internal/SessionFactoryOptionsImpl.java | 9 ++ .../internal/SessionFactoryOptionsState.java | 3 + ...stractDelegatingSessionFactoryBuilder.java | 24 ++++ ...stractDelegatingSessionFactoryOptions.java | 6 + .../boot/spi/SessionFactoryOptions.java | 3 + .../org/hibernate/cfg/AvailableSettings.java | 49 +++++++ .../transaction/internal/TransactionImpl.java | 44 +++++- .../AbstractSharedSessionContract.java | 6 +- .../util/config/ConfigurationHelper.java | 23 ++- .../java/org/hibernate/jpa/JpaCompliance.java | 66 +++++++++ .../hibernate/jpa/spi/JpaComplianceImpl.java | 90 ++++++++++++ .../spi/TransactionCoordinatorBuilder.java | 2 +- .../jpa/JpaComplianceTestingImpl.java | 66 +++++++++ .../tck2_2/EntityTransactionTests.java | 132 ++++++++++++++++++ .../jdbc/BasicJdbcTransactionTests.java | 22 +-- .../AbstractRegionAccessStrategyTest.java | 10 +- 18 files changed, 584 insertions(+), 36 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/jpa/JpaCompliance.java create mode 100644 hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaComplianceImpl.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/JpaComplianceTestingImpl.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/EntityTransactionTests.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java index 3a05689dc3..6943b3db7b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/SessionFactoryBuilder.java @@ -19,6 +19,7 @@ import org.hibernate.cache.spi.QueryCacheFactory; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.jpa.JpaCompliance; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; @@ -718,6 +719,28 @@ public interface SessionFactoryBuilder { */ SessionFactoryBuilder enableReleaseResourcesOnCloseEnabled(boolean enable); + + /** + * @see JpaCompliance#isJpaQueryComplianceEnabled() + */ + SessionFactoryBuilder enableJpaQueryCompliance(boolean enabled); + + /** + * @see JpaCompliance#isJpaTransactionComplianceEnabled() + */ + SessionFactoryBuilder enableJpaTransactionCompliance(boolean enabled); + + /** + * @see JpaCompliance#isJpaListComplianceEnabled() + */ + SessionFactoryBuilder enableJpaListCompliance(boolean enabled); + + /** + * @see JpaCompliance#isJpaClosedComplianceEnabled() + */ + SessionFactoryBuilder enableJpaClosedCompliance(boolean enabled); + + /** * Allows unwrapping this builder as another, more specific type. * diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java index b18f8d267d..0bf84e653a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java @@ -52,6 +52,8 @@ import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.jpa.JpaCompliance; +import org.hibernate.jpa.spi.JpaComplianceImpl; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.query.criteria.LiteralHandlingMode; @@ -502,6 +504,30 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement return this; } + @Override + public SessionFactoryBuilder enableJpaQueryCompliance(boolean enabled) { + this.options.jpaCompliance.setQueryCompliance( enabled ); + return this; + } + + @Override + public SessionFactoryBuilder enableJpaTransactionCompliance(boolean enabled) { + this.options.jpaCompliance.setTransactionCompliance( enabled ); + return this; + } + + @Override + public SessionFactoryBuilder enableJpaListCompliance(boolean enabled) { + this.options.jpaCompliance.setListCompliance( enabled ); + return this; + } + + @Override + public SessionFactoryBuilder enableJpaClosedCompliance(boolean enabled) { + this.options.jpaCompliance.setClosedCompliance( enabled ); + return this; + } + @Override @SuppressWarnings("unchecked") public T unwrap(Class type) { @@ -636,6 +662,8 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement private Map sqlFunctions; + private JpaComplianceImpl jpaCompliance; + public SessionFactoryOptionsStateStandardImpl(StandardServiceRegistry serviceRegistry) { this.serviceRegistry = serviceRegistry; @@ -855,6 +883,9 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement configurationSettings, false ); + + // added the boolean parameter in case we want to define some form of "all" as discussed + this.jpaCompliance = new JpaComplianceImpl( configurationSettings, false ); } private static Interceptor determineInterceptor(Map configurationSettings, StrategySelector strategySelector) { @@ -996,6 +1027,7 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement return allowOutOfTransactionUpdateOperations; } + @Override public boolean isReleaseResourcesOnCloseEnabled() { return releaseResourcesOnCloseEnabled; @@ -1313,6 +1345,11 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement public boolean jdbcStyleParamsZeroBased() { return this.jdbcStyleParamsZeroBased; } + + @Override + public JpaCompliance getJpaCompliance() { + return jpaCompliance; + } } private static Supplier interceptorSupplier(Class clazz) { @@ -1354,6 +1391,11 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement return options.releaseResourcesOnCloseEnabled; } + @Override + public JpaCompliance getJpaCompliance() { + return options.jpaCompliance; + } + @Override public Object getBeanManagerReference() { return options.getBeanManagerReference(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java index 4dc330db2e..2b03a8aa22 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java @@ -24,6 +24,7 @@ import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; import org.hibernate.internal.log.DeprecationLogger; +import org.hibernate.jpa.JpaCompliance; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.query.criteria.LiteralHandlingMode; @@ -133,6 +134,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions { private boolean queryParametersValidationEnabled; private LiteralHandlingMode criteriaLiteralHandlingMode; private boolean jdbcStyleParamsZeroBased; + private final JpaCompliance jpaCompliance; public SessionFactoryOptionsImpl(SessionFactoryOptionsState state) { this.serviceRegistry = state.getServiceRegistry(); @@ -218,6 +220,8 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions { this.sqlFunctions = state.getCustomSqlFunctionMap(); this.jdbcTimeZone = state.getJdbcTimeZone(); + + this.jpaCompliance = state.getJpaCompliance(); } @Override @@ -568,4 +572,9 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions { public boolean jdbcStyleParamsZeroBased() { return jdbcStyleParamsZeroBased; } + + @Override + public JpaCompliance getJpaCompliance() { + return jpaCompliance; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java index 3373944fc6..44d8f4781c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java @@ -24,6 +24,7 @@ import org.hibernate.cfg.BaselineSessionEventsListenerBuilder; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.jpa.JpaCompliance; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.query.criteria.LiteralHandlingMode; @@ -58,6 +59,8 @@ public interface SessionFactoryOptionsState { boolean isReleaseResourcesOnCloseEnabled(); + JpaCompliance getJpaCompliance(); + Object getBeanManagerReference(); Object getValidatorFactoryReference(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java index 1bdc3dd7c6..a5e49def13 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryBuilder.java @@ -389,6 +389,30 @@ public abstract class AbstractDelegatingSessionFactoryBuilder S unwrap(Class type) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index a57efbefcf..0a8de75e9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -22,6 +22,7 @@ import org.hibernate.cfg.BaselineSessionEventsListenerBuilder; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.jpa.JpaCompliance; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.query.criteria.LiteralHandlingMode; @@ -400,4 +401,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp public boolean jdbcStyleParamsZeroBased() { return delegate.jdbcStyleParamsZeroBased(); } + + @Override + public JpaCompliance getJpaCompliance() { + return delegate.getJpaCompliance(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 2291c00957..62d71c96eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -23,6 +23,7 @@ import org.hibernate.cfg.BaselineSessionEventsListenerBuilder; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.jpa.JpaCompliance; import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.query.criteria.LiteralHandlingMode; @@ -250,4 +251,6 @@ public interface SessionFactoryOptions { } boolean jdbcStyleParamsZeroBased(); + + JpaCompliance getJpaCompliance(); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 5c7a8e08a1..9814c5f5a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -10,9 +10,11 @@ import java.util.function.Supplier; import javax.persistence.GeneratedValue; +import org.hibernate.Transaction; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.registry.classloading.internal.TcclLookupPrecedence; import org.hibernate.internal.log.DeprecationLogger; +import org.hibernate.jpa.JpaCompliance; import org.hibernate.query.internal.ParameterMetadataImpl; import org.hibernate.resource.transaction.spi.TransactionCoordinator; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; @@ -1741,4 +1743,51 @@ public interface AvailableSettings { * `hibernate_sequence` name should disable this setting, */ String PREFER_GENERATOR_NAME_AS_DEFAULT_SEQUENCE_NAME = "hibernate.model.generator_name_as_sequence_name"; + + /** + * Should Hibernate's {@link Transaction} behave as + * defined by the spec for JPA's {@link javax.persistence.EntityTransaction} + * since it extends the JPA one. + * + * @see JpaCompliance#isJpaTransactionComplianceEnabled() + */ + String JPA_TRANSACTION_COMPLIANCE = "hibernate.jpa.compliance.transaction"; + + /** + * Controls whether Hibernate's handling of {@link javax.persistence.Query} + * (JPQL, Criteria and native-query) should strictly follow the JPA spec. + * Tis includes both in terms of parsing or translating a query as well + * as calls to the {@link javax.persistence.Query} methods throwing spec + * defined exceptions where as Hibernate might not. + * + * Deviations result in an exception if enabled + * + * @see JpaCompliance#isJpaQueryComplianceEnabled() + */ + String JPA_QUERY_COMPLIANCE = "hibernate.jpa.compliance.query"; + + /** + * Controls whether Hibernate should recognize what it considers a "bag" + * ({@link org.hibernate.collection.internal.PersistentBag}) as a List + * ({@link org.hibernate.collection.internal.PersistentList}) or as a bag. + * + * If enabled, we will recognize it as a List where {@link @{@link javax.persistence.OrderColumn}} + * is just missing (and its defaults will apply). + * + * @see JpaCompliance#isJpaListComplianceEnabled() + */ + String JPA_LIST_COMPLIANCE = "hibernate.jpa.compliance.list"; + + /** + * JPA defines specific exceptions on specific methods when called on + * {@link javax.persistence.EntityManager} and {@link javax.persistence.EntityManagerFactory} + * when those objects have been closed. This setting controls + * whether the spec defined behavior or Hibernate's behavior will be used. + * + * If enabled Hibernate will operate in the JPA specified way throwing + * exceptions when the spec says it should. + * + * @see JpaCompliance#isJpaClosedComplianceEnabled() + */ + String JPA_CLOSED_COMPLIANCE = "hibernate.jpa.compliance.closed"; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java index 3270406a45..3c9252be02 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/TransactionImpl.java @@ -13,6 +13,7 @@ import org.hibernate.TransactionException; import org.hibernate.engine.spi.ExceptionConverter; import org.hibernate.engine.transaction.spi.TransactionImplementor; import org.hibernate.internal.CoreLogging; +import org.hibernate.jpa.JpaCompliance; import org.hibernate.resource.transaction.spi.TransactionCoordinator; import org.hibernate.resource.transaction.spi.TransactionStatus; @@ -29,12 +30,24 @@ public class TransactionImpl implements TransactionImplementor { private final TransactionCoordinator transactionCoordinator; private final ExceptionConverter exceptionConverter; + private final JpaCompliance jpaCompliance; + private TransactionDriver transactionDriverControl; - public TransactionImpl(TransactionCoordinator transactionCoordinator, ExceptionConverter exceptionConverter) { + public TransactionImpl( + TransactionCoordinator transactionCoordinator, + ExceptionConverter exceptionConverter, + JpaCompliance jpaCompliance) { this.transactionCoordinator = transactionCoordinator; this.exceptionConverter = exceptionConverter; - transactionDriverControl = transactionCoordinator.getTransactionDriverControl(); + this.jpaCompliance = jpaCompliance; + + this.transactionDriverControl = transactionCoordinator.getTransactionDriverControl(); + + LOG.debugf( + "On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == %s", + jpaCompliance.isJpaTransactionComplianceEnabled() + ); } @Override @@ -82,7 +95,14 @@ public class TransactionImpl implements TransactionImplementor { @Override public void rollback() { - // todo : may need a "JPA compliant" flag here + if ( jpaCompliance.isJpaTransactionComplianceEnabled() ) { + if ( !isActive() ) { + throw new IllegalStateException( + "JPA compliance dictates throwing IllegalStateException when #rollback " + + "is called on non-active transaction" + ); + } + } TransactionStatus status = getStatus(); if ( status == TransactionStatus.ROLLED_BACK || status == TransactionStatus.NOT_ACTIVE ) { @@ -140,11 +160,29 @@ public class TransactionImpl implements TransactionImplementor { @Override public void setRollbackOnly() { + if ( jpaCompliance.isJpaTransactionComplianceEnabled() ) { + if ( !isActive() ) { + throw new IllegalStateException( + "JPA compliance dictates throwing IllegalStateException when #setRollbackOnly " + + "is called on non-active transaction" + ); + } + } + internalGetTransactionDriverControl().markRollbackOnly(); } @Override public boolean getRollbackOnly() { + if ( jpaCompliance.isJpaTransactionComplianceEnabled() ) { + if ( !isActive() ) { + throw new IllegalStateException( + "JPA compliance dictates throwing IllegalStateException when #getRollbackOnly " + + "is called on non-active transaction" + ); + } + } + return getStatus() == TransactionStatus.MARKED_ROLLBACK; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index c953b5f011..454a4e8cae 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -374,7 +374,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont @Override public Transaction getTransaction() throws HibernateException { - if ( getFactory().getSessionFactoryOptions().isJpaBootstrap() ) { + if ( getFactory().getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() ) { // JPA requires that we throw IllegalStateException if this is called // on a JTA EntityManager if ( getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta() ) { @@ -383,6 +383,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont } } } + return accessTransaction(); } @@ -391,7 +392,8 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont if ( this.currentHibernateTransaction == null || this.currentHibernateTransaction.getStatus() != TransactionStatus.ACTIVE ) { this.currentHibernateTransaction = new TransactionImpl( getTransactionCoordinator(), - getExceptionConverter() + getExceptionConverter(), + getFactory().getSessionFactoryOptions().getJpaCompliance() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java index 98b7ec7fba..1f2f18d03d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java @@ -110,19 +110,32 @@ public final class ConfigurationHelper { * @return The value. */ public static boolean getBoolean(String name, Map values, boolean defaultValue) { - Object value = values.get( name ); + final Object raw = values.get( name ); + + final Boolean value = toBoolean( raw, defaultValue ); + if ( value == null ) { + throw new ConfigurationException( + "Could not determine how to handle configuration raw [name=" + name + ", value=" + raw + "] as boolean" + ); + } + + return value; + } + + public static Boolean toBoolean(Object value, boolean defaultValue) { if ( value == null ) { return defaultValue; } + if ( Boolean.class.isInstance( value ) ) { - return ( (Boolean) value ).booleanValue(); + return (Boolean) value; } + if ( String.class.isInstance( value ) ) { return Boolean.parseBoolean( (String) value ); } - throw new ConfigurationException( - "Could not determine how to handle configuration value [name=" + name + ", value=" + value + "] as boolean" - ); + + return null; } /** diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/JpaCompliance.java b/hibernate-core/src/main/java/org/hibernate/jpa/JpaCompliance.java new file mode 100644 index 0000000000..acf1729924 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/JpaCompliance.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa; + +import org.hibernate.Transaction; + +/** + * Encapsulates settings controlling whether certain aspects of the JPA spec + * should be strictly followed. + * + * @author Steve Ebersole + */ +public interface JpaCompliance { + /** + * Controls whether Hibernate's handling of JPA's + * {@link javax.persistence.Query} (JPQL, Criteria and native-query) should + * strictly follow the JPA spec. This includes both in terms of parsing or + * translating a query as well as calls to the {@link javax.persistence.Query} + * methods throwing spec defined exceptions where as Hibernate might not. + * + * Deviations result in an exception if enabled + * + * @return {@code true} indicates to behave in the spec-defined way + */ + boolean isJpaQueryComplianceEnabled(); + + /** + * Indicates that Hibernate's {@link Transaction} should behave as + * defined by the spec for JPA's {@link javax.persistence.EntityTransaction} + * since it extends the JPA one. + * + * @return {@code true} indicates to behave in the spec-defined way + */ + boolean isJpaTransactionComplianceEnabled(); + + /** + * Controls how Hibernate interprets a mapped List without an "order columns" + * specified. Historically Hibernate defines this as a "bag", which is a concept + * JPA does not have. + * + * If enabled, Hibernate will recognize this condition as defining + * a {@link org.hibernate.collection.internal.PersistentList}, otherwise + * Hibernate will treat is as a {@link org.hibernate.collection.internal.PersistentBag} + * + * @return {@code true} indicates to behave in the spec-defined way, interpreting the + * mapping as a "list", rather than a "bag" + */ + boolean isJpaListComplianceEnabled(); + + /** + *JPA defines specific exceptions on specific methods when called on + * {@link javax.persistence.EntityManager} and {@link javax.persistence.EntityManagerFactory} + * when those objects have been closed. This setting controls + * whether the spec defined behavior or Hibernate's behavior will be used. + * + * If enabled Hibernate will operate in the JPA specified way throwing + * exceptions when the spec says it should with regard to close checking + * + * @return {@code true} indicates to behave in the spec-defined way + */ + boolean isJpaClosedComplianceEnabled(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaComplianceImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaComplianceImpl.java new file mode 100644 index 0000000000..678d54314d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/JpaComplianceImpl.java @@ -0,0 +1,90 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.spi; + +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.jpa.JpaCompliance; + +/** + * @author Steve Ebersole + */ +public class JpaComplianceImpl implements JpaCompliance { + private boolean queryCompliance; + private boolean transactionCompliance; + private boolean listCompliance; + private boolean closedCompliance; + + + @SuppressWarnings("ConstantConditions") + public JpaComplianceImpl(Map configurationSettings, boolean jpaByDefault) { + final Object legacyQueryCompliance = configurationSettings.get( AvailableSettings.JPAQL_STRICT_COMPLIANCE ); + + queryCompliance = ConfigurationHelper.getBoolean( + AvailableSettings.JPA_QUERY_COMPLIANCE, + configurationSettings, + legacyQueryCompliance == null ? jpaByDefault : ConfigurationHelper.toBoolean( legacyQueryCompliance, jpaByDefault ) + ); + transactionCompliance = ConfigurationHelper.getBoolean( + AvailableSettings.JPA_TRANSACTION_COMPLIANCE, + configurationSettings, + jpaByDefault + ); + listCompliance = ConfigurationHelper.getBoolean( + AvailableSettings.JPA_LIST_COMPLIANCE, + configurationSettings, + jpaByDefault + ); + closedCompliance = ConfigurationHelper.getBoolean( + AvailableSettings.JPA_CLOSED_COMPLIANCE, + configurationSettings, + jpaByDefault + ); + } + + @Override + public boolean isJpaQueryComplianceEnabled() { + return queryCompliance; + } + + @Override + public boolean isJpaTransactionComplianceEnabled() { + return transactionCompliance; + } + + @Override + public boolean isJpaListComplianceEnabled() { + return listCompliance; + } + + @Override + public boolean isJpaClosedComplianceEnabled() { + return closedCompliance; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Mutators + + public void setQueryCompliance(boolean queryCompliance) { + this.queryCompliance = queryCompliance; + } + + public void setTransactionCompliance(boolean transactionCompliance) { + this.transactionCompliance = transactionCompliance; + } + + public void setListCompliance(boolean listCompliance) { + this.listCompliance = listCompliance; + } + + public void setClosedCompliance(boolean closedCompliance) { + this.closedCompliance = closedCompliance; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinatorBuilder.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinatorBuilder.java index ab638eafbe..e27af47551 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinatorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/spi/TransactionCoordinatorBuilder.java @@ -23,7 +23,7 @@ public interface TransactionCoordinatorBuilder extends Service { /** * Access to options to are specific to each TransactionCoordinator instance */ - static interface Options { + interface Options { /** * Indicates whether an active transaction should be automatically joined. Only relevant * for JTA-based TransactionCoordinator instances. diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/JpaComplianceTestingImpl.java b/hibernate-core/src/test/java/org/hibernate/jpa/JpaComplianceTestingImpl.java new file mode 100644 index 0000000000..3991b71fb8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/JpaComplianceTestingImpl.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa; + +/** + * @author Steve Ebersole + */ +public class JpaComplianceTestingImpl implements JpaCompliance { + public static JpaCompliance normal() { + return new JpaComplianceTestingImpl(); + } + + public static JpaCompliance withTransactionCompliance() { + final JpaComplianceTestingImpl compliance = new JpaComplianceTestingImpl(); + compliance.transactionCompliance = true; + return compliance; + } + + public static JpaCompliance withQueryCompliance() { + final JpaComplianceTestingImpl compliance = new JpaComplianceTestingImpl(); + compliance.queryCompliance = true; + return compliance; + } + + public static JpaCompliance withListCompliance() { + final JpaComplianceTestingImpl compliance = new JpaComplianceTestingImpl(); + compliance.listCompliance = true; + return compliance; + } + + public static JpaCompliance withClosedCompliance() { + final JpaComplianceTestingImpl compliance = new JpaComplianceTestingImpl(); + compliance.closedCompliance = true; + return compliance; + } + + private boolean queryCompliance; + private boolean transactionCompliance; + private boolean listCompliance; + private boolean closedCompliance; + + @Override + public boolean isJpaQueryComplianceEnabled() { + return queryCompliance; + } + + @Override + public boolean isJpaTransactionComplianceEnabled() { + return transactionCompliance; + } + + @Override + public boolean isJpaListComplianceEnabled() { + return listCompliance; + } + + @Override + public boolean isJpaClosedComplianceEnabled() { + return closedCompliance; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/EntityTransactionTests.java b/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/EntityTransactionTests.java new file mode 100644 index 0000000000..a5a4be0924 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/jpa/compliance/tck2_2/EntityTransactionTests.java @@ -0,0 +1,132 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.jpa.compliance.tck2_2; + +import org.hibernate.Transaction; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil2.inSession; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +/** + * @author Steve Ebersole + */ +public class EntityTransactionTests extends BaseUnitTestCase { + + @Test + public void testGetRollbackOnlyExpectations() { + final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ) + .build(); + + try { + final SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) new MetadataSources( ssr ) + .buildMetadata() + .buildSessionFactory(); + + try { + inSession( + sessionFactory, + session -> { + final Transaction transaction = session.getTransaction(); + assertFalse( transaction.isActive() ); + try { + transaction.getRollbackOnly(); + fail( "Expecting failure #getRollbackOnly on non-active txn" ); + } + catch (IllegalStateException expected) { + } + } + ); + } + finally { + sessionFactory.close(); + } + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + @Test + public void testSetRollbackOnlyExpectations() { + final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ) + .build(); + + try { + final SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) new MetadataSources( ssr ) + .buildMetadata() + .buildSessionFactory(); + + try { + inSession( + sessionFactory, + session -> { + final Transaction transaction = session.getTransaction(); + assertFalse( transaction.isActive() ); + try { + transaction.setRollbackOnly(); + fail( "Expecting failure #setRollbackOnly on non-active txn" ); + } + catch (IllegalStateException expected) { + } + } + ); + } + finally { + sessionFactory.close(); + } + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + @Test + public void testRollbackExpectations() { + final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ) + .build(); + + try { + final SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) new MetadataSources( ssr ) + .buildMetadata() + .buildSessionFactory(); + + try { + inSession( + sessionFactory, + session -> { + final Transaction transaction = session.getTransaction(); + assertFalse( transaction.isActive() ); + try { + transaction.rollback(); + fail( "Expecting failure #rollback on non-active txn" ); + } + catch (IllegalStateException expected) { + } + } + ); + } + finally { + sessionFactory.close(); + } + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/BasicJdbcTransactionTests.java b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/BasicJdbcTransactionTests.java index 9108845ad8..86f182d638 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/BasicJdbcTransactionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/BasicJdbcTransactionTests.java @@ -9,7 +9,6 @@ package org.hibernate.test.resource.transaction.jdbc; import org.hibernate.TransactionException; import org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl; import org.hibernate.resource.transaction.spi.TransactionCoordinator; -import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; import org.hibernate.resource.transaction.spi.TransactionStatus; import org.hibernate.test.resource.common.SynchronizationCollectorImpl; @@ -31,12 +30,7 @@ public class BasicJdbcTransactionTests { final TransactionCoordinator transactionCoordinator = transactionCoordinatorBuilder.buildTransactionCoordinator( owner, - new TransactionCoordinatorBuilder.Options() { - @Override - public boolean shouldAutoJoinTransaction() { - return false; - } - } + () -> false ); SynchronizationCollectorImpl sync = new SynchronizationCollectorImpl(); @@ -63,12 +57,7 @@ public class BasicJdbcTransactionTests { final TransactionCoordinator transactionCoordinator = transactionCoordinatorBuilder.buildTransactionCoordinator( owner, - new TransactionCoordinatorBuilder.Options() { - @Override - public boolean shouldAutoJoinTransaction() { - return false; - } - } + () -> false ); assertEquals( TransactionStatus.NOT_ACTIVE, transactionCoordinator.getTransactionDriverControl().getStatus() ); @@ -98,12 +87,7 @@ public class BasicJdbcTransactionTests { final TransactionCoordinator transactionCoordinator = transactionCoordinatorBuilder.buildTransactionCoordinator( owner, - new TransactionCoordinatorBuilder.Options() { - @Override - public boolean shouldAutoJoinTransaction() { - return false; - } - } + () -> false ); assertEquals( TransactionStatus.NOT_ACTIVE, transactionCoordinator.getTransactionDriverControl().getStatus() ); diff --git a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractRegionAccessStrategyTest.java b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractRegionAccessStrategyTest.java index ada6c4701b..c0e12b9735 100644 --- a/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractRegionAccessStrategyTest.java +++ b/hibernate-infinispan/src/test/java/org/hibernate/test/cache/infinispan/AbstractRegionAccessStrategyTest.java @@ -60,11 +60,6 @@ import org.junit.After; import org.junit.Test; import junit.framework.AssertionFailedError; -import org.infinispan.Cache; -import org.infinispan.test.TestingUtil; - -import org.jboss.logging.Logger; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -339,7 +334,10 @@ public abstract class AbstractRegionAccessStrategyTest { - Transaction tx = new TransactionImpl(txCoord, session.getExceptionConverter()); + Transaction tx = new TransactionImpl( + txCoord, + session.getExceptionConverter(), + session.getFactory().getSessionFactoryOptions().getJpaCompliance() ); tx.begin(); return tx; });