Also Javadoc for JPA compliance stuff
clarify logic of a method in AbstractEntityPersister (there was actually a bug here)
This commit is contained in:
parent
ccf88c925e
commit
a3ef7b906d
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue