diff --git a/hibernate-core/src/main/java/org/hibernate/IdentifierLoadAccess.java b/hibernate-core/src/main/java/org/hibernate/IdentifierLoadAccess.java index 79ea743ca7..09ed30ba00 100644 --- a/hibernate-core/src/main/java/org/hibernate/IdentifierLoadAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/IdentifierLoadAccess.java @@ -13,24 +13,36 @@ import org.hibernate.graph.RootGraph; /** * Loads an entity by its primary identifier. - * + *

+ * The interface is especially useful when customizing association + * fetching using an {@link jakarta.persistence.EntityGraph}. + *

+ * 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);
+ * 
+ * * @author Eric Dalquist * @author Steve Ebersole + * + * @see Session#byId(Class) */ public interface IdentifierLoadAccess { /** - * 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 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 { */ IdentifierLoadAccess 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 withFetchGraph(RootGraph 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 withLoadGraph(RootGraph graph) { return with( graph, GraphSemantic.LOAD ); } @@ -59,15 +81,22 @@ public interface IdentifierLoadAccess { 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 with(RootGraph 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. *

- * 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 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 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 loadOptional(Object id); } diff --git a/hibernate-core/src/main/java/org/hibernate/MultiIdentifierLoadAccess.java b/hibernate-core/src/main/java/org/hibernate/MultiIdentifierLoadAccess.java index ae80d7116c..3e1456e11a 100644 --- a/hibernate-core/src/main/java/org/hibernate/MultiIdentifierLoadAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/MultiIdentifierLoadAccess.java @@ -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. + *

+ * var graph = session.createEntityGraph(Book.class);
+ * graph.addSubgraph(Book_.publisher);
+ * session.byId(Book.class).withFetchGraph(graph).multiLoad(bookIds);
+ * 
+ * + * @see Session#byMultipleIds(Class) * * @author Steve Ebersole */ public interface MultiIdentifierLoadAccess { /** - * 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 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 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 withFetchGraph(RootGraph 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 withLoadGraph(RootGraph graph) { return with( graph, GraphSemantic.LOAD ); } @@ -52,16 +71,28 @@ public interface MultiIdentifierLoadAccess { 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 with(RootGraph 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. + *
    + *
  • By default, the batch sizing strategy is determined by the + * {@linkplain org.hibernate.dialect.Dialect#getBatchLoadSizingStrategy + * SQL dialect}, but + *
  • if some {@code batchSize>1} is specified as an + * argument to this method, then that batch size will be used. + *
*

- * 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. + *

+ * A batch size is considered a hint. * * @param batchSize The batch size * @@ -70,53 +101,58 @@ public interface MultiIdentifierLoadAccess { MultiIdentifierLoadAccess 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 for the purpose of not including those - * ids to the batch-load SQL. + * 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. + *

+ * 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 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? + *

+ * 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 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}? *

- * 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 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. + *

+ * Note that the options {@link #enableReturnOfDeletedEntities} and + * {@link #enableOrderedReturn} affect the size and shape of the + * returned list of entity instances. * * @param The identifier type * @@ -126,9 +162,11 @@ public interface MultiIdentifierLoadAccess { List 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. + *

+ * 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 The identifier type diff --git a/hibernate-core/src/main/java/org/hibernate/NaturalIdLoadAccess.java b/hibernate-core/src/main/java/org/hibernate/NaturalIdLoadAccess.java index 37d798733c..5024ff7a80 100644 --- a/hibernate-core/src/main/java/org/hibernate/NaturalIdLoadAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/NaturalIdLoadAccess.java @@ -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. *

- * 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. + *

+ * Book book =
+ *         session.byNaturalId(Book.class)
+ *             .using(Book_.isbn, isbn)
+ *             .using(Book_.printing, printing)
+ *             .load();
+ * 
* * @author Eric Dalquist * @author Steve Ebersole * + * @see Session#byNaturalId(Class) * @see org.hibernate.annotations.NaturalId - * @see Session#byNaturalId + * @see SimpleNaturalIdLoadAccess */ public interface NaturalIdLoadAccess { /** - * 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 { NaturalIdLoadAccess with(LockOptions lockOptions); /** - * Add a NaturalId attribute value. + * Add a {@link org.hibernate.annotations.NaturalId @NaturalId} + * attribute value in a typesafe way. + * + * @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 + */ + NaturalIdLoadAccess using(SingularAttribute attribute, X value); + + /** + * Add a {@link org.hibernate.annotations.NaturalId @NaturalId} + * attribute value. * - * @param attributeName The entity attribute name that is marked as a NaturalId + * @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 { NaturalIdLoadAccess 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: + *
+	 * Book book =
+	 *         session.byNaturalId(Book.class)
+	 *             .using(Map.of(Book_.ISBN, isbn, Book_.PRINTING, printing))
+	 *             .load();
+	 * 
* * @return {@code this}, for method chaining */ + NaturalIdLoadAccess using(Map 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: + *
+	 * Book book =
+	 *         session.byNaturalId(Book.class)
+	 *             .using(Book_.ISBN, isbn, Book_.PRINTING, printing)
+	 *             .load();
+	 * 
+ * + * @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 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. *

- * 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 have 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 + * have 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 { NaturalIdLoadAccess 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. *

- * 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 loadOptional(); diff --git a/hibernate-core/src/main/java/org/hibernate/NaturalIdMultiLoadAccess.java b/hibernate-core/src/main/java/org/hibernate/NaturalIdMultiLoadAccess.java index 426d0490b1..f05392b9bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/NaturalIdMultiLoadAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/NaturalIdMultiLoadAccess.java @@ -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. + *

+ * Composite natural ids may be accommodated by passing a list of + * maps of type {@code Map} to {@link #multiLoad}. + * Each map must contain the natural id attribute values keyed by + * {@link org.hibernate.annotations.NaturalId @NaturalId} attribute + * name. + *

+ * var compositeNaturalId =
+ *         Map.of(Book_.ISBN, isbn, Book_.PRINTING, printing);
+ * 
+ * + * @see Session#byMultipleNaturalId(Class) */ public interface NaturalIdMultiLoadAccess { /** - * 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 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 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 withFetchGraph(RootGraph 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 withLoadGraph(RootGraph graph) { return with( graph, GraphSemantic.LOAD ); } @@ -52,17 +78,27 @@ public interface NaturalIdMultiLoadAccess { } /** - * 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 with(RootGraph 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. + *
    + *
  • By default, the batch sizing strategy is determined by the + * {@linkplain org.hibernate.dialect.Dialect#getBatchLoadSizingStrategy + * SQL dialect}, but + *
  • if some {@code batchSize>1} is specified as an + * argument to this method, then that batch size will be used. + *
*

- * 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. + *

+ * A batch size is considered a hint. * * @param batchSize The batch size * @@ -71,68 +107,75 @@ public interface NaturalIdMultiLoadAccess { NaturalIdMultiLoadAccess 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? + *

+ * 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 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}? *

- * 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 enableOrderedReturn(boolean enabled); /** - * Perform a load of multiple entities by natural-id. + * Retrieve the entities with the given natural id values. *

- * 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 multiLoad(Object... ids); /** - * Perform a load of multiple entities by natural-id. + * Retrieve the entities with the given natural id values. *

- * 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 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 compoundValue(Object... elements) { - return CollectionHelper.asMap( elements ); + return asMap( elements ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/SimpleNaturalIdLoadAccess.java b/hibernate-core/src/main/java/org/hibernate/SimpleNaturalIdLoadAccess.java index 9aaa55f414..44c4b71e47 100644 --- a/hibernate-core/src/main/java/org/hibernate/SimpleNaturalIdLoadAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/SimpleNaturalIdLoadAccess.java @@ -9,69 +9,88 @@ 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. + *

+ *

+ * Book book = session.bySimpleNaturalId(Book.class).load(isbn);
+ * 
* * @author Eric Dalquist * @author Steve Ebersole * + * @see Session#bySimpleNaturalId(Class) * @see org.hibernate.annotations.NaturalId * @see NaturalIdLoadAccess */ public interface SimpleNaturalIdLoadAccess { /** - * 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 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). *

- * 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; - * {@code false} indicates it will be circumvented. + * @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 */ SimpleNaturalIdLoadAccess 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. + *

+ * 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 loadOptional(Object naturalIdValue); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/NaturalIdLoadAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/NaturalIdLoadAccessImpl.java index d1faf3829e..32eec4acd7 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/NaturalIdLoadAccessImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/NaturalIdLoadAccessImpl.java @@ -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 extends BaseNaturalIdLoadAccessImpl i return (NaturalIdLoadAccessImpl) super.with( lockOptions ); } + @Override + public NaturalIdLoadAccess using(SingularAttribute attribute, X value) { + naturalIdParameters.put( attribute.getName(), value ); + return this; + } + @Override public NaturalIdLoadAccess using(String attributeName, Object value) { naturalIdParameters.put( attributeName, value ); @@ -38,6 +44,12 @@ public class NaturalIdLoadAccessImpl extends BaseNaturalIdLoadAccessImpl i } @Override + public NaturalIdLoadAccess using(Map mappings) { + naturalIdParameters.putAll( mappings ); + return this; + } + + @Override @Deprecated public NaturalIdLoadAccess using(Object... mappings) { CollectionHelper.collectMapEntries( naturalIdParameters::put, mappings ); return this; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/MultipleNaturalIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/MultipleNaturalIdTest.java index 03d1471294..d2aa07dd52 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/MultipleNaturalIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/MultipleNaturalIdTest.java @@ -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()); }); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/CompoundNaturalIdTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/CompoundNaturalIdTests.java index 18075ed749..1c403c4391 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/CompoundNaturalIdTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/CompoundNaturalIdTests.java @@ -190,8 +190,8 @@ public class CompoundNaturalIdTests { final NaturalIdMultiLoadAccess loadAccess = session.byMultipleNaturalId( Account.class ); loadAccess.enableOrderedReturn( false ); final List 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 ) );