Skip to content

Commit 5c0b942

Browse files
committed
HHH-19829 - Deprecate MultiIdentifierLoadAccess and the Session.byMultipleIds() methods
1 parent 0530f02 commit 5c0b942

File tree

8 files changed

+163
-127
lines changed

8 files changed

+163
-127
lines changed

documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc

Lines changed: 14 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -187,102 +187,31 @@ If you want to load multiple entities by providing their identifiers, calling th
187187
but also inefficient.
188188

189189
While the Jakarta Persistence standard does not support retrieving multiple entities at once, other than running a JPQL or Criteria API query,
190-
Hibernate offers this functionality via the
191-
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#byMultipleIds-java.lang.Class-[`byMultipleIds` method] of the Hibernate `Session`.
192-
193-
The `byMultipleIds` method returns a
194-
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/MultiIdentifierLoadAccess.html[`MultiIdentifierLoadAccess`]
195-
which you can use to customize the multi-load request.
196-
197-
The `MultiIdentifierLoadAccess` interface provides several methods which you can use to
198-
change the behavior of the multi-load call:
199-
200-
`enableOrderedReturn(boolean enabled)`::
201-
This setting controls whether the returned `List` is ordered and positional in relation to the
202-
incoming ids. If enabled (the default), the return `List` is ordered and
203-
positional relative to the incoming ids. In other words, a request to
204-
`multiLoad([2,1,3])` will return `[Entity#2, Entity#1, Entity#3]`.
205-
+
206-
An important distinction is made here in regards to the handling of
207-
unknown entities depending on this "ordered return" setting.
208-
If enabled, a null is inserted into the `List` at the proper position(s).
209-
If disabled, the nulls are not put into the return List.
210-
+
211-
In other words, consumers of the returned ordered List would need to be able to handle null elements.
212-
`enableSessionCheck(boolean enabled)`::
213-
This setting, which is disabled by default, tells Hibernate to check the first-level cache (a.k.a `Session` or Persistence Context) first and, if the entity is found and already managed by the Hibernate `Session`, the cached entity will be added to the returned `List`, therefore skipping it from being fetched via the multi-load query.
214-
`enableReturnOfDeletedEntities(boolean enabled)`::
215-
This setting instructs Hibernate if the multi-load operation is allowed to return entities that were deleted by the current Persistence Context. A deleted entity is one which has been passed to this
216-
`Session.delete` or `Session.remove` method, but the `Session` was not flushed yet, meaning that the
217-
associated row was not deleted in the database table.
218-
+
219-
The default behavior is to handle them as null in the return (see `enableOrderedReturn`).
220-
When enabled, the result set will contain deleted entities.
221-
When disabled (which is the default behavior), deleted entities are not included in the returning `List`.
222-
`with(LockOptions lockOptions)`::
223-
This setting allows you to pass a given
224-
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/LockOptions.html[`LockOptions`] mode to the multi-load query.
225-
`with(CacheMode cacheMode)`::
226-
This setting allows you to pass a given
227-
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/CacheMode.html[`CacheMode`]
228-
strategy so that we can load entities from the second-level cache, therefore skipping the cached entities from being fetched via the multi-load query.
229-
`withBatchSize(int batchSize)`::
230-
This setting allows you to specify a batch size for loading the entities (e.g. how many at a time).
231-
+
232-
The default is to use a batch sizing strategy defined by the `Dialect.getDefaultBatchLoadSizingStrategy()` method.
233-
+
234-
Any greater-than-one value here will override that default behavior.
235-
`with(RootGraph<T> graph)`::
236-
The `RootGraph` is a Hibernate extension to the Jakarta Persistence `EntityGraph` contract,
237-
and this method allows you to pass a specific `RootGraph` to the multi-load query
238-
so that it can fetch additional relationships of the current loading entity.
190+
Hibernate offers this functionality via the `Session#findMultiple` methods which accepts a list of identifiers to load and a group of options which control certain behaviors of the loading -
191+
192+
* `ReadOnlyMode` - whether the entities loaded should be marked as read-only.
193+
* `LockMode` (`LockModeType`) - a lock mode to be applied
194+
* `Timeout` - if a pessimistic lock mode is used, a timeout to allow
195+
* `Locking.Scope` (PessimisticLockScope`) - if a pessimistic lock mode is used, what scope should it be applied
196+
* `Locking.FollowOn` - allow (or not) Hibernate to acquire locks through additional queries if needed
197+
* `CacheMode` (`CacheStoreMode` / `CacheRetrieveMode`) - how second level caching should be used, if at all
198+
* `BatchSize` - how many identifiers should be loaded from the database at once
199+
* `SessionChecking` - whether to look into the persistence context to check entity state
200+
* `IncludeRemovals` - if `SessionChecking` is enabled, how removed entities should be handled
201+
* `OrderedReturn` - whether the results should be ordered according to the order of the passed identifiers
239202

240203
Now, assuming we have 3 `Person` entities in the database, we can load all of them with a single call
241204
as illustrated by the following example:
242205

243206
[[tag::pc-by-multiple-ids-example]]
244-
.Loading multiple entities using the `byMultipleIds()` Hibernate API
207+
.Loading multiple entities using the `findMultiple()` Hibernate API
245208
====
246209
247210
[source, java, indent=0]
248211
----
249-
include::{example-dir-pc}/MultiLoadIdTest.java[tags=pc-by-multiple-ids-example]
212+
include::{example-dir-pc}/FindMultipleDocTests.java[tags=pc-find-multiple-example]
250213
----
251-
252-
[source, SQL, indent=0]
253-
----
254-
include::{extrasdir}/pc-by-multiple-ids-example.sql[]
255-
----
256-
====
257-
258-
Notice that only one SQL SELECT statement was executed since the second call uses the
259-
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/MultiIdentifierLoadAccess.html#enableSessionCheck-boolean-[`enableSessionCheck`] method of the `MultiIdentifierLoadAccess`
260-
to instruct Hibernate to skip entities that are already loaded in the current Persistence Context.
261-
262-
If the entities are not available in the current Persistence Context but they could be loaded from the second-level cache, you can use the
263-
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/MultiIdentifierLoadAccess.html#with-org.hibernate.CacheMode-[`with(CacheMode)`] method of the `MultiIdentifierLoadAccess` object.
264-
265-
[[tag::pc-by-multiple-ids-second-level-cache-example]]
266-
.Loading multiple entities from the second-level cache
267214
====
268-
[source, java, indent=0]
269-
----
270-
include::{example-dir-pc}/MultiLoadIdTest.java[tags=pc-by-multiple-ids-second-level-cache-example]
271-
----
272-
====
273-
274-
In the example above, we first make sure that we clear the second-level cache to demonstrate that
275-
the multi-load query will put the returning entities into the second-level cache.
276-
277-
After executing the first `byMultipleIds` call, Hibernate is going to fetch the requested entities,
278-
and as illustrated by the `getSecondLevelCachePutCount` method call, 3 entities were indeed added to the
279-
shared cache.
280-
281-
Afterward, when executing the second `byMultipleIds` call for the same entities in a new Hibernate `Session`,
282-
we set the
283-
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/CacheMode.html#NORMAL[`CacheMode.NORMAL`] second-level cache mode so that entities are going to be returned from the second-level cache.
284-
285-
The `getSecondLevelCacheHitCount` statistics method returns 3 this time, since the 3 entities were loaded from the second-level cache, and, as illustrated by `sqlStatementInterceptor.getSqlQueries()`, no multi-load SELECT statement was executed this time.
286215

287216
[[pc-find-natural-id]]
288217
=== Obtain an entity by natural-id

hibernate-core/src/main/java/org/hibernate/IncludeRemovals.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,32 @@
1111
import java.util.List;
1212

1313
/**
14-
* MultiFindOption implementation to specify whether the returned list
15-
* of entity instances should contain instances that have been
16-
* {@linkplain Session#remove(Object) marked for removal} in the
17-
* current session, but not yet deleted in the database.
14+
* When {@linkplain SessionChecking} is enabled, this option controls how
15+
* to handle entities which are already contained by the persistence context
16+
* but which are in a removed state (marked for removal, but not yet flushed).
1817
* <p>
19-
* The default is {@link #EXCLUDE}, meaning that instances marked for
20-
* removal are replaced by null in the returned list of entities when {@link OrderedReturn}
21-
* is used.
18+
* The default is {@link #EXCLUDE}.
2219
*
23-
* @see org.hibernate.MultiFindOption
24-
* @see OrderedReturn
2520
* @see org.hibernate.Session#findMultiple(Class, List, FindOption...)
2621
* @see org.hibernate.Session#findMultiple(EntityGraph, List , FindOption...)
22+
*
23+
* @since 7.2
2724
*/
2825
public enum IncludeRemovals implements MultiFindOption {
26+
/**
27+
* Removed entities are included in the load result.
28+
*/
2929
INCLUDE,
30+
/**
31+
* The default. Removed entities are excluded from the load result.
32+
* <p/>
33+
* When combined with {@linkplain OrderedReturn#UNORDERED}, the entity is
34+
* simply excluded from the result.
35+
* <p/>
36+
* When combined with {@linkplain OrderedReturn#ORDERED}, the entity is replaced
37+
* by {@code null} in the result.
38+
*
39+
* @see OrderedReturn
40+
*/
3041
EXCLUDE
3142
}

hibernate-core/src/main/java/org/hibernate/MultiFindOption.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,18 @@
55
package org.hibernate;
66

77

8+
import jakarta.persistence.EntityGraph;
89
import jakarta.persistence.FindOption;
910

11+
import java.util.List;
12+
1013
/**
1114
* Simple marker interface for FindOptions which can be applied to multiple id loading.
15+
*
16+
* @see org.hibernate.Session#findMultiple(Class, List, FindOption...)
17+
* @see org.hibernate.Session#findMultiple(EntityGraph, List , FindOption...)
18+
*
19+
* @since 7.2
1220
*/
1321
public interface MultiFindOption extends FindOption {
1422
}

hibernate-core/src/main/java/org/hibernate/OrderedReturn.java

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,43 @@
1111
import java.util.List;
1212

1313
/**
14-
* MultiFindOption implementation to specify whether the returned list
15-
* of entity instances should be ordered, where the position of an entity
16-
* instance is determined by the position of its identifier
17-
* in the list of ids passed to {@code findMultiple(...)}.
14+
* Indicates whether the result list should be ordered relative to the
15+
* position of the identifier list. E.g.
16+
* <pre>
17+
* List&lt;Person&gt; results = session.findMultiple(
18+
* Person.class,
19+
* List.of(1,2,3,2),
20+
* ORDERED
21+
* );
22+
* assert results.get(0).getId() == 1;
23+
* assert results.get(1).getId() == 2;
24+
* assert results.get(2).getId() == 3;
25+
* assert results.get(3).getId() == 2;
26+
* </pre>
1827
* <p>
19-
* The default is {@link #ORDERED}, meaning the positions of the entities
20-
* in the returned list correspond to the positions of their ids. In this case,
21-
* the {@link IncludeRemovals} handling of entities marked for removal
22-
* becomes important.
28+
* The default is {@link #ORDERED}.
2329
*
24-
* @see org.hibernate.MultiFindOption
25-
* @see IncludeRemovals
2630
* @see org.hibernate.Session#findMultiple(Class, List, FindOption...)
2731
* @see org.hibernate.Session#findMultiple(EntityGraph, List , FindOption...)
32+
*
33+
* @since 7.2
2834
*/
2935
public enum OrderedReturn implements MultiFindOption {
36+
/**
37+
* The default. The result list is ordered relative to the
38+
* position of the identifiers list. This may result in {@code null}
39+
* elements in the list - <ul>
40+
* <li>non-existent identifiers
41+
* <li>removed entities (when combined with {@linkplain IncludeRemovals#EXCLUDE})
42+
* </ul>
43+
* <p/>
44+
* The result list will also always have the same length as the identifier list.
45+
*
46+
* @see IncludeRemovals
47+
*/
3048
ORDERED,
49+
/**
50+
* The result list may be in any order.
51+
*/
3152
UNORDERED
3253
}

hibernate-core/src/main/java/org/hibernate/Session.java

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -577,9 +577,6 @@ public interface Session extends SharedSessionContract, EntityManager {
577577
* <li>on the other hand, for databases with no SQL array type, a large batch size results
578578
* in long SQL statements with many JDBC parameters.
579579
* </ul>
580-
* <p>
581-
* For more advanced cases, use {@link #byMultipleIds(Class)}, which returns an instance of
582-
* {@link MultiIdentifierLoadAccess}.
583580
*
584581
* @param entityType the entity type
585582
* @param ids the list of identifiers
@@ -588,7 +585,9 @@ public interface Session extends SharedSessionContract, EntityManager {
588585
* @return an ordered list of persistent instances, with null elements representing missing
589586
* entities, whose positions in the list match the positions of their ids in the
590587
* given list of identifiers
591-
* @see #byMultipleIds(Class)
588+
*
589+
* @see MultiFindOption
590+
*
592591
* @since 7.0
593592
*/
594593
<E> List<E> findMultiple(Class<E> entityType, List<?> ids, FindOption... options);
@@ -617,9 +616,6 @@ public interface Session extends SharedSessionContract, EntityManager {
617616
* <li>on the other hand, for databases with no SQL array type, a large batch size results
618617
* in long SQL statements with many JDBC parameters.
619618
* </ul>
620-
* <p>
621-
* For more advanced cases, use {@link #byMultipleIds(Class)}, which returns an instance of
622-
* {@link MultiIdentifierLoadAccess}.
623619
*
624620
* @param entityGraph the entity graph interpreted as a load graph
625621
* @param ids the list of identifiers
@@ -628,7 +624,9 @@ public interface Session extends SharedSessionContract, EntityManager {
628624
* @return an ordered list of persistent instances, with null elements representing missing
629625
* entities, whose positions in the list match the positions of their ids in the
630626
* given list of identifiers
631-
* @see #byMultipleIds(Class)
627+
*
628+
* @see MultiFindOption
629+
*
632630
* @since 7.0
633631
*/
634632
<E> List<E> findMultiple(EntityGraph<E> entityGraph, List<?> ids, FindOption... options);
@@ -1166,9 +1164,7 @@ public interface Session extends SharedSessionContract, EntityManager {
11661164
*
11671165
* @see #findMultiple(Class, List, FindOption...)
11681166
*
1169-
* @deprecated This method will be removed.
1170-
* Use {@link #findMultiple(Class, List, FindOption...)} instead.
1171-
* See {@link MultiFindOption}.
1167+
* @deprecated Use {@link #findMultiple(Class, List, FindOption...)} instead.
11721168
*/
11731169
@Deprecated(since = "7.2", forRemoval = true)
11741170
<T> MultiIdentifierLoadAccess<T> byMultipleIds(Class<T> entityClass);
@@ -1183,9 +1179,8 @@ public interface Session extends SharedSessionContract, EntityManager {
11831179
*
11841180
* @throws HibernateException If the given name does not resolve to a mapped entity
11851181
*
1186-
* @deprecated This method will be removed.
1187-
* Use {@link #findMultiple(Class, List, FindOption...)} instead.
1188-
* See {@link MultiFindOption}.
1182+
* @deprecated Use {@link #findMultiple(EntityGraph, List, FindOption...)} instead,
1183+
* with {@linkplain SessionFactory#createGraphForDynamicEntity(String)}.
11891184
*/
11901185
@Deprecated(since = "7.2", forRemoval = true)
11911186
<T> MultiIdentifierLoadAccess<T> byMultipleIds(String entityName);

hibernate-core/src/main/java/org/hibernate/SessionChecking.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,32 @@
1111
import java.util.List;
1212

1313
/**
14-
* MultiFindOption implementation to specify whether the ids of managed entity instances already
15-
* cached in the current persistence context should be excluded.
16-
* from the list of ids sent to the database.
17-
* <p>
18-
* The default is {@link #DISABLED}, meaning all ids are included and sent to the database.
14+
* Indicates whether the persistence context should be checked for entities
15+
* matching the identifiers to be loaded - <ul>
16+
* <li>Entities which are in a managed state are not re-loaded from the database.
17+
* <li>Entities which are in a removed state are {@linkplain IncludeRemovals#EXCLUDE excluded}
18+
* from the result by default, but can be {@linkplain IncludeRemovals#INCLUDE included} if desired.
19+
* </ul>
20+
* <p/>
21+
* The default is {@link #DISABLED}
1922
*
20-
* Use {@link #ENABLED} to exclude already managed entity instance ids from
21-
* the list of ids sent to the database.
22-
*
23-
* @see org.hibernate.MultiFindOption
2423
* @see org.hibernate.Session#findMultiple(Class, List , FindOption...)
2524
* @see org.hibernate.Session#findMultiple(EntityGraph, List , FindOption...)
25+
*
26+
* @since 7.2
2627
*/
2728
public enum SessionChecking implements MultiFindOption {
29+
/**
30+
* The persistence context will be checked. Identifiers for entities already contained
31+
* in the persistence context will not be sent to the database for loading. If the
32+
* entity is marked for removal in the persistence context, whether it is returned
33+
* is controlled by {@linkplain IncludeRemovals}.
34+
*
35+
* @see IncludeRemovals
36+
*/
2837
ENABLED,
38+
/**
39+
* The default. All identifiers to be loaded will be read from the database and returned.
40+
*/
2941
DISABLED
3042
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.pc;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.Id;
9+
import jakarta.persistence.Table;
10+
import org.hibernate.testing.orm.junit.DomainModel;
11+
import org.hibernate.testing.orm.junit.SessionFactory;
12+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
13+
import org.junit.jupiter.api.Test;
14+
15+
import java.util.List;
16+
17+
import static org.hibernate.LockMode.PESSIMISTIC_WRITE;
18+
import static org.hibernate.OrderedReturn.ORDERED;
19+
20+
/**
21+
* @author Steve Ebersole
22+
*/
23+
@SuppressWarnings("JUnitMalformedDeclaration")
24+
@DomainModel(annotatedClasses = FindMultipleDocTests.Person.class)
25+
@SessionFactory
26+
public class FindMultipleDocTests {
27+
@Test
28+
void testUsage(SessionFactoryScope factoryScope) {
29+
factoryScope.inTransaction( (session) -> {
30+
//tag::pc-find-multiple-example[]
31+
List<Person> persons = session.findMultiple(
32+
Person.class,
33+
List.of(1,2,3),
34+
PESSIMISTIC_WRITE,
35+
ORDERED
36+
);
37+
//end::pc-find-multiple-example[]
38+
} );
39+
}
40+
41+
@Entity(name="Person")
42+
@Table(name="persons")
43+
public static class Person {
44+
@Id
45+
private Integer id;
46+
private String name;
47+
}
48+
}

0 commit comments

Comments
 (0)