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.
* <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 Steve Ebersole
*
* @see Session#byId(Class)
*/
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
*/
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
*/
@ -43,10 +55,20 @@ public interface IdentifierLoadAccess<T> {
*/
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) {
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) {
return with( graph, GraphSemantic.LOAD );
}
@ -59,15 +81,22 @@ public interface IdentifierLoadAccess<T> {
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);
/**
* Return the persistent instance with the given identifier, assuming that the instance exists. This method
* might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed.
* Return the persistent instance with the given identifier, 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} instead. Use this only to
* 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
*
@ -76,9 +105,10 @@ public interface IdentifierLoadAccess<T> {
T getReference(Object id);
/**
* Return the persistent instance with the given identifier, or null if there is no 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 with the given identifier, or null
* if there is no 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.
*
* @param id The identifier
*
@ -87,12 +117,12 @@ public interface IdentifierLoadAccess<T> {
T load(Object id);
/**
* Same semantic as {@link #load} except that here {@link Optional} is returned to
* handle nullability.
* Just like {@link #load}, except that here an {@link Optional} is
* returned.
*
* @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);
}

View File

@ -12,34 +12,53 @@ import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.RootGraph;
/**
* Loads multiple entities at once by identifiers, ultimately via one of the
* {@link #multiLoad} methods, using the various options specified (if any)
* Loads multiple instances of a given entity type at once, by
* 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
*/
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
*/
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
*/
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) {
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) {
return with( graph, GraphSemantic.LOAD );
}
@ -52,16 +71,28 @@ public interface MultiIdentifierLoadAccess<T> {
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);
/**
* Specify a batch size for loading the entities (how many at a time). The default is
* to use a batch sizing strategy defined by the Dialect in use. Any greater-than-one
* value here will override that default behavior. If giving an explicit value here,
* care should be taken to not exceed the capabilities of the underlying database.
* Specify a batch size, that is, how many entities should be
* fetched in each request to the database.
* <ul>
* <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>
* Note that overall a batch-size is considered a hint. How the underlying loading
* mechanism interprets that is completely up to that underlying loading mechanism.
* 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
*
@ -70,53 +101,58 @@ public interface MultiIdentifierLoadAccess<T> {
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
* entities to be loaded in a managed state <b>for the purpose of not including those
* ids to the batch-load SQL</b>.
* Specifies whether the ids of managed entity instances already
* cached in the current persistence context should be excluded
* 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
*/
MultiIdentifierLoadAccess<T> enableSessionCheck(boolean enabled);
/**
* Should the multi-load operation be allowed to return entities that are locally
* deleted? A locally deleted entity is one which has been passed to this
* Session's {@link Session#delete} / {@link Session#remove} method, but not
* yet flushed. The default behavior is to handle them as null in the return
* (see {@link #enableOrderedReturn}).
* Should {@link #multiLoad} return entity instances that have been
* {@link Session#remove(Object) marked for removal} in the current
* session, but not yet {@code delete}d in the database?
* <p>
* 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;
* {@code false} (the default) disables it.
* @param enabled {@code true} if removed entities should be returned;
* {@code false} if they should be replaced by null values.
*
* @return {@code this}, for method chaining
*/
MultiIdentifierLoadAccess<T> enableReturnOfDeletedEntities(boolean enabled);
/**
* Should the return List be ordered and positional in relation to the
* incoming ids? If enabled (the default), the return List is ordered and
* positional relative to the incoming ids. In other words, a request to
* {@code multiLoad([2,1,3])} will return {@code [Entity#2, Entity#1, Entity#3]}.
* Should the returned list of entity instances be ordered, with the
* position of an entity instance determined by the position of its
* identifier in the list if ids passed to {@link #multiLoad}?
* <p>
* An important distinction is made here in regards to the handling of
* unknown entities depending on this "ordered return" setting. If enabled
* a null is inserted into the List at the proper position(s). If disabled,
* the nulls are not put into the return List. In other words, consumers of
* the returned ordered List would need to be able to handle null elements.
* By default, the returned list is ordered and the positions of the
* entities correspond to the positions of their ids. In this case,
* the {@linkplain #enableReturnOfDeletedEntities handling of entities
* marked for removal} becomes important.
*
* @param enabled {@code true} (the default) enables ordering;
* {@code false} disables it.
* @param enabled {@code true} if entity instances should be ordered;
* {@code false} if they may be returned in any order.
*
* @return {@code this}, for method chaining
*/
MultiIdentifierLoadAccess<T> enableOrderedReturn(boolean enabled);
/**
* Perform a load of multiple entities by identifiers. See {@link #enableOrderedReturn}
* and {@link #enableReturnOfDeletedEntities} for options which effect
* the size and "shape" of the return list.
* Retrieve the entities with the given identifiers.
* <p>
* 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
*
@ -126,9 +162,11 @@ public interface MultiIdentifierLoadAccess<T> {
<K> List<T> multiLoad(K... ids);
/**
* Perform a load of multiple entities by identifiers. See {@link #enableOrderedReturn}
* and {@link #enableReturnOfDeletedEntities} for options which effect
* the size and "shape" of the return list.
* Retrieve the entities with the given identifiers.
* <p>
* 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 <K> The identifier type

View File

@ -6,24 +6,37 @@
*/
package org.hibernate;
import jakarta.persistence.metamodel.SingularAttribute;
import java.util.Map;
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>
* This is a generic form of load-by-natural-id covering both a single attribute
* and multiple attributes as the natural-id. For natural-ids defined by a single
* attribute, {@link SimpleNaturalIdLoadAccess} offers simplified access.
* <pre>
* Book book =
* session.byNaturalId(Book.class)
* .using(Book_.isbn, isbn)
* .using(Book_.printing, printing)
* .load();
* </pre>
*
* @author Eric Dalquist
* @author Steve Ebersole
*
* @see Session#byNaturalId(Class)
* @see org.hibernate.annotations.NaturalId
* @see Session#byNaturalId
* @see SimpleNaturalIdLoadAccess
*/
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.
*
@ -32,9 +45,23 @@ public interface NaturalIdLoadAccess<T> {
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
*
* @return {@code this}, for method chaining
@ -42,24 +69,54 @@ public interface NaturalIdLoadAccess<T> {
NaturalIdLoadAccess<T> using(String attributeName, Object value);
/**
* Set multiple natural-id attribute values at once. The passed array is
* expected to have an even number of elements, with the attribute name followed
* by its value, for example, {@code using( "system", "matrix", "username", "neo" )}.
* 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(Map.of(Book_.ISBN, isbn, Book_.PRINTING, printing))
* .load();
* </pre>
*
* @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);
/**
* For entities with mutable natural ids, should natural ids be synchronized prior to performing a lookup?
* The default, for correctness, is to synchronize.
* For entities with mutable natural ids, should natural ids be
* synchronized prior to executing a query? The default, for
* correctness, is to synchronize.
* <p>
* Here "synchronization" means updating the natural id to primary key cross-reference maintained by the
* session. When enabled, prior to performing the lookup, Hibernate will check all entities of the given
* type associated with the session to see if any natural id values have changed and, if so, update 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 result in incorrect results!
* Here "synchronization" means updating the natural id to
* primary key cross-reference maintained by the session. When
* enabled, prior to performing the lookup, Hibernate will check
* all entities of the given type associated with the session to
* see if any natural id values have changed and, if so, update
* 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?
* {@code true} indicates synchronization will be performed;
@ -70,31 +127,36 @@ public interface NaturalIdLoadAccess<T> {
NaturalIdLoadAccess<T> setSynchronizationEnabled(boolean enabled);
/**
* Return the persistent instance with the natural id value(s) defined by the call(s) to {@link #using}. This
* method might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed.
* Return the persistent instance with the full natural id specified
* 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>
* 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} instead. Use this method
* only to retrieve an instance that you assume exists, where
* non-existence would be an actual error.
*
* @return the persistent instance or proxy
*/
T getReference();
/**
* Return the persistent instance with the natural id value(s) defined by the call(s) to {@link #using}, or
* {@code null} if there is no 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 with the full natural id specified
* by previous calls to {@link #using}, or {@code null} if there is no
* 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}
*/
T load();
/**
* Same semantic as {@link #load} except that here {@link Optional} is returned to
* handle nullability.
* Just like {@link #load}, except that here an {@link Optional} is
* returned.
*
* @return The persistent instance, if one, wrapped in Optional
* @return The persistent instance, if one, as an {@link Optional}
*/
Optional<T> loadOptional();

View File

@ -11,34 +11,60 @@ import java.util.Map;
import org.hibernate.graph.GraphSemantic;
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> {
/**
* 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
*/
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
*/
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) {
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) {
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);
/**
* Specify a batch size for loading the entities (how many at a time). The default is
* to use a batch sizing strategy defined by the Dialect in use. Any greater-than-one
* value here will override that default behavior. If giving an explicit value here,
* care should be taken to not exceed the capabilities of the underlying database.
* Specify a batch size, that is, how many entities should be
* fetched in each request to the database.
* <ul>
* <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>
* 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
*
@ -71,68 +107,75 @@ public interface NaturalIdMultiLoadAccess<T> {
NaturalIdMultiLoadAccess<T> withBatchSize(int batchSize);
/**
* Should the multi-load operation be allowed to return entities that are locally
* deleted? A locally deleted entity is one which has been passed to this
* Session's {@link Session#delete} / {@link Session#remove} method, but not
* yet flushed. The default behavior is to handle them as null in the return
* (see {@link #enableOrderedReturn}).
* Should {@link #multiLoad} return entity instances that have been
* {@link Session#remove(Object) marked for removal} in the current
* session, but not yet {@code delete}d in the database?
* <p>
* 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;
* {@code false} (the default) disables it.
* @param enabled {@code true} if removed entities should be returned;
* {@code false} if they should be replaced by null values.
*
* @return {@code this}, for method chaining
*/
NaturalIdMultiLoadAccess<T> enableReturnOfDeletedEntities(boolean enabled);
/**
* Should the return List be ordered and positional in relation to the
* incoming ids? If enabled (the default), the return List is ordered and
* positional relative to the incoming ids. In other words, a request to
* {@code multiLoad([2,1,3])} will return {@code [Entity#2, Entity#1, Entity#3]}.
* Should the returned list of entity instances be ordered, with the
* position of an entity instance determined by the position of its
* identifier in the list if ids passed to {@link #multiLoad}?
* <p>
* An important distinction is made here in regards to the handling of
* unknown entities depending on this "ordered return" setting. If enabled
* a null is inserted into the List at the proper position(s). If disabled,
* the nulls are not put into the return List. In other words, consumers of
* the returned ordered List would need to be able to handle null elements.
* By default, the returned list is ordered and the positions of the
* entities correspond to the positions of their ids. In this case,
* the {@linkplain #enableReturnOfDeletedEntities handling of entities
* marked for removal} becomes important.
*
* @param enabled {@code true} (the default) enables ordering;
* {@code false} disables it.
* @param enabled {@code true} if entity instances should be ordered;
* {@code false} if they may be returned in any order.
*
* @return {@code this}, for method chaining
*/
NaturalIdMultiLoadAccess<T> enableOrderedReturn(boolean enabled);
/**
* Perform a load of multiple entities by natural-id.
* Retrieve the entities with the given natural id values.
* <p>
* See {@link #enableOrderedReturn} and {@link #enableReturnOfDeletedEntities}
* for options which effect 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 natural-id values to load
* @param ids The natural id values to load
*
* @return The managed entities.
*/
List<T> multiLoad(Object... ids);
/**
* Perform a load of multiple entities by natural-id.
* Retrieve the entities with the given natural id values.
* <p>
* See {@link #enableOrderedReturn} and {@link #enableReturnOfDeletedEntities}
* for options which effect 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 natural-id values to load
* @param ids The natural id values to load
*
* @return The managed entities.
*/
List<T> multiLoad(List<?> ids);
/**
* Helper for creating a Map that represents the value of a compound natural-id
* for use in loading. The passed array is expected to have an even number of elements
* representing key, value pairs. E.g. `using( "system", "matrix", "username", "neo" )`
* Helper for creating a {@link Map} that represents the value of a
* composite natural id. An even number of arguments is expected,
* 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) {
return CollectionHelper.asMap( elements );
return asMap( elements );
}
}

View File

@ -9,31 +9,44 @@ package org.hibernate;
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 Steve Ebersole
*
* @see Session#bySimpleNaturalId(Class)
* @see org.hibernate.annotations.NaturalId
* @see NaturalIdLoadAccess
*/
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
*/
SimpleNaturalIdLoadAccess<T> with(LockOptions lockOptions);
/**
* For entities with mutable natural ids, should Hibernate perform "synchronization" prior to performing
* lookups? The default is to perform "synchronization" (for correctness).
* For entities with mutable natural ids, should Hibernate perform
* "synchronization" prior to performing lookups? The default is
* to perform "synchronization" (for correctness).
* <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 true} indicates synchronization will be performed;
* {@code false} indicates it will be circumvented.
*
* @return {@code this}, for method chaining
@ -41,37 +54,43 @@ public interface SimpleNaturalIdLoadAccess<T> {
SimpleNaturalIdLoadAccess<T> setSynchronizationEnabled(boolean enabled);
/**
* Return the persistent instance with the given natural id value, assuming that the instance exists. This method
* might return a proxied instance that is initialized on-demand, when a non-identifier method is accessed.
* Return the persistent instance with the given natural id value,
* 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}
* 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
*
* @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);
/**
* Return the persistent instance with the given natural id value, or {@code null} if there is no 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 with the given natural id value,
* or {@code null} if there is no 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.
*
* @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}
*/
T load(Object naturalIdValue);
/**
* Same semantic as {@link #load} except that here {@link Optional} is returned to
* handle nullability.
* Just like {@link #load}, except that here an {@link Optional}
* is returned.
*
* @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);
}

View File

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

View File

@ -7,9 +7,8 @@
package org.hibernate.orm.test.mapping.identifier;
import java.io.Serializable;
import java.util.Map;
import java.util.Objects;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
@ -67,6 +66,16 @@ public class MultipleNaturalIdTest extends BaseEntityManagerFunctionalTestCase {
.load();
//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());
});
}

View File

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