diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index 898312adc553..4331c77d9dbe 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -901,6 +901,13 @@ public interface Session extends SharedSessionContract, EntityManager { * @return a persistent instance or null * * @deprecated The semantics of this method may change in a future release. + * Use {@link SessionFactory#createGraphForDynamicEntity(String)} + * together with {@link #find(EntityGraph, Object, FindOption...)} + * to load {@link org.hibernate.metamodel.RepresentationMode#MAP + * dynamic entities}. + * + * @see SessionFactory#createGraphForDynamicEntity(String) + * @see #find(EntityGraph, Object, FindOption...) */ @Deprecated(since = "7") Object get(String entityName, Object id); diff --git a/hibernate-core/src/main/java/org/hibernate/SessionFactory.java b/hibernate-core/src/main/java/org/hibernate/SessionFactory.java index e72a3fef6ffb..26694b2b6fdd 100644 --- a/hibernate-core/src/main/java/org/hibernate/SessionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/SessionFactory.java @@ -14,6 +14,7 @@ import javax.naming.Referenceable; import jakarta.persistence.EntityManager; +import jakarta.persistence.FindOption; import jakarta.persistence.SynchronizationType; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.engine.spi.FilterDefinition; @@ -413,6 +414,27 @@ default R fromStatelessTransaction(Function acti */ RootGraph findEntityGraphByName(String name); + /** + * Create an {@link EntityGraph} which may be used from loading a + * {@linkplain org.hibernate.metamodel.RepresentationMode#MAP dynamic} + * entity with {@link Session#find(EntityGraph, Object, FindOption...)}. + *

+ * This allows a dynamic entity to be loaded without the need for a cast. + *

+	 * var MyDynamicEntity_ = factory.createGraphForDynamicEntity("MyDynamicEntity");
+	 * Map<String,?> myDynamicEntity = session.find(MyDynamicEntity_, id);
+	 * 
+ * + * @apiNote Dynamic entities are normally defined using XML mappings. + * + * @param entityName The name of the dynamic entity + * + * @since 7.0 + * + * @see Session#find(EntityGraph, Object, FindOption...) + */ + RootGraph> createGraphForDynamicEntity(String entityName); + /** * Obtain the set of names of all {@link org.hibernate.annotations.FilterDef * defined filters}. diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java index 36df4c831819..a59e27868601 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java @@ -38,6 +38,7 @@ import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.EntityCopyObserverFactory; import org.hibernate.event.spi.EventEngine; +import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.event.service.spi.EventListenerGroups; import org.hibernate.metamodel.MappingMetamodel; @@ -226,6 +227,11 @@ public SqlStringGenerationContext getSqlStringGenerationContext() { return delegate.getSqlStringGenerationContext(); } + @Override + public RootGraph> createGraphForDynamicEntity(String entityName) { + return delegate.createGraphForDynamicEntity( entityName ); + } + @Override public RootGraphImplementor findEntityGraphByName(String name) { return delegate.findEntityGraphByName( name ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java index d73ce9a7a856..03c160d9f457 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -72,6 +72,8 @@ import org.hibernate.event.spi.EventEngine; import org.hibernate.event.service.spi.EventListenerGroups; import org.hibernate.generator.Generator; +import org.hibernate.graph.RootGraph; +import org.hibernate.graph.internal.RootGraphImpl; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.integrator.spi.Integrator; import org.hibernate.integrator.spi.IntegratorService; @@ -82,8 +84,10 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.RootClass; import org.hibernate.metamodel.MappingMetamodel; +import org.hibernate.metamodel.RepresentationMode; import org.hibernate.metamodel.internal.RuntimeMetamodelsImpl; import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl; import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; @@ -724,6 +728,17 @@ public boolean isOpen() { return status != Status.CLOSED; } + @Override + public RootGraph> createGraphForDynamicEntity(String entityName) { + final EntityDomainType entity = getJpaMetamodel().entity( entityName ); + if ( entity.getRepresentationMode() != RepresentationMode.MAP ) { + throw new IllegalArgumentException( "Entity '" + entityName + "' is not a dynamic entity" ); + } + @SuppressWarnings("unchecked") //Safe, because we just checked + final EntityDomainType> dynamicEntity = (EntityDomainType>) entity; + return new RootGraphImpl<>( null, dynamicEntity ); + } + @Override public RootGraphImplementor findEntityGraphByName(String name) { return getJpaMetamodel().findEntityGraphByName( name ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 048f5ae4c344..8686dd5d1647 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -27,8 +27,8 @@ import org.hibernate.BatchSize; import org.hibernate.CacheMode; import org.hibernate.ConnectionAcquisitionMode; -import org.hibernate.EntityFilterException; import org.hibernate.EnabledFetchProfile; +import org.hibernate.EntityFilterException; import org.hibernate.FetchNotFoundException; import org.hibernate.FlushMode; import org.hibernate.HibernateException; @@ -106,6 +106,7 @@ import org.hibernate.event.spi.ReplicateEvent; import org.hibernate.event.spi.ReplicateEventListener; import org.hibernate.loader.internal.CacheLoadHelper; +import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.hibernate.resource.transaction.spi.TransactionObserver; import org.hibernate.event.monitor.spi.EventMonitor; import org.hibernate.event.monitor.spi.DiagnosticEvent; @@ -961,8 +962,7 @@ public void load(Object object, Object id) { fireLoad( new LoadEvent( id, object, this, getReadOnlyFromLoadQueryInfluencers() ), LoadEventListener.RELOAD ); } - private MultiIdentifierLoadAccess multiloadAccessWithOptions(Class entityClass, FindOption[] options) { - final MultiIdentifierLoadAccess loadAccess = byMultipleIds( entityClass ); + private void setMultiIdentifierLoadAccessOptions(FindOption[] options, MultiIdentifierLoadAccess loadAccess) { CacheStoreMode storeMode = getCacheStoreMode(); CacheRetrieveMode retrieveMode = getCacheRetrieveMode(); LockOptions lockOptions = copySessionLockOptions(); @@ -1006,12 +1006,13 @@ else if ( option instanceof BatchSize batchSizeOption ) { loadAccess.with( lockOptions ) .with( interpretCacheMode( storeMode, retrieveMode ) ) .withBatchSize( batchSize ); - return loadAccess; } @Override public List findMultiple(Class entityType, List ids, FindOption... options) { - return multiloadAccessWithOptions( entityType, options ).multiLoad( ids ); + final MultiIdentifierLoadAccess loadAccess = byMultipleIds( entityType ); + setMultiIdentifierLoadAccessOptions( options, loadAccess ); + return loadAccess.multiLoad( ids ); } @Override @@ -2498,8 +2499,7 @@ private static void logIgnoringEntityNotFound(Class entityClass, Object p } } - private IdentifierLoadAccessImpl loadAccessWithOptions(Class entityClass, FindOption[] options) { - final IdentifierLoadAccessImpl loadAccess = byId( entityClass ); + private void setLoadAccessOptions(FindOption[] options, IdentifierLoadAccessImpl loadAccess) { CacheStoreMode storeMode = getCacheStoreMode(); CacheRetrieveMode retrieveMode = getCacheRetrieveMode(); LockOptions lockOptions = copySessionLockOptions(); @@ -2537,19 +2537,26 @@ else if ( option instanceof ReadOnlyMode ) { } } loadAccess.with( lockOptions ).with( interpretCacheMode( storeMode, retrieveMode ) ); - return loadAccess; } @Override public T find(Class entityClass, Object primaryKey, FindOption... options) { - return loadAccessWithOptions( entityClass, options ).load( primaryKey ); + final IdentifierLoadAccessImpl loadAccess = byId( entityClass ); + setLoadAccessOptions( options, loadAccess ); + return loadAccess.load( primaryKey ); } @Override public T find(EntityGraph entityGraph, Object primaryKey, FindOption... options) { final RootGraph graph = (RootGraph) entityGraph; - final Class entityClass = graph.getGraphedType().getJavaType(); - return loadAccessWithOptions( entityClass, options ).withLoadGraph( graph ).load( primaryKey ); + final ManagedDomainType type = graph.getGraphedType(); + final IdentifierLoadAccessImpl loadAccess = + switch ( type.getRepresentationMode() ) { + case MAP -> byId( type.getTypeName() ); + case POJO -> byId( type.getJavaType() ); + }; + setLoadAccessOptions( options, loadAccess ); + return loadAccess.withLoadGraph( graph ).load( primaryKey ); } private void checkTransactionNeededForLock(LockMode lockMode) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/RepresentationMode.java b/hibernate-core/src/main/java/org/hibernate/metamodel/RepresentationMode.java index f9d022b53782..d20a375b9857 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/RepresentationMode.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/RepresentationMode.java @@ -4,7 +4,6 @@ */ package org.hibernate.metamodel; -import org.hibernate.AssertionFailure; import java.util.Locale; @@ -19,28 +18,19 @@ public enum RepresentationMode { MAP; public String getExternalName() { - switch (this) { - case POJO: - return "pojo"; - case MAP: - return "dynamic-map"; - default: - throw new AssertionFailure("Unknown RepresentationMode"); - } + return switch ( this ) { + case POJO -> "pojo"; + case MAP -> "dynamic-map"; + }; } public static RepresentationMode fromExternalName(String externalName) { - if ( externalName == null ) { - return POJO; - } - switch ( externalName.toLowerCase(Locale.ROOT) ) { - case "pojo": - return POJO; - case "dynamic-map": - case "map": - return MAP; - default: - throw new IllegalArgumentException("Unknown RepresentationMode"); - } + return externalName == null + ? POJO + : switch ( externalName.toLowerCase( Locale.ROOT ) ) { + case "pojo" -> POJO; + case "dynamic-map", "map" -> MAP; + default -> throw new IllegalArgumentException( "Unknown RepresentationMode" ); + }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java index 313f4f194b40..be6d9d1834d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java @@ -30,6 +30,7 @@ import org.hibernate.metamodel.model.domain.internal.AttributeContainer; import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.spi.DynamicModelJavaType; import static java.util.Collections.emptySet; import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty; @@ -66,10 +67,11 @@ protected AbstractManagedType( supertype.addSubType( this ); } - // todo (6.0) : need to handle RepresentationMode#MAP as well - this.representationMode = RepresentationMode.POJO; + representationMode = javaType instanceof DynamicModelJavaType + ? RepresentationMode.MAP + : RepresentationMode.POJO; - this.inFlightAccess = createInFlightAccess(); + inFlightAccess = createInFlightAccess(); } protected InFlightAccess createInFlightAccess() { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index ce21eaf0b2ff..8666a08e06b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -163,7 +163,8 @@ */ @Internal public abstract class AbstractCollectionPersister - implements CollectionPersister, InFlightCollectionMapping, CollectionMutationTarget, PluralAttributeMappingImpl.Aware, FetchProfileAffectee, Joinable { + implements CollectionPersister, InFlightCollectionMapping, CollectionMutationTarget, + PluralAttributeMappingImpl.Aware, FetchProfileAffectee, Joinable { private final NavigableRole navigableRole; private final CollectionSemantics collectionSemantics; diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 9a6bca03267a..518fdffdbc07 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -319,7 +319,8 @@ @Internal @SuppressWarnings("deprecation") public abstract class AbstractEntityPersister - implements EntityPersister, InFlightEntityMappingType, EntityMutationTarget, LazyPropertyInitializer, FetchProfileAffectee, Joinable { + implements EntityPersister, InFlightEntityMappingType, EntityMutationTarget, LazyPropertyInitializer, + FetchProfileAffectee, Joinable { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractEntityPersister.class ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/dynamic/DynamicJoinedInheritanceTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/dynamic/DynamicJoinedInheritanceTests.java index c8375802ee71..a06c6f277a29 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/dynamic/DynamicJoinedInheritanceTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/inheritance/dynamic/DynamicJoinedInheritanceTests.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.Map; +import org.hibernate.graph.RootGraph; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; @@ -33,6 +34,20 @@ public void testLoading(SessionFactoryScope scope) { } ); } + @Test + public void testLoadingNewApi(SessionFactoryScope scope) { + final RootGraph> Sub_ = + scope.getSessionFactory() + .createGraphForDynamicEntity( "Sub" ); + scope.inTransaction( (session) -> { + final Map entity = session.find( Sub_, 1 ); + assertThat( entity ).isNotNull(); + assertThat( entity.get( "name" ) ).isEqualTo( "sub" ); + assertThat( entity.get( TYPE_KEY ) ).isNotNull(); + assertThat( entity.get( TYPE_KEY ) ).isEqualTo( "Sub" ); + } ); + } + @BeforeEach public void createTestData(SessionFactoryScope scope) { scope.inTransaction( (session) -> {