Also Javadoc for JPA compliance stuff

clarify logic of a method in AbstractEntityPersister
(there was actually a bug here)
This commit is contained in:
Gavin King 2022-01-26 08:20:32 +01:00
parent ccf88c925e
commit a3ef7b906d
4 changed files with 137 additions and 84 deletions

View File

@ -2151,6 +2151,13 @@ public interface AvailableSettings {
String JPA_LIST_COMPLIANCE = "hibernate.jpa.compliance.list";
/**
* JPA specifies that items occurring in {@link jakarta.persistence.OrderBy}
* lists must be references to entity attributes, whereas Hibernate, by default,
* allows more complex expressions.
* <p>
* If enabled, an exception is thrown for items which are not entity attribute
* references.
*
* @see org.hibernate.jpa.spi.JpaCompliance#isJpaOrderByMappingComplianceEnabled()
*
* @since 6.0
@ -2158,11 +2165,14 @@ public interface AvailableSettings {
String JPA_ORDER_BY_MAPPING_COMPLIANCE = "hibernate.jpa.compliance.orderby";
/**
* JPA defines specific exception types that must be thrown by specific
* methods of {@link jakarta.persistence.EntityManager} and
* {@link jakarta.persistence.EntityManagerFactory} when the object has
* been closed. When enabled, this setting specifies that Hibernate should
* comply with the specification.
* JPA specifies that an {@link IllegalStateException} must be thrown by
* {@link jakarta.persistence.EntityManager#close()} and
* {@link jakarta.persistence.EntityManagerFactory#close()} if the object has
* already been closed. By default, Hibernate treats any additional call to
* {@code close()} as a noop.
* <p>
* When enabled, this setting forces Hibernate to throw an exception if
* {@code close()} is called on an instance that was already closed.
*
* @see org.hibernate.jpa.spi.JpaCompliance#isJpaClosedComplianceEnabled()
*
@ -2192,17 +2202,33 @@ public interface AvailableSettings {
String JPA_PROXY_COMPLIANCE = "hibernate.jpa.compliance.proxy";
/**
* By default, Hibernate uses second-level cache invalidation for entities
* with {@linkplain jakarta.persistence.SecondaryTable secondary tables}
* in order to avoid the possibility of inconsistent cached data in the
* case where different transactions simultaneously update different table
* rows corresponding to the same entity instance.
* <p>
* The JPA TCK, for no good reason, requires that entities with secondary
* tables be immediately cached in the second-level cache rather than
* invalidated and re-cached on a subsequent read.
* <p>
* Note that Hibernate's default behavior here is safer and more careful
* than the behavior mandated by the TCK but YOLO.
* <p>
* When enabled, this setting makes Hibernate pass the TCK.
*
* @see org.hibernate.jpa.spi.JpaCompliance#isJpaCacheComplianceEnabled()
* @see org.hibernate.persister.entity.AbstractEntityPersister#isCacheInvalidationRequired()
*
* @since 5.3
*/
String JPA_CACHING_COMPLIANCE = "hibernate.jpa.compliance.caching";
/**
* Determines whether the scope of any identifier generator name specified via
* {@link jakarta.persistence.TableGenerator#name()} or
* {@link jakarta.persistence.SequenceGenerator#name()} is considered global to
* the persistence unit, or local to the entity in which identifier generator
* Determines whether the scope of any identifier generator name specified
* via {@link jakarta.persistence.TableGenerator#name()} or
* {@link jakarta.persistence.SequenceGenerator#name()} is considered global
* to the persistence unit, or local to the entity in which identifier generator
* is defined.
* <p>
* If enabled, the name will be considered globally scoped, and so the existence
@ -2216,6 +2242,16 @@ public interface AvailableSettings {
String JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE = "hibernate.jpa.compliance.global_id_generators";
/**
* Determines if an identifier value passed to
* {@link jakarta.persistence.EntityManager#find} or
* {@link jakarta.persistence.EntityManager#getReference} may be
* {@linkplain org.hibernate.type.descriptor.java.JavaType#coerce coerced} to
* the identifier type declared by the entity. For example, an {@link Integer}
* argument might be widened to {@link Long}.
* <p>
* By default, coercion is allowed. When enabled, coercion is disallowed, as
* required by the JPA specification.
*
* @see org.hibernate.jpa.spi.JpaCompliance#isLoadByIdComplianceEnabled()
*
* @since 6.0
@ -2276,7 +2312,7 @@ public interface AvailableSettings {
* <p>
* However, for database systems supporting execution plan caching, there's a
* better chance of hitting the cache if the number of possible {@code IN} clause
* parameters is smaller.
* parameter list lengths is smaller.
* <p>
* When this setting is enabled, we expand the number of bind parameters to an
* integer power of two: 4, 8, 16, 32, 64. Thus, if 5, 6, or 7 arguments are bound

View File

@ -9,18 +9,19 @@ package org.hibernate.jpa.spi;
import org.hibernate.Transaction;
/**
* Encapsulates settings controlling whether certain aspects of the JPA spec
* should be strictly followed.
* Encapsulates settings controlling whether Hibernate complies strictly
* with certain debatable strictures of the JPA specification.
*
* @author Steve Ebersole
*/
public interface JpaCompliance {
/**
* Controls whether Hibernate's handling of JPA's
* {@link jakarta.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 jakarta.persistence.Query}
* methods throwing spec defined exceptions where as Hibernate might not.
* {@link jakarta.persistence.Query} (JPQL, Criteria and native-query)
* should strictly follow the JPA spec. This includes parsing and
* translating a query as JPQL instead of HQL, as well as whether calls
* to the {@link jakarta.persistence.Query} methods always throw the
* exceptions defined by the specification.
*
* Deviations result in an exception if enabled
*
@ -32,8 +33,8 @@ public interface JpaCompliance {
/**
* Indicates that Hibernate's {@link Transaction} should behave as
* defined by the spec for JPA's {@link jakarta.persistence.EntityTransaction}
* since it extends the JPA one.
* defined by the specification for JPA's
* {@link jakarta.persistence.EntityTransaction} since it extends it.
*
* @return {@code true} indicates to behave in the spec-defined way
*
@ -42,16 +43,17 @@ public interface JpaCompliance {
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.
* Controls how Hibernate interprets a mapped List without an
* "order column" specified. Historically Hibernate defines this as
* a "bag", which is a concept JPA does not have.
* <p>
* If enabled, Hibernate will recognize this condition as defining
* a {@link org.hibernate.collection.spi.PersistentList}, otherwise
* Hibernate will treat is as a {@link org.hibernate.collection.spi.PersistentBag}
* Hibernate will treat is as a
* {@link org.hibernate.collection.spi.PersistentBag}
*
* @return {@code true} indicates to behave in the spec-defined way, interpreting the
* mapping as a "list", rather than a "bag"
* @return {@code true} indicates to behave in the spec-defined way,
* interpreting the mapping as a "list", rather than a "bag"
*
* @see org.hibernate.cfg.AvailableSettings#JPA_LIST_COMPLIANCE
*/
@ -59,9 +61,10 @@ public interface JpaCompliance {
/**
* JPA defines specific exceptions on specific methods when called on
* {@link jakarta.persistence.EntityManager} and {@link jakarta.persistence.EntityManagerFactory}
* when those objects have been closed. This setting controls
* whether the spec defined behavior or Hibernate's behavior will be used.
* {@link jakarta.persistence.EntityManager} and
* {@link jakarta.persistence.EntityManagerFactory} when those objects
* have been closed. This setting controls whether the spec defined
* behavior or Hibernate's behavior will be used.
* <p>
* If enabled Hibernate will operate in the JPA specified way throwing
* exceptions when the spec says it should with regard to close checking
@ -74,14 +77,15 @@ public interface JpaCompliance {
/**
* JPA spec says that an {@link jakarta.persistence.EntityNotFoundException}
* should be thrown when accessing an entity Proxy which does not have an associated
* table row in the database.
* should be thrown when accessing an entity proxy which does not have
* an associated table row in the database.
* <p>
* Traditionally, Hibernate does not initialize an entity Proxy when accessing its
* identifier since we already know the identifier value, hence we can save a database
* round trip.
* Traditionally, Hibernate does not initialize an entity Proxy when
* accessing its identifier since we already know the identifier value,
* hence we can save a database round trip.
* <p>
* If enabled Hibernate will initialize the entity Proxy even when accessing its identifier.
* If enabled Hibernate will initialize the entity proxy even when
* accessing its identifier.
*
* @return {@code true} indicates to behave in the spec-defined way
*
@ -90,25 +94,26 @@ public interface JpaCompliance {
boolean isJpaProxyComplianceEnabled();
/**
* Should Hibernate comply with all aspects of caching as defined by JPA? Or can
* it deviate to perform things it believes will be "better"?
* Should Hibernate comply with all aspects of caching as defined by JPA?
* Or can it deviate to perform things it believes will be "better"?
*
* @implNote Effects include marking all secondary tables as non-optional. The reason
* being that optional secondary tables can lead to entity cache being invalidated rather
* than updated.
* @implNote Effects include marking all secondary tables as non-optional.
* The reason being that optional secondary tables can lead to entity cache
* being invalidated rather than updated.
*
* @return {@code true} says to act the spec-defined way.
* @return {@code true} indicates to behave in the spec-defined way
*
* @see org.hibernate.cfg.AvailableSettings#JPA_CACHING_COMPLIANCE
* @see org.hibernate.persister.entity.AbstractEntityPersister#isCacheInvalidationRequired()
*/
boolean isJpaCacheComplianceEnabled();
/**
* Should the scope of {@link jakarta.persistence.TableGenerator#name()} and
* {@link jakarta.persistence.SequenceGenerator#name()} be considered globally or locally
* defined?
* Should the scope of {@link jakarta.persistence.TableGenerator#name()}
* and {@link jakarta.persistence.SequenceGenerator#name()} be considered
* globally or locally defined?
*
* @return {@code true} indicates the generator name scope is considered global.
* @return {@code true} if the generator name scope is considered global
*
* @see org.hibernate.cfg.AvailableSettings#JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE
*/
@ -117,25 +122,27 @@ public interface JpaCompliance {
/**
* Should we strictly handle {@link jakarta.persistence.OrderBy} expressions?
* <p>
* JPA says the order-items can only be attribute references whereas Hibernate supports a
* wide range of items. With this enabled, Hibernate will throw a compliance error when a
* non-attribute-reference is used.
* JPA says the order-items can only be attribute references whereas
* Hibernate supports a wide range of items. With this enabled, Hibernate
* will throw a compliance error when a non-attribute-reference is used.
*
* @see org.hibernate.cfg.AvailableSettings#JPA_ORDER_BY_MAPPING_COMPLIANCE
*/
boolean isJpaOrderByMappingComplianceEnabled();
/**
* JPA says that the id passed to {@link jakarta.persistence.EntityManager#getReference} and
* {@link jakarta.persistence.EntityManager#find} should be exactly the expected type, allowing
* no type coercion.
* JPA says that the id passed to
* {@link jakarta.persistence.EntityManager#getReference} and
* {@link jakarta.persistence.EntityManager#find} should be exactly the
* expected type, allowing no type coercion.
* <p>
* Historically, Hibernate behaved the same way. Since 6.0 however, Hibernate has the ability
* to coerce the passed type to the expected type. For example, an {@link Integer} may be
* widened to {@link Long}. Coercion is performed by calling
* Historically, Hibernate behaved the same way. Since 6.0 however,
* Hibernate has the ability to coerce the passed type to the expected
* type. For example, an {@link Integer} may be widened to {@link Long}.
* Coercion is performed by calling
* {@link org.hibernate.type.descriptor.java.JavaType#coerce}.
* <p>
* This setting controls whether such a coercion should be allowed.
* This setting controls whether such coercion should be allowed.
*
* @see org.hibernate.cfg.AvailableSettings#JPA_LOAD_BY_ID_COMPLIANCE
*

View File

@ -9,7 +9,6 @@ package org.hibernate.metamodel.spi;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.hibernate.EntityNameResolver;
import org.hibernate.MappingException;

View File

@ -96,7 +96,6 @@ import org.hibernate.engine.spi.SelfDirtinessTracker;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.ValueInclusion;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.LoadEvent;
import org.hibernate.id.Assigned;
@ -257,7 +256,6 @@ import org.hibernate.tuple.NonIdentifierAttribute;
import org.hibernate.tuple.ValueGeneration;
import org.hibernate.tuple.entity.EntityBasedAssociationAttribute;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.tuple.entity.EntityTuplizer;
import org.hibernate.type.AnyType;
import org.hibernate.type.AssociationType;
import org.hibernate.type.BasicType;
@ -268,7 +266,6 @@ import org.hibernate.type.Type;
import org.hibernate.type.TypeHelper;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Basic functionality for persisting an entity via JDBC
@ -1087,7 +1084,7 @@ public abstract class AbstractEntityPersister
this.cacheEntryHelper = buildCacheEntryHelper();
if ( sessionFactoryOptions.isSecondLevelCacheEnabled() ) {
this.invalidateCache = canWriteToCache && determineWhetherToInvalidateCache( bootDescriptor, creationContext );
this.invalidateCache = canWriteToCache && shouldInvalidateCache( bootDescriptor, creationContext );
}
else {
this.invalidateCache = false;
@ -1102,41 +1099,50 @@ public abstract class AbstractEntityPersister
return new SingleIdEntityLoaderDynamicBatch<>( entityDescriptor, batchSize, factory );
}
@SuppressWarnings("RedundantIfStatement")
private boolean determineWhetherToInvalidateCache(
/**
* We might need to use cache invalidation is we have formulas,
* dynamic update, or secondary tables.
*
* @see #isCacheInvalidationRequired()
*/
private boolean shouldInvalidateCache(
PersistentClass persistentClass,
PersisterCreationContext creationContext) {
if ( hasFormulaProperties() ) {
// we need to evaluate formulas in the database
return true;
}
if ( isVersioned() ) {
else if ( isVersioned() ) {
// we don't need to be "careful" in the case of
// versioned entities
return false;
}
if ( entityMetamodel.isDynamicUpdate() ) {
else if ( entityMetamodel.isDynamicUpdate() ) {
// if the unversioned entity has dynamic updates
// there is a risk of concurrent updates
return true;
}
else if ( isCacheComplianceEnabled(creationContext) ) {
// The JPA TCK (inadvertently, but still...)
// requires that we cache entities with secondary
// tables instead of being more careful and just
// invalidating them
return false;
}
else {
// if the unversioned entity has second tables
// there is a risk of concurrent updates
// todo : this should really consider optionality of the secondary tables
// in count so non-optional tables do not cause this bypass
return persistentClass.getJoinClosureSpan() >= 1;
}
}
// We need to check whether the user may have circumvented this logic (JPA TCK)
final boolean complianceEnabled = creationContext.getSessionFactory()
private boolean isCacheComplianceEnabled(PersisterCreationContext creationContext) {
return creationContext.getSessionFactory()
.getSessionFactoryOptions()
.getJpaCompliance()
.isJpaCacheComplianceEnabled();
if ( complianceEnabled ) {
// The JPA TCK (inadvertently, but still...) requires that we cache
// entities with secondary tables even though Hibernate historically
// invalidated them
return false;
}
if ( persistentClass.getJoinClosureSpan() >= 1 ) {
// todo : this should really consider optionality of the secondary tables in count
// non-optional tables do not cause this bypass
return true;
}
return false;
}
private boolean determineCanWriteToCache(PersistentClass persistentClass, EntityDataAccess cacheAccessStrategy) {
@ -1842,10 +1848,15 @@ public abstract class AbstractEntityPersister
* We can't immediately add to the cache if we have formulas
* which must be evaluated, or if we have the possibility of
* two concurrent updates to the same item being merged on
* the database. This can happen if (a) the item is not
* versioned and either (b) we have dynamic update enabled
* or (c) we have multiple tables holding the state of the
* item.
* the database. This second case can happen if:
* <ol>
* <li> the item is not versioned, and either
* <li>we have dynamic update enabled, or
* <li>the state of the item spans multiple tables.
* </ol>
* Therefore, we're careful, and just invalidate the cache in
* these cases (the item will be readded when it's read again
* fresh from the database).
*/
public boolean isCacheInvalidationRequired() {
return invalidateCache;