HHH-17948 add Session.findAll(), StatelessSession.getAll()

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-04-12 15:12:56 +02:00
parent 250a59a1c7
commit 526e282c4c
9 changed files with 175 additions and 4 deletions

View File

@ -126,8 +126,8 @@ public interface MultiIdentifierLoadAccess<T> {
/**
* 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?
* {@linkplain 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}

View File

@ -691,6 +691,26 @@ public interface Session extends SharedSessionContract, EntityManager {
*/
void clear();
/**
* Return the persistent instances of the given entity class with the given identifiers
* as a list. The position of an instance in the list matches the position of its identifier
* in the given array, and the list contains a null value if there is no persistent instance
* matching a given identifier. If an instance is already associated with the session, that
* instance is returned. This method never returns an uninitialized instance.
* <p>
* Every object returned by {@code findAll()} is either an unproxied instance of the given
* entity class, or a fully-fetched proxy object.
*
* @param entityType the entity type
* @param ids the identifiers
*
* @return an ordered list of persistent instances, with null elements representing missing
* entities
*
* @since 6.5
*/
<E> List<E> findAll(Class<E> entityType, Object... ids);
/**
* Return the persistent instance of the given entity class with the given identifier,
* or null if there is no such persistent instance. If the instance is already associated
@ -699,8 +719,8 @@ public interface Session extends SharedSessionContract, EntityManager {
* <p>
* This operation is very similar to {@link #find(Class, Object)}.
* <p>
* The object returned by {@code get()} or {@code find() } is either an unproxied instance
* of the given entity class, of a fully-fetched proxy object.
* The object returned by {@code get()} or {@code find()} is either an unproxied instance
* of the given entity class, or a fully-fetched proxy object.
* <p>
* This operation requests {@link LockMode#NONE}, that is, no lock, allowing the object
* to be retrieved from the cache without the cost of database access. However, if it is

View File

@ -9,6 +9,8 @@ package org.hibernate;
import jakarta.persistence.EntityGraph;
import org.hibernate.graph.GraphSemantic;
import java.util.List;
/**
* A command-oriented API often used for performing bulk operations against
* the database. A stateless session has no persistence context, and always
@ -250,6 +252,23 @@ public interface StatelessSession extends SharedSessionContract {
*/
<T> T get(EntityGraph<T> graph, GraphSemantic graphSemantic, Object id, LockMode lockMode);
/**
* Retrieve multiple rows, returning entity instances in a
* list where the position of an instance in the list matches
* the position of its identifier in the given array, and the
* list contains a null value if there is no persistent
* instance matching a given identifier.
*
* @param entityClass The class of the entity to retrieve
* @param ids The ids of the entities to retrieve
*
* @return an ordered list of detached entity instances, with
* null elements representing missing entities
*
* @since 6.5
*/
<T> List<T> getAll(Class<T> entityClass, Object... ids);
/**
* Refresh the entity instance state from the database.
*

View File

@ -953,6 +953,11 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
delegate.detach( entity );
}
@Override
public <E> List<E> findAll(Class<E> entityType, Object... ids) {
return delegate.findAll( entityType, ids );
}
@Override
public <T> T get(Class<T> theClass, Object id) {
return delegate.get( theClass, id );

View File

@ -263,6 +263,11 @@ public class SessionLazyDelegator implements Session {
this.lazySession.get().clear();
}
@Override
public <E> List<E> findAll(Class<E> entityType, Object... ids) {
return this.lazySession.get().findAll( entityType, ids );
}
@Override
public <T> T get(Class<T> entityType, Object id) {
return this.lazySession.get().get( entityType, id );

View File

@ -19,6 +19,7 @@ import java.sql.Connection;
import java.sql.NClob;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
@ -946,6 +947,11 @@ public class SessionImpl
return this.byId( entityName ).getReference( id );
}
@Override
public <E> List<E> findAll(Class<E> entityType, Object... ids) {
return this.byMultipleIds( entityType ).multiLoad( ids );
}
@Override
public <T> T get(Class<T> entityClass, Object id) throws HibernateException {
return this.byId( entityClass ).load( id );

View File

@ -6,6 +6,9 @@
*/
package org.hibernate.internal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
@ -60,12 +63,15 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.tuple.entity.EntityMetamodel;
import jakarta.persistence.EntityGraph;
import jakarta.transaction.SystemException;
import static java.util.Arrays.asList;
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable;
import static org.hibernate.engine.internal.Versioning.incrementVersion;
@ -510,6 +516,30 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
}
}
@Override
public <T> List<T> getAll(Class<T> entityClass, Object... ids) {
for (Object id : ids) {
if ( id == null ) {
throw new IllegalArgumentException("Null id");
}
}
final EntityPersister persister = getEntityPersister( entityClass.getName() );
final JpaCriteriaQuery<T> query = getCriteriaBuilder().createQuery(entityClass);
final JpaRoot<T> from = query.from(entityClass);
query.where( from.get(persister.getIdentifierPropertyName()).in(ids) );
final List<T> resultList = createSelectionQuery(query).getResultList();
final List<Object> idList = new ArrayList<>(resultList.size());
for (T entity : resultList) {
idList.add( persister.getIdentifier(entity, this) );
}
final List<T> list = new ArrayList<>(ids.length);
for (Object id : ids) {
final int pos = idList.indexOf(id);
list.add( pos < 0 ? null : resultList.get(pos) );
}
return list;
}
private EntityPersister getEntityPersister(String entityName) {
return getFactory().getMappingMetamodel().getEntityDescriptor( entityName );
}

View File

@ -0,0 +1,43 @@
package org.hibernate.orm.test.loading.multiLoad;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@SessionFactory
@DomainModel(annotatedClasses = FindAllTest.Record.class)
public class FindAllTest {
@Test void test(SessionFactoryScope scope) {
scope.inTransaction(s-> {
s.persist(new Record(123L,"hello earth"));
s.persist(new Record(456L,"hello mars"));
});
scope.inTransaction(s-> {
List<Record> all = s.findAll(Record.class, 456L, 123L, 2L);
assertEquals("hello mars",all.get(0).message);
assertEquals("hello earth",all.get(1).message);
assertNull(all.get(2));
});
}
@Entity
static class Record {
@Id Long id;
String message;
Record(Long id, String message) {
this.id = id;
this.message = message;
}
Record() {
}
}
}

View File

@ -0,0 +1,43 @@
package org.hibernate.orm.test.stateless;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@SessionFactory
@DomainModel(annotatedClasses = GetAllTest.Record.class)
public class GetAllTest {
@Test void test(SessionFactoryScope scope) {
scope.inStatelessTransaction(s-> {
s.insert(new Record(123L,"hello earth"));
s.insert(new Record(456L,"hello mars"));
});
scope.inStatelessTransaction(s-> {
List<Record> all = s.getAll(Record.class, 456L, 123L, 2L);
assertEquals("hello mars",all.get(0).message);
assertEquals("hello earth",all.get(1).message);
assertNull(all.get(2));
});
}
@Entity
static class Record {
@Id Long id;
String message;
Record(Long id, String message) {
this.id = id;
this.message = message;
}
Record() {
}
}
}