improvements to typesafety of NaturalIdLoadAccess

and clean up its jdoc and the jdoc of its friends
This commit is contained in:
Gavin 2023-05-24 22:47:20 +02:00 committed by Gavin King
parent 5efa49f7d1
commit 76fa597d1b
8 changed files with 369 additions and 156 deletions

View File

@ -13,24 +13,36 @@ import org.hibernate.graph.RootGraph;
/** /**
* Loads an entity by its primary identifier. * Loads an entity by its primary identifier.
* <p>
* The interface is especially useful when customizing association
* fetching using an {@link jakarta.persistence.EntityGraph}.
* <pre>
* var graph = session.createEntityGraph(Book.class);
* graph.addSubgraph(Book_.publisher);
* graph.addPluralSubgraph(Book_.authors).addSubgraph(Author_.person);
* session.byId(Book.class).withFetchGraph(graph).load(bookId);
* </pre>
* *
* @author Eric Dalquist * @author Eric Dalquist
* @author Steve Ebersole * @author Steve Ebersole
*
* @see Session#byId(Class)
*/ */
public interface IdentifierLoadAccess<T> { public interface IdentifierLoadAccess<T> {
/** /**
* Specify the {@link LockOptions} to use when retrieving the entity. * Specify the {@linkplain LockOptions lock options} to use when
* querying the database.
* *
* @param lockOptions The lock options to use. * @param lockOptions The lock options to use
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
IdentifierLoadAccess<T> with(LockOptions lockOptions); IdentifierLoadAccess<T> with(LockOptions lockOptions);
/** /**
* Specify the {@link CacheMode} to use when retrieving the entity. * Specify the {@link CacheMode} to use when obtaining an entity.
* *
* @param cacheMode The CacheMode to use. * @param cacheMode The {@code CacheMode} to use
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
@ -43,10 +55,20 @@ public interface IdentifierLoadAccess<T> {
*/ */
IdentifierLoadAccess<T> withReadOnly(boolean readOnly); IdentifierLoadAccess<T> withReadOnly(boolean readOnly);
/**
* Override the associations fetched by default by specifying
* the complete list of associations to be fetched as an
* {@linkplain jakarta.persistence.EntityGraph entity graph}.
*/
default IdentifierLoadAccess<T> withFetchGraph(RootGraph<T> graph) { default IdentifierLoadAccess<T> withFetchGraph(RootGraph<T> graph) {
return with( graph, GraphSemantic.FETCH ); return with( graph, GraphSemantic.FETCH );
} }
/**
* Augment the associations fetched by default by specifying a
* list of additional associations to be fetched as an
* {@linkplain jakarta.persistence.EntityGraph entity graph}.
*/
default IdentifierLoadAccess<T> withLoadGraph(RootGraph<T> graph) { default IdentifierLoadAccess<T> withLoadGraph(RootGraph<T> graph) {
return with( graph, GraphSemantic.LOAD ); return with( graph, GraphSemantic.LOAD );
} }
@ -59,15 +81,22 @@ public interface IdentifierLoadAccess<T> {
return withLoadGraph( graph ); return withLoadGraph( graph );
} }
/**
* Customize the associations fetched by specifying an
* {@linkplain jakarta.persistence.EntityGraph entity graph},
* and how it should be {@linkplain GraphSemantic interpreted}.
*/
IdentifierLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic); IdentifierLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic);
/** /**
* Return the persistent instance with the given identifier, assuming that the instance exists. This method * Return the persistent instance with the given identifier, assuming
* might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed. * that the instance exists. This method might return a proxied instance
* that is initialized on-demand, when a non-identifier method is accessed.
* <p> * <p>
* You should not use this method to determine if an instance exists; to check for existence, use {@link #load} * You should not use this method to determine if an instance exists;
* instead. Use this only to retrieve an instance that you assume exists, where non-existence would be an * to check for existence, use {@link #load} instead. Use this only to
* actual error. * retrieve an instance that you assume exists, where non-existence would
* be an actual error.
* *
* @param id The identifier for which to obtain a reference * @param id The identifier for which to obtain a reference
* *
@ -76,9 +105,10 @@ public interface IdentifierLoadAccess<T> {
T getReference(Object id); T getReference(Object id);
/** /**
* Return the persistent instance with the given identifier, or null if there is no such persistent instance. * Return the persistent instance with the given identifier, or null
* If the instance is already associated with the session, return that instance, initializing it if needed. This * if there is no such persistent instance. If the instance is already
* method never returns an uninitialized instance. * associated with the session, return that instance, initializing it
* if needed. This method never returns an uninitialized instance.
* *
* @param id The identifier * @param id The identifier
* *
@ -87,12 +117,12 @@ public interface IdentifierLoadAccess<T> {
T load(Object id); T load(Object id);
/** /**
* Same semantic as {@link #load} except that here {@link Optional} is returned to * Just like {@link #load}, except that here an {@link Optional} is
* handle nullability. * returned.
* *
* @param id The identifier * @param id The identifier
* *
* @return The persistent instance, if one, wrapped in Optional * @return The persistent instance, if any, as an {@link Optional}
*/ */
Optional<T> loadOptional(Object id); Optional<T> loadOptional(Object id);
} }

View File

@ -12,34 +12,53 @@ import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.RootGraph; import org.hibernate.graph.RootGraph;
/** /**
* Loads multiple entities at once by identifiers, ultimately via one of the * Loads multiple instances of a given entity type at once, by
* {@link #multiLoad} methods, using the various options specified (if any) * specifying a list of identifier values. This allows the entities
* to be fetched from the database in batches.
* <pre>
* var graph = session.createEntityGraph(Book.class);
* graph.addSubgraph(Book_.publisher);
* session.byId(Book.class).withFetchGraph(graph).multiLoad(bookIds);
* </pre>
*
* @see Session#byMultipleIds(Class)
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface MultiIdentifierLoadAccess<T> { public interface MultiIdentifierLoadAccess<T> {
/** /**
* Specify the {@link LockOptions} to use when retrieving the entity. * Specify the {@linkplain LockOptions lock options} to use when
* querying the database.
* *
* @param lockOptions The lock options to use. * @param lockOptions The lock options to use
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
MultiIdentifierLoadAccess<T> with(LockOptions lockOptions); MultiIdentifierLoadAccess<T> with(LockOptions lockOptions);
/** /**
* Specify the {@link CacheMode} to use when retrieving the entity. * Specify the {@link CacheMode} to use when obtaining an entity.
* *
* @param cacheMode The CacheMode to use. * @param cacheMode The {@code CacheMode} to use
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
MultiIdentifierLoadAccess<T> with(CacheMode cacheMode); MultiIdentifierLoadAccess<T> with(CacheMode cacheMode);
/**
* Override the associations fetched by default by specifying
* the complete list of associations to be fetched as an
* {@linkplain jakarta.persistence.EntityGraph entity graph}.
*/
default MultiIdentifierLoadAccess<T> withFetchGraph(RootGraph<T> graph) { default MultiIdentifierLoadAccess<T> withFetchGraph(RootGraph<T> graph) {
return with( graph, GraphSemantic.FETCH ); return with( graph, GraphSemantic.FETCH );
} }
/**
* Augment the associations fetched by default by specifying a
* list of additional associations to be fetched as an
* {@linkplain jakarta.persistence.EntityGraph entity graph}.
*/
default MultiIdentifierLoadAccess<T> withLoadGraph(RootGraph<T> graph) { default MultiIdentifierLoadAccess<T> withLoadGraph(RootGraph<T> graph) {
return with( graph, GraphSemantic.LOAD ); return with( graph, GraphSemantic.LOAD );
} }
@ -52,16 +71,28 @@ public interface MultiIdentifierLoadAccess<T> {
return with( graph, GraphSemantic.LOAD ); return with( graph, GraphSemantic.LOAD );
} }
/**
* Customize the associations fetched by specifying an
* {@linkplain jakarta.persistence.EntityGraph entity graph},
* and how it should be {@linkplain GraphSemantic interpreted}.
*/
MultiIdentifierLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic); MultiIdentifierLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic);
/** /**
* Specify a batch size for loading the entities (how many at a time). The default is * Specify a batch size, that is, how many entities should be
* to use a batch sizing strategy defined by the Dialect in use. Any greater-than-one * fetched in each request to the database.
* value here will override that default behavior. If giving an explicit value here, * <ul>
* care should be taken to not exceed the capabilities of the underlying database. * <li>By default, the batch sizing strategy is determined by the
* {@linkplain org.hibernate.dialect.Dialect#getBatchLoadSizingStrategy
* SQL dialect}, but
* <li>if some {@code batchSize>1} is specified as an
* argument to this method, then that batch size will be used.
* </ul>
* <p> * <p>
* Note that overall a batch-size is considered a hint. How the underlying loading * If an explicit batch size is set manually, care should be taken
* mechanism interprets that is completely up to that underlying loading mechanism. * to not exceed the capabilities of the underlying database.
* <p>
* A batch size is considered a hint.
* *
* @param batchSize The batch size * @param batchSize The batch size
* *
@ -70,53 +101,58 @@ public interface MultiIdentifierLoadAccess<T> {
MultiIdentifierLoadAccess<T> withBatchSize(int batchSize); MultiIdentifierLoadAccess<T> withBatchSize(int batchSize);
/** /**
* Specify whether we should check the {@link Session} to see whether the first-level cache already contains any of the * Specifies whether the ids of managed entity instances already
* entities to be loaded in a managed state <b>for the purpose of not including those * cached in the current persistence context should be excluded
* ids to the batch-load SQL</b>. * from the list of ids sent to the database.
* <p>
* By default, all ids are included and sent to the database.
* *
* @param enabled {@code true} enables this checking; {@code false} (the default) disables it. * @param enabled {@code true} if they should be excluded;
* {@code false} if they should be included.
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
MultiIdentifierLoadAccess<T> enableSessionCheck(boolean enabled); MultiIdentifierLoadAccess<T> enableSessionCheck(boolean enabled);
/** /**
* Should the multi-load operation be allowed to return entities that are locally * Should {@link #multiLoad} return entity instances that have been
* deleted? A locally deleted entity is one which has been passed to this * {@link Session#remove(Object) marked for removal} in the current
* Session's {@link Session#delete} / {@link Session#remove} method, but not * session, but not yet {@code delete}d in the database?
* yet flushed. The default behavior is to handle them as null in the return * <p>
* (see {@link #enableOrderedReturn}). * By default, instances marked for removal are replaced by null in
* the returned list of entities when {@link #enableOrderedReturn}
* is used.
* *
* @param enabled {@code true} enables returning the deleted entities; * @param enabled {@code true} if removed entities should be returned;
* {@code false} (the default) disables it. * {@code false} if they should be replaced by null values.
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
MultiIdentifierLoadAccess<T> enableReturnOfDeletedEntities(boolean enabled); MultiIdentifierLoadAccess<T> enableReturnOfDeletedEntities(boolean enabled);
/** /**
* Should the return List be ordered and positional in relation to the * Should the returned list of entity instances be ordered, with the
* incoming ids? If enabled (the default), the return List is ordered and * position of an entity instance determined by the position of its
* positional relative to the incoming ids. In other words, a request to * identifier in the list if ids passed to {@link #multiLoad}?
* {@code multiLoad([2,1,3])} will return {@code [Entity#2, Entity#1, Entity#3]}.
* <p> * <p>
* An important distinction is made here in regards to the handling of * By default, the returned list is ordered and the positions of the
* unknown entities depending on this "ordered return" setting. If enabled * entities correspond to the positions of their ids. In this case,
* a null is inserted into the List at the proper position(s). If disabled, * the {@linkplain #enableReturnOfDeletedEntities handling of entities
* the nulls are not put into the return List. In other words, consumers of * marked for removal} becomes important.
* the returned ordered List would need to be able to handle null elements.
* *
* @param enabled {@code true} (the default) enables ordering; * @param enabled {@code true} if entity instances should be ordered;
* {@code false} disables it. * {@code false} if they may be returned in any order.
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
MultiIdentifierLoadAccess<T> enableOrderedReturn(boolean enabled); MultiIdentifierLoadAccess<T> enableOrderedReturn(boolean enabled);
/** /**
* Perform a load of multiple entities by identifiers. See {@link #enableOrderedReturn} * Retrieve the entities with the given identifiers.
* and {@link #enableReturnOfDeletedEntities} for options which effect * <p>
* the size and "shape" of the return list. * Note that the options {@link #enableReturnOfDeletedEntities} and
* {@link #enableOrderedReturn} affect the size and shape of the
* returned list of entity instances.
* *
* @param <K> The identifier type * @param <K> The identifier type
* *
@ -126,9 +162,11 @@ public interface MultiIdentifierLoadAccess<T> {
<K> List<T> multiLoad(K... ids); <K> List<T> multiLoad(K... ids);
/** /**
* Perform a load of multiple entities by identifiers. See {@link #enableOrderedReturn} * Retrieve the entities with the given identifiers.
* and {@link #enableReturnOfDeletedEntities} for options which effect * <p>
* the size and "shape" of the return list. * Note that the options {@link #enableReturnOfDeletedEntities} and
* {@link #enableOrderedReturn} affect the size and shape of the
* returned list of entity instances.
* *
* @param ids The ids to load * @param ids The ids to load
* @param <K> The identifier type * @param <K> The identifier type

View File

@ -6,24 +6,37 @@
*/ */
package org.hibernate; package org.hibernate;
import jakarta.persistence.metamodel.SingularAttribute;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
/** /**
* Loads an entity by its natural identifier. * Loads an entity by its natural identifier, which may be a
* composite value comprising more than one attribute of the
* entity. If the entity has exactly one attribute annotated
* {@link org.hibernate.annotations.NaturalId @NaturalId},
* then {@link SimpleNaturalIdLoadAccess} may be used instead.
* <p> * <p>
* This is a generic form of load-by-natural-id covering both a single attribute * <pre>
* and multiple attributes as the natural-id. For natural-ids defined by a single * Book book =
* attribute, {@link SimpleNaturalIdLoadAccess} offers simplified access. * session.byNaturalId(Book.class)
* .using(Book_.isbn, isbn)
* .using(Book_.printing, printing)
* .load();
* </pre>
* *
* @author Eric Dalquist * @author Eric Dalquist
* @author Steve Ebersole * @author Steve Ebersole
* *
* @see Session#byNaturalId(Class)
* @see org.hibernate.annotations.NaturalId * @see org.hibernate.annotations.NaturalId
* @see Session#byNaturalId * @see SimpleNaturalIdLoadAccess
*/ */
public interface NaturalIdLoadAccess<T> { public interface NaturalIdLoadAccess<T> {
/** /**
* Specify the {@link LockOptions} to use when retrieving the entity. * Specify the {@linkplain LockOptions lock options} to use when
* querying the database.
* *
* @param lockOptions The lock options to use. * @param lockOptions The lock options to use.
* *
@ -32,9 +45,23 @@ public interface NaturalIdLoadAccess<T> {
NaturalIdLoadAccess<T> with(LockOptions lockOptions); NaturalIdLoadAccess<T> with(LockOptions lockOptions);
/** /**
* Add a NaturalId attribute value. * Add a {@link org.hibernate.annotations.NaturalId @NaturalId}
* attribute value in a typesafe way.
* *
* @param attributeName The entity attribute name that is marked as a NaturalId * @param attribute A typesafe reference to an attribute of the
* entity that is annotated {@code @NaturalId}
* @param value The value of the attribute
*
* @return {@code this}, for method chaining
*/
<X> NaturalIdLoadAccess<T> using(SingularAttribute<? super T, X> attribute, X value);
/**
* Add a {@link org.hibernate.annotations.NaturalId @NaturalId}
* attribute value.
*
* @param attributeName The name of an attribute of the entity
* that is annotated {@code @NaturalId}
* @param value The value of the attribute * @param value The value of the attribute
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
@ -42,24 +69,54 @@ public interface NaturalIdLoadAccess<T> {
NaturalIdLoadAccess<T> using(String attributeName, Object value); NaturalIdLoadAccess<T> using(String attributeName, Object value);
/** /**
* Set multiple natural-id attribute values at once. The passed array is * Set multiple {@link org.hibernate.annotations.NaturalId @NaturalId}
* expected to have an even number of elements, with the attribute name followed * attribute values at once. An even number of arguments is expected,
* by its value, for example, {@code using( "system", "matrix", "username", "neo" )}. * with each attribute name followed by its value, for example:
* <pre>
* Book book =
* session.byNaturalId(Book.class)
* .using(Map.of(Book_.ISBN, isbn, Book_.PRINTING, printing))
* .load();
* </pre>
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
NaturalIdLoadAccess<T> using(Map<String,?> mappings);
/**
* Set multiple {@link org.hibernate.annotations.NaturalId @NaturalId}
* attribute values at once. An even number of arguments is expected,
* with each attribute name followed by its value, for example:
* <pre>
* Book book =
* session.byNaturalId(Book.class)
* .using(Book_.ISBN, isbn, Book_.PRINTING, printing)
* .load();
* </pre>
*
* @return {@code this}, for method chaining
*
* @deprecated use {@link #using(Map)} with {@link Map#of}, which is
* slightly more typesafe
*/
@Deprecated(since = "6.3")
NaturalIdLoadAccess<T> using(Object... mappings); NaturalIdLoadAccess<T> using(Object... mappings);
/** /**
* For entities with mutable natural ids, should natural ids be synchronized prior to performing a lookup? * For entities with mutable natural ids, should natural ids be
* The default, for correctness, is to synchronize. * synchronized prior to executing a query? The default, for
* correctness, is to synchronize.
* <p> * <p>
* Here "synchronization" means updating the natural id to primary key cross-reference maintained by the * Here "synchronization" means updating the natural id to
* session. When enabled, prior to performing the lookup, Hibernate will check all entities of the given * primary key cross-reference maintained by the session. When
* type associated with the session to see if any natural id values have changed and, if so, update the * enabled, prior to performing the lookup, Hibernate will check
* cross-reference. There is a performance penalty associated with this, so if it is completely certain * all entities of the given type associated with the session to
* the no natural id in play has changed, this setting can be disabled to circumvent that impact. * see if any natural id values have changed and, if so, update
* Disabling this setting when natural id values <em>have</em> changed can result in incorrect results! * the cross-reference. There is a performance penalty associated
* with this, so if it is completely certain the no natural id in
* play has changed, this setting can be disabled to circumvent
* that impact. Disabling this setting when natural id values
* <em>have</em> changed can lead to incorrect results!
* *
* @param enabled Should synchronization be performed? * @param enabled Should synchronization be performed?
* {@code true} indicates synchronization will be performed; * {@code true} indicates synchronization will be performed;
@ -70,31 +127,36 @@ public interface NaturalIdLoadAccess<T> {
NaturalIdLoadAccess<T> setSynchronizationEnabled(boolean enabled); NaturalIdLoadAccess<T> setSynchronizationEnabled(boolean enabled);
/** /**
* Return the persistent instance with the natural id value(s) defined by the call(s) to {@link #using}. This * Return the persistent instance with the full natural id specified
* method might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed. * by previous calls to {@link #using}. This method might return a
* proxied instance that is initialized on-demand, when a non-identifier
* method is accessed.
* <p> * <p>
* You should not use this method to determine if an instance exists; to check for existence, use {@link #load} * You should not use this method to determine if an instance exists;
* instead. Use this only to retrieve an instance that you assume exists, where non-existence would be an * to check for existence, use {@link #load} instead. Use this method
* actual error. * only to retrieve an instance that you assume exists, where
* non-existence would be an actual error.
* *
* @return the persistent instance or proxy * @return the persistent instance or proxy
*/ */
T getReference(); T getReference();
/** /**
* Return the persistent instance with the natural id value(s) defined by the call(s) to {@link #using}, or * Return the persistent instance with the full natural id specified
* {@code null} if there is no such persistent instance. If the instance is already associated with the session, * by previous calls to {@link #using}, or {@code null} if there is no
* return that instance, initializing it if needed. This method never returns an uninitialized instance. * such persistent instance. If the instance is already associated with
* the session, return that instance, initializing it if needed. This
* method never returns an uninitialized instance.
* *
* @return The persistent instance or {@code null} * @return The persistent instance or {@code null}
*/ */
T load(); T load();
/** /**
* Same semantic as {@link #load} except that here {@link Optional} is returned to * Just like {@link #load}, except that here an {@link Optional} is
* handle nullability. * returned.
* *
* @return The persistent instance, if one, wrapped in Optional * @return The persistent instance, if one, as an {@link Optional}
*/ */
Optional<T> loadOptional(); Optional<T> loadOptional();

View File

@ -11,34 +11,60 @@ import java.util.Map;
import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.RootGraph; import org.hibernate.graph.RootGraph;
import org.hibernate.internal.util.collections.CollectionHelper;
import static org.hibernate.internal.util.collections.CollectionHelper.asMap;
/** /**
* Defines the ability to load multiple entities by simple natural-id simultaneously. * Loads multiple instances of a given entity type at once, by
* specifying a list of natural id values. This allows the entities
* to be fetched from the database in batches.
* <p>
* Composite natural ids may be accommodated by passing a list of
* maps of type {@code Map<String,Object>} to {@link #multiLoad}.
* Each map must contain the natural id attribute values keyed by
* {@link org.hibernate.annotations.NaturalId @NaturalId} attribute
* name.
* <pre>
* var compositeNaturalId =
* Map.of(Book_.ISBN, isbn, Book_.PRINTING, printing);
* </pre>
*
* @see Session#byMultipleNaturalId(Class)
*/ */
public interface NaturalIdMultiLoadAccess<T> { public interface NaturalIdMultiLoadAccess<T> {
/** /**
* Specify the {@link LockOptions} to use when retrieving the entity. * Specify the {@linkplain LockOptions lock options} to use when
* querying the database.
* *
* @param lockOptions The lock options to use. * @param lockOptions The lock options to use
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
NaturalIdMultiLoadAccess<T> with(LockOptions lockOptions); NaturalIdMultiLoadAccess<T> with(LockOptions lockOptions);
/** /**
* Specify the {@link CacheMode} to use when retrieving the entity. * Specify the {@link CacheMode} to use when obtaining an entity.
* *
* @param cacheMode The CacheMode to use. * @param cacheMode The {@code CacheMode} to use
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
NaturalIdMultiLoadAccess<T> with(CacheMode cacheMode); NaturalIdMultiLoadAccess<T> with(CacheMode cacheMode);
/**
* Override the associations fetched by default by specifying
* the complete list of associations to be fetched as an
* {@linkplain jakarta.persistence.EntityGraph entity graph}.
*/
default NaturalIdMultiLoadAccess<T> withFetchGraph(RootGraph<T> graph) { default NaturalIdMultiLoadAccess<T> withFetchGraph(RootGraph<T> graph) {
return with( graph, GraphSemantic.FETCH ); return with( graph, GraphSemantic.FETCH );
} }
/**
* Augment the associations fetched by default by specifying a
* list of additional associations to be fetched as an
* {@linkplain jakarta.persistence.EntityGraph entity graph}.
*/
default NaturalIdMultiLoadAccess<T> withLoadGraph(RootGraph<T> graph) { default NaturalIdMultiLoadAccess<T> withLoadGraph(RootGraph<T> graph) {
return with( graph, GraphSemantic.LOAD ); return with( graph, GraphSemantic.LOAD );
} }
@ -52,17 +78,27 @@ public interface NaturalIdMultiLoadAccess<T> {
} }
/** /**
* Define a load or fetch graph to be used when retrieving the entity * Customize the associations fetched by specifying an
* {@linkplain jakarta.persistence.EntityGraph entity graph},
* and how it should be {@linkplain GraphSemantic interpreted}.
*/ */
NaturalIdMultiLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic); NaturalIdMultiLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic);
/** /**
* Specify a batch size for loading the entities (how many at a time). The default is * Specify a batch size, that is, how many entities should be
* to use a batch sizing strategy defined by the Dialect in use. Any greater-than-one * fetched in each request to the database.
* value here will override that default behavior. If giving an explicit value here, * <ul>
* care should be taken to not exceed the capabilities of the underlying database. * <li>By default, the batch sizing strategy is determined by the
* {@linkplain org.hibernate.dialect.Dialect#getBatchLoadSizingStrategy
* SQL dialect}, but
* <li>if some {@code batchSize>1} is specified as an
* argument to this method, then that batch size will be used.
* </ul>
* <p> * <p>
* Note that overall a batch-size is considered a hint. * If an explicit batch size is set manually, care should be taken
* to not exceed the capabilities of the underlying database.
* <p>
* A batch size is considered a hint.
* *
* @param batchSize The batch size * @param batchSize The batch size
* *
@ -71,68 +107,75 @@ public interface NaturalIdMultiLoadAccess<T> {
NaturalIdMultiLoadAccess<T> withBatchSize(int batchSize); NaturalIdMultiLoadAccess<T> withBatchSize(int batchSize);
/** /**
* Should the multi-load operation be allowed to return entities that are locally * Should {@link #multiLoad} return entity instances that have been
* deleted? A locally deleted entity is one which has been passed to this * {@link Session#remove(Object) marked for removal} in the current
* Session's {@link Session#delete} / {@link Session#remove} method, but not * session, but not yet {@code delete}d in the database?
* yet flushed. The default behavior is to handle them as null in the return * <p>
* (see {@link #enableOrderedReturn}). * By default, instances marked for removal are replaced by null in
* the returned list of entities when {@link #enableOrderedReturn}
* is used.
* *
* @param enabled {@code true} enables returning the deleted entities; * @param enabled {@code true} if removed entities should be returned;
* {@code false} (the default) disables it. * {@code false} if they should be replaced by null values.
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
NaturalIdMultiLoadAccess<T> enableReturnOfDeletedEntities(boolean enabled); NaturalIdMultiLoadAccess<T> enableReturnOfDeletedEntities(boolean enabled);
/** /**
* Should the return List be ordered and positional in relation to the * Should the returned list of entity instances be ordered, with the
* incoming ids? If enabled (the default), the return List is ordered and * position of an entity instance determined by the position of its
* positional relative to the incoming ids. In other words, a request to * identifier in the list if ids passed to {@link #multiLoad}?
* {@code multiLoad([2,1,3])} will return {@code [Entity#2, Entity#1, Entity#3]}.
* <p> * <p>
* An important distinction is made here in regards to the handling of * By default, the returned list is ordered and the positions of the
* unknown entities depending on this "ordered return" setting. If enabled * entities correspond to the positions of their ids. In this case,
* a null is inserted into the List at the proper position(s). If disabled, * the {@linkplain #enableReturnOfDeletedEntities handling of entities
* the nulls are not put into the return List. In other words, consumers of * marked for removal} becomes important.
* the returned ordered List would need to be able to handle null elements.
* *
* @param enabled {@code true} (the default) enables ordering; * @param enabled {@code true} if entity instances should be ordered;
* {@code false} disables it. * {@code false} if they may be returned in any order.
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
NaturalIdMultiLoadAccess<T> enableOrderedReturn(boolean enabled); NaturalIdMultiLoadAccess<T> enableOrderedReturn(boolean enabled);
/** /**
* Perform a load of multiple entities by natural-id. * Retrieve the entities with the given natural id values.
* <p> * <p>
* See {@link #enableOrderedReturn} and {@link #enableReturnOfDeletedEntities} * Note that the options {@link #enableReturnOfDeletedEntities} and
* for options which effect the size and "shape" of the return list. * {@link #enableOrderedReturn} affect the size and shape of the
* returned list of entity instances.
* *
* @param ids The natural-id values to load * @param ids The natural id values to load
* *
* @return The managed entities. * @return The managed entities.
*/ */
List<T> multiLoad(Object... ids); List<T> multiLoad(Object... ids);
/** /**
* Perform a load of multiple entities by natural-id. * Retrieve the entities with the given natural id values.
* <p> * <p>
* See {@link #enableOrderedReturn} and {@link #enableReturnOfDeletedEntities} * Note that the options {@link #enableReturnOfDeletedEntities} and
* for options which effect the size and "shape" of the return list. * {@link #enableOrderedReturn} affect the size and shape of the
* returned list of entity instances.
* *
* @param ids The natural-id values to load * @param ids The natural id values to load
* *
* @return The managed entities. * @return The managed entities.
*/ */
List<T> multiLoad(List<?> ids); List<T> multiLoad(List<?> ids);
/** /**
* Helper for creating a Map that represents the value of a compound natural-id * Helper for creating a {@link Map} that represents the value of a
* for use in loading. The passed array is expected to have an even number of elements * composite natural id. An even number of arguments is expected,
* representing key, value pairs. E.g. `using( "system", "matrix", "username", "neo" )` * with each attribute name followed by its value.
*
* @see NaturalIdLoadAccess#using(Object...)
*
* @deprecated use {@link Map#of} instead
*/ */
@Deprecated(since = "6.3")
static Map<String,?> compoundValue(Object... elements) { static Map<String,?> compoundValue(Object... elements) {
return CollectionHelper.asMap( elements ); return asMap( elements );
} }
} }

View File

@ -9,69 +9,88 @@ package org.hibernate;
import java.util.Optional; import java.util.Optional;
/** /**
* Loads an entity by its natural identifier. * Loads an entity by its natural identifier. This simplified API is
* used when the entity has exactly one field or property annotated
* {@link org.hibernate.annotations.NaturalId @NaturalId}. If an
* entity has multiple attributes annotated {@code @NaturalId}, then
* {@link NaturalIdLoadAccess} should be used instead.
* <p>
* <pre>
* Book book = session.bySimpleNaturalId(Book.class).load(isbn);
* </pre>
* *
* @author Eric Dalquist * @author Eric Dalquist
* @author Steve Ebersole * @author Steve Ebersole
* *
* @see Session#bySimpleNaturalId(Class)
* @see org.hibernate.annotations.NaturalId * @see org.hibernate.annotations.NaturalId
* @see NaturalIdLoadAccess * @see NaturalIdLoadAccess
*/ */
public interface SimpleNaturalIdLoadAccess<T> { public interface SimpleNaturalIdLoadAccess<T> {
/** /**
* Specify the {@link LockOptions} to use when retrieving the entity. * Specify the {@linkplain LockOptions lock options} to use when
* querying the database.
* *
* @param lockOptions The lock options to use. * @param lockOptions The lock options to use
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
SimpleNaturalIdLoadAccess<T> with(LockOptions lockOptions); SimpleNaturalIdLoadAccess<T> with(LockOptions lockOptions);
/** /**
* For entities with mutable natural ids, should Hibernate perform "synchronization" prior to performing * For entities with mutable natural ids, should Hibernate perform
* lookups? The default is to perform "synchronization" (for correctness). * "synchronization" prior to performing lookups? The default is
* to perform "synchronization" (for correctness).
* <p> * <p>
* See {@link NaturalIdLoadAccess#setSynchronizationEnabled} for detailed discussion. * See {@link NaturalIdLoadAccess#setSynchronizationEnabled} for
* detailed discussion.
* *
* @param enabled Should synchronization be performed? {@code true} indicates synchronization will be performed; * @param enabled Should synchronization be performed?
* {@code false} indicates it will be circumvented. * {@code true} indicates synchronization will be performed;
* {@code false} indicates it will be circumvented.
* *
* @return {@code this}, for method chaining * @return {@code this}, for method chaining
*/ */
SimpleNaturalIdLoadAccess<T> setSynchronizationEnabled(boolean enabled); SimpleNaturalIdLoadAccess<T> setSynchronizationEnabled(boolean enabled);
/** /**
* Return the persistent instance with the given natural id value, assuming that the instance exists. This method * Return the persistent instance with the given natural id value,
* might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed. * assuming that the instance exists. This method might return a
* proxied instance that is initialized on-demand, when a
* non-identifier method is accessed.
* <p>
* You should not use this method to determine if an instance exists;
* to check for existence, use {@link #load} instead. Use this only to
* retrieve an instance that you assume exists, where non-existence
* would be an actual error.
* *
* You should not use this method to determine if an instance exists; to check for existence, use {@link #load} * @param naturalIdValue The value of the natural id
* instead. Use this only to retrieve an instance that you assume exists, where non-existence would be an
* actual error.
* *
* @param naturalIdValue The value of the natural-id for the entity to retrieve * @return The persistent instance or proxy, if an instance exists.
* * Otherwise, {@code null}.
* @return The persistent instance or proxy, if an instance exists. Otherwise, {@code null}.
*/ */
T getReference(Object naturalIdValue); T getReference(Object naturalIdValue);
/** /**
* Return the persistent instance with the given natural id value, or {@code null} if there is no such persistent * Return the persistent instance with the given natural id value,
* instance. If the instance is already associated with the session, return that instance, initializing it if * or {@code null} if there is no such persistent instance. If the
* needed. This method never returns an uninitialized instance. * instance is already associated with the session, return that
* instance, initializing it if needed. This method never returns
* an uninitialized instance.
* *
* @param naturalIdValue The value of the natural-id for the entity to retrieve * @param naturalIdValue The value of the natural-id
* *
* @return The persistent instance or {@code null} * @return The persistent instance or {@code null}
*/ */
T load(Object naturalIdValue); T load(Object naturalIdValue);
/** /**
* Same semantic as {@link #load} except that here {@link Optional} is returned to * Just like {@link #load}, except that here an {@link Optional}
* handle nullability. * is returned.
* *
* @param naturalIdValue The identifier * @param naturalIdValue The identifier
* *
* @return The persistent instance, if one, wrapped in Optional * @return The persistent instance, if any, as an {@link Optional}
*/ */
Optional<T> loadOptional(Object naturalIdValue); Optional<T> loadOptional(Object naturalIdValue);
} }

View File

@ -10,9 +10,9 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import jakarta.persistence.metamodel.SingularAttribute;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.NaturalIdLoadAccess; import org.hibernate.NaturalIdLoadAccess;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
@ -31,6 +31,12 @@ public class NaturalIdLoadAccessImpl<T> extends BaseNaturalIdLoadAccessImpl<T> i
return (NaturalIdLoadAccessImpl<T>) super.with( lockOptions ); return (NaturalIdLoadAccessImpl<T>) super.with( lockOptions );
} }
@Override
public <X> NaturalIdLoadAccess<T> using(SingularAttribute<? super T, X> attribute, X value) {
naturalIdParameters.put( attribute.getName(), value );
return this;
}
@Override @Override
public NaturalIdLoadAccess<T> using(String attributeName, Object value) { public NaturalIdLoadAccess<T> using(String attributeName, Object value) {
naturalIdParameters.put( attributeName, value ); naturalIdParameters.put( attributeName, value );
@ -38,6 +44,12 @@ public class NaturalIdLoadAccessImpl<T> extends BaseNaturalIdLoadAccessImpl<T> i
} }
@Override @Override
public NaturalIdLoadAccess<T> using(Map<String, ?> mappings) {
naturalIdParameters.putAll( mappings );
return this;
}
@Override @Deprecated
public NaturalIdLoadAccess<T> using(Object... mappings) { public NaturalIdLoadAccess<T> using(Object... mappings) {
CollectionHelper.collectMapEntries( naturalIdParameters::put, mappings ); CollectionHelper.collectMapEntries( naturalIdParameters::put, mappings );
return this; return this;

View File

@ -7,9 +7,8 @@
package org.hibernate.orm.test.mapping.identifier; package org.hibernate.orm.test.mapping.identifier;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType; import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
@ -67,6 +66,16 @@ public class MultipleNaturalIdTest extends BaseEntityManagerFunctionalTestCase {
.load(); .load();
//end::naturalid-load-access-example[] //end::naturalid-load-access-example[]
assertEquals("High-Performance Java Persistence", book.getTitle());
});
doInJPA(this::entityManagerFactory, entityManager -> {
Publisher publisher = entityManager.getReference(Publisher.class, 1L);
Book book = entityManager
.unwrap(Session.class)
.byNaturalId(Book.class)
.using(Map.of("productNumber", "973022823X", "publisher", publisher))
.load();
assertEquals("High-Performance Java Persistence", book.getTitle()); assertEquals("High-Performance Java Persistence", book.getTitle());
}); });
} }

View File

@ -190,8 +190,8 @@ public class CompoundNaturalIdTests {
final NaturalIdMultiLoadAccess<Account> loadAccess = session.byMultipleNaturalId( Account.class ); final NaturalIdMultiLoadAccess<Account> loadAccess = session.byMultipleNaturalId( Account.class );
loadAccess.enableOrderedReturn( false ); loadAccess.enableOrderedReturn( false );
final List<Account> accounts = loadAccess.multiLoad( final List<Account> accounts = loadAccess.multiLoad(
NaturalIdMultiLoadAccess.compoundValue( "system", "matrix", "username", "neo" ), Map.of( "system", "matrix", "username", "neo" ),
NaturalIdMultiLoadAccess.compoundValue( "system", "matrix", "username", "trinity" ) Map.of( "system", "matrix", "username", "trinity" )
); );
assertThat( accounts.size(), is( 2 ) ); assertThat( accounts.size(), is( 2 ) );