HHH-18001 introduce support for FindOptions
Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
parent
23b461a109
commit
be6d8a9917
|
@ -9,6 +9,7 @@ package org.hibernate;
|
|||
import java.util.Locale;
|
||||
import jakarta.persistence.CacheRetrieveMode;
|
||||
import jakarta.persistence.CacheStoreMode;
|
||||
import jakarta.persistence.FindOption;
|
||||
|
||||
/**
|
||||
* Controls how the session interacts with the {@linkplain Cache second-level cache}
|
||||
|
@ -30,7 +31,7 @@ import jakarta.persistence.CacheStoreMode;
|
|||
* @see CacheStoreMode
|
||||
* @see CacheRetrieveMode
|
||||
*/
|
||||
public enum CacheMode {
|
||||
public enum CacheMode implements FindOption {
|
||||
|
||||
/**
|
||||
* The session may read items from the cache, and add items to the cache
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate;
|
||||
|
||||
import jakarta.persistence.FindOption;
|
||||
|
||||
/**
|
||||
* A {@link jakarta.persistence.FindOption} which requests a named
|
||||
* {@linkplain org.hibernate.annotations.FetchProfile fetch profile}.
|
||||
*
|
||||
* @param profileName the {@link org.hibernate.annotations.FetchProfile#name()}
|
||||
*
|
||||
* @since 7.0
|
||||
*
|
||||
* @see org.hibernate.annotations.FetchProfile
|
||||
* @see Session#find(Class, Object, FindOption...)
|
||||
*
|
||||
* @author Gavin King
|
||||
*/
|
||||
public record EnabledFetchProfile(String profileName)
|
||||
implements FindOption {
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate;
|
||||
|
||||
import jakarta.persistence.FindOption;
|
||||
import org.hibernate.jpa.internal.util.LockModeTypeHelper;
|
||||
|
||||
import jakarta.persistence.LockModeType;
|
||||
|
@ -42,7 +43,7 @@ import java.util.Locale;
|
|||
* @see LockOptions
|
||||
* @see org.hibernate.annotations.OptimisticLocking
|
||||
*/
|
||||
public enum LockMode {
|
||||
public enum LockMode implements FindOption {
|
||||
/**
|
||||
* No lock required. If an object is requested with this lock
|
||||
* mode, a {@link #READ} lock will be obtained if it turns out
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.util.Map;
|
|||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.persistence.FindOption;
|
||||
import jakarta.persistence.PessimisticLockScope;
|
||||
import org.hibernate.query.Query;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
|
@ -51,7 +52,7 @@ import static java.util.Collections.unmodifiableSet;
|
|||
*
|
||||
* @author Scott Marlow
|
||||
*/
|
||||
public class LockOptions implements Serializable {
|
||||
public class LockOptions implements FindOption, Serializable {
|
||||
/**
|
||||
* Represents {@link LockMode#NONE}, to which timeout and scope are
|
||||
* not applicable.
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate;
|
||||
|
||||
import jakarta.persistence.FindOption;
|
||||
|
||||
/**
|
||||
* A {@link jakarta.persistence.FindOption} which requests that
|
||||
* entities be loaded in {@link #READ_ONLY} mode or in regular
|
||||
* {@link #READ_WRITE} mode.
|
||||
*
|
||||
* @since 7.0
|
||||
*
|
||||
* @see Session#setDefaultReadOnly(boolean)
|
||||
* @see Session#find(Class, Object, FindOption...)
|
||||
*
|
||||
* @author Gavin King
|
||||
*/
|
||||
public enum ReadOnlyMode implements FindOption {
|
||||
/**
|
||||
* Specifies that an entity should be loaded in read-only mode.
|
||||
* <p>
|
||||
* Read-only entities are not dirty-checked and snapshots of
|
||||
* persistent state are not maintained. Read-only entities can
|
||||
* be modified, but changes are not persisted.
|
||||
*/
|
||||
READ_ONLY,
|
||||
/**
|
||||
* Specifies that an entity should be loaded in the default
|
||||
* modifiable mode, regardless of the default mode of the
|
||||
* session.
|
||||
*/
|
||||
READ_WRITE
|
||||
}
|
|
@ -407,8 +407,8 @@ public interface Session extends SharedSessionContract, EntityManager {
|
|||
* changes are not persisted.
|
||||
* <p>
|
||||
* When a proxy is initialized, the loaded entity will have the same
|
||||
* read-only/modifiable setting as the uninitialized
|
||||
* proxy has, regardless of the session's current setting.
|
||||
* read-only/modifiable setting as the uninitialized proxy has,
|
||||
* regardless of the session's current setting.
|
||||
* <p>
|
||||
* To change the read-only/modifiable setting for a particular entity
|
||||
* or proxy that already belongs to this session use
|
||||
|
|
|
@ -22,9 +22,12 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import jakarta.persistence.PessimisticLockScope;
|
||||
import jakarta.persistence.Timeout;
|
||||
import org.hibernate.CacheMode;
|
||||
import org.hibernate.ConnectionAcquisitionMode;
|
||||
import org.hibernate.EntityFilterException;
|
||||
import org.hibernate.EnabledFetchProfile;
|
||||
import org.hibernate.FetchNotFoundException;
|
||||
import org.hibernate.FlushMode;
|
||||
import org.hibernate.HibernateException;
|
||||
|
@ -39,6 +42,7 @@ import org.hibernate.NaturalIdLoadAccess;
|
|||
import org.hibernate.NaturalIdMultiLoadAccess;
|
||||
import org.hibernate.ObjectDeletedException;
|
||||
import org.hibernate.ObjectNotFoundException;
|
||||
import org.hibernate.ReadOnlyMode;
|
||||
import org.hibernate.ReplicationMode;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionEventListener;
|
||||
|
@ -106,6 +110,7 @@ import org.hibernate.event.spi.ResolveNaturalIdEventListener;
|
|||
import org.hibernate.event.spi.SaveOrUpdateEvent;
|
||||
import org.hibernate.event.spi.SaveOrUpdateEventListener;
|
||||
import org.hibernate.graph.GraphSemantic;
|
||||
import org.hibernate.graph.RootGraph;
|
||||
import org.hibernate.graph.spi.RootGraphImplementor;
|
||||
import org.hibernate.internal.util.ExceptionHelper;
|
||||
import org.hibernate.jpa.internal.LegacySpecHelper;
|
||||
|
@ -2557,16 +2562,58 @@ public class SessionImpl
|
|||
}
|
||||
}
|
||||
|
||||
private <T> IdentifierLoadAccessImpl<T> loadAccessWithOptions(Class<T> entityClass, FindOption[] options) {
|
||||
final IdentifierLoadAccessImpl<T> loadAccess = byId(entityClass);
|
||||
CacheStoreMode storeMode = null;
|
||||
CacheRetrieveMode retrieveMode = null;
|
||||
LockOptions lockOptions = new LockOptions();
|
||||
for ( FindOption option : options) {
|
||||
if ( option instanceof CacheStoreMode ) {
|
||||
storeMode = (CacheStoreMode) option;
|
||||
}
|
||||
else if ( option instanceof CacheRetrieveMode ) {
|
||||
retrieveMode = (CacheRetrieveMode) option;
|
||||
}
|
||||
else if ( option instanceof LockModeType ) {
|
||||
lockOptions.setLockMode( LockModeTypeHelper.getLockMode( (LockModeType) option ) );
|
||||
}
|
||||
else if ( option instanceof LockMode ) {
|
||||
lockOptions.setLockMode( (LockMode) option );
|
||||
}
|
||||
else if ( option instanceof LockOptions ) {
|
||||
lockOptions = (LockOptions) option;
|
||||
}
|
||||
else if ( option instanceof PessimisticLockScope ) {
|
||||
lockOptions.setLockScope( (PessimisticLockScope) option );
|
||||
}
|
||||
else if ( option instanceof Timeout ) {
|
||||
final Timeout timeout = (Timeout) option;
|
||||
lockOptions.setTimeOut( timeout.milliseconds() );
|
||||
}
|
||||
else if ( option instanceof EnabledFetchProfile ) {
|
||||
EnabledFetchProfile enabledFetchProfile = (EnabledFetchProfile) option;
|
||||
loadAccess.enableFetchProfile( enabledFetchProfile.profileName() );
|
||||
}
|
||||
else if ( option instanceof ReadOnlyMode ) {
|
||||
loadAccess.withReadOnly( option == ReadOnlyMode.READ_ONLY );
|
||||
}
|
||||
}
|
||||
loadAccess.with( lockOptions ).with( interpretCacheMode( storeMode, retrieveMode ) );
|
||||
return loadAccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T find(Class<T> entityClass, Object primaryKey, FindOption... options) {
|
||||
// todo (jpa 3.2) : implement
|
||||
throw new UnsupportedOperationException( "Not yet implemented" );
|
||||
return loadAccessWithOptions( entityClass, options ).load( primaryKey );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T find(EntityGraph<T> entityGraph, Object primaryKey, FindOption... options) {
|
||||
// todo (jpa 3.2) : implement
|
||||
throw new UnsupportedOperationException( "Not yet implemented" );
|
||||
final RootGraph<T> graph = (RootGraph<T>) entityGraph;
|
||||
final Class<T> entityClass = graph.getGraphedType().getJavaType();
|
||||
return loadAccessWithOptions( entityClass, options )
|
||||
.withLoadGraph( graph )
|
||||
.load( primaryKey );
|
||||
}
|
||||
|
||||
private void checkTransactionNeededForLock(LockModeType lockModeType) {
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
package org.hibernate.orm.test.jpa.findoptions;
|
||||
|
||||
import jakarta.persistence.CacheStoreMode;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityGraph;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.LockModeType;
|
||||
import jakarta.persistence.Timeout;
|
||||
import org.hibernate.EnabledFetchProfile;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.ReadOnlyMode;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.annotations.FetchProfile;
|
||||
import org.hibernate.annotations.FetchProfileOverride;
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.hibernate.Hibernate.isInitialized;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
@Jpa(annotatedClasses = FindOptionsTest.MyEntity.class)
|
||||
public class FindOptionsTest {
|
||||
@Test
|
||||
void test(EntityManagerFactoryScope scope) {
|
||||
MyEntity hello = new MyEntity("Hello");
|
||||
scope.getEntityManagerFactory()
|
||||
.runInTransaction(em -> em.persist(hello));
|
||||
scope.getEntityManagerFactory()
|
||||
.runInTransaction(em -> {
|
||||
MyEntity entity = em.find(MyEntity.class, hello.id,
|
||||
LockModeType.PESSIMISTIC_WRITE, Timeout.seconds(10),
|
||||
CacheStoreMode.BYPASS, ReadOnlyMode.READ_WRITE);
|
||||
assertFalse(em.unwrap(Session.class).isReadOnly(entity));
|
||||
assertEquals(LockModeType.PESSIMISTIC_WRITE, em.getLockMode(entity));
|
||||
assertEquals(hello.name, entity.name);
|
||||
assertFalse(isInitialized(entity.list));
|
||||
});
|
||||
scope.getEntityManagerFactory()
|
||||
.runInTransaction(em -> {
|
||||
MyEntity entity = em.find(MyEntity.class, hello.id,
|
||||
LockModeType.PESSIMISTIC_READ, ReadOnlyMode.READ_ONLY,
|
||||
new EnabledFetchProfile("hello world"));
|
||||
assertTrue(em.unwrap(Session.class).isReadOnly(entity));
|
||||
assertEquals(LockModeType.PESSIMISTIC_READ, em.getLockMode(entity));
|
||||
assertEquals(hello.name, entity.name);
|
||||
assertTrue(isInitialized(entity.list));
|
||||
});
|
||||
scope.getEntityManagerFactory()
|
||||
.runInTransaction(em -> {
|
||||
try {
|
||||
em.find(MyEntity.class, hello.id, LockMode.OPTIMISTIC);
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
return;
|
||||
}
|
||||
fail();
|
||||
});
|
||||
scope.getEntityManagerFactory()
|
||||
.runInTransaction(em -> {
|
||||
try {
|
||||
em.find(MyEntity.class, hello.id, LockMode.OPTIMISTIC_FORCE_INCREMENT);
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
return;
|
||||
}
|
||||
fail();
|
||||
});
|
||||
scope.getEntityManagerFactory()
|
||||
.runInTransaction(em -> {
|
||||
try {
|
||||
em.find(MyEntity.class, hello.id, LockMode.PESSIMISTIC_FORCE_INCREMENT);
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
return;
|
||||
}
|
||||
fail();
|
||||
});
|
||||
scope.getEntityManagerFactory()
|
||||
.runInTransaction(em -> {
|
||||
EntityGraph<MyEntity> graph = em.createEntityGraph(MyEntity.class);
|
||||
MyEntity entity = em.find(graph, hello.id,
|
||||
LockModeType.PESSIMISTIC_WRITE, ReadOnlyMode.READ_ONLY);
|
||||
assertTrue(em.unwrap(Session.class).isReadOnly(entity));
|
||||
assertEquals(LockModeType.PESSIMISTIC_WRITE, em.getLockMode(entity));
|
||||
assertEquals(hello.name, entity.name);
|
||||
assertFalse(isInitialized(entity.list));
|
||||
});
|
||||
scope.getEntityManagerFactory()
|
||||
.runInTransaction(em -> {
|
||||
EntityGraph<MyEntity> graph = em.createEntityGraph(MyEntity.class);
|
||||
graph.addAttributeNode("list");
|
||||
MyEntity entity = em.find(graph, hello.id,
|
||||
LockModeType.PESSIMISTIC_READ, ReadOnlyMode.READ_WRITE);
|
||||
assertFalse(em.unwrap(Session.class).isReadOnly(entity));
|
||||
assertEquals(LockModeType.PESSIMISTIC_READ, em.getLockMode(entity));
|
||||
assertEquals(hello.name, entity.name);
|
||||
assertTrue(isInitialized(entity.list));
|
||||
});
|
||||
}
|
||||
|
||||
@Entity
|
||||
@FetchProfile(name = "hello world")
|
||||
public static class MyEntity {
|
||||
@Id @GeneratedValue long id;
|
||||
String name;
|
||||
|
||||
@ElementCollection(fetch = FetchType.LAZY)
|
||||
@FetchProfileOverride(profile = "hello world",
|
||||
fetch = FetchType.EAGER)
|
||||
List<String> list;
|
||||
|
||||
public MyEntity(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
MyEntity() {
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue