mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-02-09 04:34:49 +00:00
HHH-18714 add support for inheritance in NamedEntityGraph annotation
This commit is contained in:
parent
36d6e46eb3
commit
4251dd8612
@ -20,6 +20,7 @@
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import org.hibernate.boot.model.NamedEntityGraphDefinition;
|
||||
import org.hibernate.boot.query.NamedQueryDefinition;
|
||||
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
|
||||
@ -73,7 +74,6 @@
|
||||
import static org.hibernate.metamodel.internal.InjectionHelper.injectTypedQueryReference;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
|
||||
@ -106,7 +106,7 @@ private ImportInfo(String importedName, Class<T> loadedClass) {
|
||||
private final Map<Class<?>, String> entityProxyInterfaceMap = new HashMap<>();
|
||||
|
||||
private final Map<String, ImportInfo<?>> nameToImportMap = new ConcurrentHashMap<>();
|
||||
private final Map<String,Object> knownInvalidnameToImportMap = new ConcurrentHashMap<>();
|
||||
private final Map<String, Object> knownInvalidnameToImportMap = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public JpaMetamodelImpl(
|
||||
@ -143,13 +143,14 @@ public JpaCompliance getJpaCompliance() {
|
||||
public <X> ManagedDomainType<X> managedType(String typeName) {
|
||||
final ManagedDomainType<X> managedType = findManagedType( typeName );
|
||||
if ( managedType == null ) {
|
||||
throw new IllegalArgumentException("Not a managed type: " + typeName);
|
||||
throw new IllegalArgumentException( "Not a managed type: " + typeName );
|
||||
}
|
||||
return managedType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable public EntityDomainType<?> findEntityType(@Nullable String entityName) {
|
||||
@Nullable
|
||||
public EntityDomainType<?> findEntityType(@Nullable String entityName) {
|
||||
if ( entityName == null ) {
|
||||
return null;
|
||||
}
|
||||
@ -162,18 +163,19 @@ public EntityDomainType<?> entity(String entityName) {
|
||||
final EntityDomainType<?> entityType = findEntityType( entityName );
|
||||
if ( entityType == null ) {
|
||||
// per JPA
|
||||
throw new IllegalArgumentException("Not an entity: " + entityName);
|
||||
throw new IllegalArgumentException( "Not an entity: " + entityName );
|
||||
}
|
||||
return entityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable public EmbeddableDomainType<?> findEmbeddableType(@Nullable String embeddableName) {
|
||||
@Nullable
|
||||
public EmbeddableDomainType<?> findEmbeddableType(@Nullable String embeddableName) {
|
||||
if ( embeddableName == null ) {
|
||||
return null;
|
||||
}
|
||||
final ManagedDomainType<?> managedType = managedTypeByName.get( embeddableName );
|
||||
if ( !( managedType instanceof EmbeddableDomainType<?> embeddableDomainType) ) {
|
||||
if ( !( managedType instanceof EmbeddableDomainType<?> embeddableDomainType ) ) {
|
||||
return null;
|
||||
}
|
||||
return embeddableDomainType;
|
||||
@ -183,7 +185,7 @@ public EntityDomainType<?> entity(String entityName) {
|
||||
public EmbeddableDomainType<?> embeddable(String embeddableName) {
|
||||
final EmbeddableDomainType<?> embeddableType = findEmbeddableType( embeddableName );
|
||||
if ( embeddableType == null ) {
|
||||
throw new IllegalArgumentException("Not an embeddable: " + embeddableName);
|
||||
throw new IllegalArgumentException( "Not an embeddable: " + embeddableName );
|
||||
}
|
||||
return embeddableType;
|
||||
}
|
||||
@ -226,7 +228,8 @@ public <X> EntityDomainType<X> resolveHqlEntityReference(String entityName) {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable public <X> ManagedDomainType<X> findManagedType(Class<X> cls) {
|
||||
@Nullable
|
||||
public <X> ManagedDomainType<X> findManagedType(Class<X> cls) {
|
||||
//noinspection unchecked
|
||||
return (ManagedDomainType<X>) managedTypeByClass.get( cls );
|
||||
}
|
||||
@ -242,7 +245,8 @@ public <X> ManagedDomainType<X> managedType(Class<X> cls) {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable public <X> EntityDomainType<X> findEntityType(Class<X> cls) {
|
||||
@Nullable
|
||||
public <X> EntityDomainType<X> findEntityType(Class<X> cls) {
|
||||
final ManagedType<?> type = managedTypeByClass.get( cls );
|
||||
if ( !( type instanceof EntityDomainType<?> ) ) {
|
||||
return null;
|
||||
@ -281,7 +285,7 @@ public <X> EmbeddableDomainType<X> embeddable(Class<X> cls) {
|
||||
|
||||
private Collection<ManagedDomainType<?>> getAllManagedTypes() {
|
||||
// should never happen
|
||||
return switch (jpaMetaModelPopulationSetting) {
|
||||
return switch ( jpaMetaModelPopulationSetting ) {
|
||||
case IGNORE_UNSUPPORTED -> managedTypeByClass.values();
|
||||
case ENABLED -> managedTypeByName.values();
|
||||
case DISABLED -> emptySet();
|
||||
@ -311,7 +315,7 @@ public Set<EmbeddableType<?>> getEmbeddables() {
|
||||
|
||||
@Override
|
||||
public @Nullable Set<String> getEnumTypesForValue(String enumValue) {
|
||||
return allowedEnumLiteralsToEnumTypeNames.get( enumValue);
|
||||
return allowedEnumLiteralsToEnumTypeNames.get( enumValue );
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -388,7 +392,8 @@ public <T> void addNamedEntityGraph(String graphName, RootGraphImplementor<T> en
|
||||
}
|
||||
}
|
||||
|
||||
@Override @SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> RootGraphImplementor<T> findEntityGraphByName(String name) {
|
||||
return (RootGraphImplementor<T>) entityGraphMap.get( name );
|
||||
}
|
||||
@ -494,17 +499,48 @@ private void applyNamedEntityGraphs(Collection<NamedEntityGraphDefinition> named
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
final NamedEntityGraph namedEntityGraph = definition.getAnnotation();
|
||||
final RootGraphImpl<?> entityGraph =
|
||||
createRootGraph( definition.getRegisteredName(), entityType,
|
||||
namedEntityGraph.includeAllAttributes() );
|
||||
if ( namedEntityGraph.attributeNodes() != null ) {
|
||||
applyNamedAttributeNodes( namedEntityGraph.attributeNodes(), namedEntityGraph, entityGraph );
|
||||
}
|
||||
createEntityGraph(
|
||||
namedEntityGraph,
|
||||
definition.getRegisteredName(),
|
||||
entityType,
|
||||
namedEntityGraph.includeAllAttributes()
|
||||
);
|
||||
|
||||
entityGraphMap.put( definition.getRegisteredName(), entityGraph );
|
||||
}
|
||||
}
|
||||
|
||||
private <T> RootGraphImpl<T> createEntityGraph(
|
||||
NamedEntityGraph namedEntityGraph,
|
||||
String registeredName,
|
||||
EntityDomainType<T> entityType,
|
||||
boolean includeAllAttributes) {
|
||||
final RootGraphImpl<T> entityGraph =
|
||||
createRootGraph( registeredName, entityType, includeAllAttributes );
|
||||
|
||||
if ( namedEntityGraph.subclassSubgraphs() != null ) {
|
||||
for ( var subclassSubgraph : namedEntityGraph.subclassSubgraphs() ) {
|
||||
GraphImplementor<?> subgraph = (GraphImplementor<?>) entityGraph.addTreatedSubgraph(
|
||||
(Class) subclassSubgraph.type() );
|
||||
|
||||
applyNamedAttributeNodes(
|
||||
subclassSubgraph.attributeNodes(),
|
||||
namedEntityGraph,
|
||||
subgraph
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( namedEntityGraph.attributeNodes() != null ) {
|
||||
applyNamedAttributeNodes( namedEntityGraph.attributeNodes(), namedEntityGraph, entityGraph );
|
||||
}
|
||||
|
||||
return entityGraph;
|
||||
}
|
||||
|
||||
private static <T> RootGraphImpl<T> createRootGraph(
|
||||
String name, EntityDomainType<T> entityType, boolean includeAllAttributes) {
|
||||
final RootGraphImpl<T> entityGraph = new RootGraphImpl<>( name, entityType );
|
||||
@ -521,31 +557,44 @@ private void applyNamedAttributeNodes(
|
||||
NamedEntityGraph namedEntityGraph,
|
||||
GraphImplementor<?> graphNode) {
|
||||
for ( NamedAttributeNode namedAttributeNode : namedAttributeNodes ) {
|
||||
final AttributeNodeImplementor<?> attributeNode =
|
||||
graphNode.findOrCreateAttributeNode( namedAttributeNode.value() );
|
||||
final String value = namedAttributeNode.value();
|
||||
final AttributeNodeImplementor<?> attributeNode = (AttributeNodeImplementor<?>) graphNode.addAttributeNode(
|
||||
value );
|
||||
|
||||
if ( isNotEmpty( namedAttributeNode.subgraph() ) ) {
|
||||
applyNamedSubgraphs(
|
||||
namedEntityGraph,
|
||||
namedAttributeNode.subgraph(),
|
||||
attributeNode.makeSubGraph()
|
||||
attributeNode,
|
||||
false
|
||||
);
|
||||
}
|
||||
if ( isNotEmpty( namedAttributeNode.keySubgraph() ) ) {
|
||||
applyNamedSubgraphs(
|
||||
namedEntityGraph,
|
||||
namedAttributeNode.keySubgraph(),
|
||||
attributeNode.makeKeySubGraph()
|
||||
attributeNode,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyNamedSubgraphs(
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private <T> void applyNamedSubgraphs(
|
||||
NamedEntityGraph namedEntityGraph,
|
||||
String subgraphName,
|
||||
SubGraphImplementor<?> subgraph) {
|
||||
AttributeNodeImplementor<T> attributeNode, Boolean isKeySubGraph) {
|
||||
for ( NamedSubgraph namedSubgraph : namedEntityGraph.subgraphs() ) {
|
||||
if ( subgraphName.equals( namedSubgraph.name() ) ) {
|
||||
final var isDefaultSubgraphType = namedSubgraph.type().equals( void.class );
|
||||
final Class subGraphType = isDefaultSubgraphType ? null : namedSubgraph.type();
|
||||
|
||||
final SubGraphImplementor<?> subgraph = makeAttributeNodeSubgraph(
|
||||
attributeNode, isKeySubGraph,
|
||||
subGraphType
|
||||
);
|
||||
|
||||
applyNamedAttributeNodes(
|
||||
namedSubgraph.attributeNodes(),
|
||||
namedEntityGraph,
|
||||
@ -555,6 +604,21 @@ private void applyNamedSubgraphs(
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> SubGraphImplementor<?> makeAttributeNodeSubgraph(
|
||||
AttributeNodeImplementor<T> attributeNode,
|
||||
Boolean isKeySubGraph,
|
||||
Class<T> subGraphType) {
|
||||
|
||||
if ( isKeySubGraph ) {
|
||||
return subGraphType != null ? attributeNode.makeKeySubGraph( subGraphType )
|
||||
: attributeNode.makeKeySubGraph();
|
||||
}
|
||||
|
||||
return subGraphType != null ?
|
||||
attributeNode.makeSubGraph( subGraphType ) :
|
||||
attributeNode.makeSubGraph();
|
||||
}
|
||||
|
||||
private <X> Class<X> resolveRequestedClass(String entityName) {
|
||||
try {
|
||||
return getServiceRegistry().requireService( ClassLoaderService.class )
|
||||
@ -631,7 +695,10 @@ public <T> EntityDomainType<T> resolveEntityReference(Class<T> javaType) {
|
||||
}
|
||||
}
|
||||
|
||||
throw new EntityTypeException( "Could not resolve entity class '" + javaType.getName() + "'", javaType.getName() );
|
||||
throw new EntityTypeException(
|
||||
"Could not resolve entity class '" + javaType.getName() + "'",
|
||||
javaType.getName()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -716,11 +783,30 @@ public void processJpa(
|
||||
|
||||
private void populateStaticMetamodel(MetadataImplementor bootMetamodel, MetadataContext context) {
|
||||
bootMetamodel.visitNamedHqlQueryDefinitions( definition
|
||||
-> injectTypedQueryReference( definition, namedQueryMetamodelClass( definition, context ) ) );
|
||||
-> injectTypedQueryReference(
|
||||
definition,
|
||||
namedQueryMetamodelClass(
|
||||
definition,
|
||||
context
|
||||
)
|
||||
) );
|
||||
bootMetamodel.visitNamedNativeQueryDefinitions( definition
|
||||
-> injectTypedQueryReference( definition, namedQueryMetamodelClass( definition, context ) ) );
|
||||
bootMetamodel.getNamedEntityGraphs().values().forEach(definition
|
||||
-> injectEntityGraph( definition, graphMetamodelClass( definition, context ), this ) );
|
||||
-> injectTypedQueryReference(
|
||||
definition,
|
||||
namedQueryMetamodelClass(
|
||||
definition,
|
||||
context
|
||||
)
|
||||
) );
|
||||
bootMetamodel.getNamedEntityGraphs().values().forEach( definition
|
||||
-> injectEntityGraph(
|
||||
definition,
|
||||
graphMetamodelClass(
|
||||
definition,
|
||||
context
|
||||
),
|
||||
this
|
||||
) );
|
||||
}
|
||||
|
||||
private Class<?> namedQueryMetamodelClass(NamedQueryDefinition<?> definition, MetadataContext context) {
|
||||
@ -764,9 +850,7 @@ private <T> EntityDomainType<T> locateOrBuildEntityType(
|
||||
MetadataContext context,
|
||||
final TypeConfiguration typeConfiguration) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final EntityDomainType<T> entityType =
|
||||
(EntityDomainType<T>)
|
||||
context.locateEntityType( persistentClass );
|
||||
final EntityDomainType<T> entityType = (EntityDomainType<T>) context.locateEntityType( persistentClass );
|
||||
return entityType == null
|
||||
? buildEntityType( persistentClass, context, typeConfiguration )
|
||||
: entityType;
|
||||
@ -818,8 +902,7 @@ private <T> MappedSuperclassDomainType<T> locateOrBuildMappedSuperclassType(
|
||||
TypeConfiguration typeConfiguration) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final MappedSuperclassDomainType<T> mappedSuperclassType =
|
||||
(MappedSuperclassDomainType<T>)
|
||||
context.locateMappedSuperclassType( mappedSuperclass );
|
||||
(MappedSuperclassDomainType<T>) context.locateMappedSuperclassType( mappedSuperclass );
|
||||
return mappedSuperclassType == null
|
||||
? buildMappedSuperclassType( mappedSuperclass, context, typeConfiguration )
|
||||
: mappedSuperclassType;
|
||||
|
@ -0,0 +1,250 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
* Copyright Red Hat Inc. and Hibernate Authors
|
||||
*/
|
||||
package org.hibernate.orm.test.entitygraph;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.graph.GraphSemantic;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.DiscriminatorColumn;
|
||||
import jakarta.persistence.DiscriminatorType;
|
||||
import jakarta.persistence.DiscriminatorValue;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Lansana DIOMANDE
|
||||
*/
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
EntityGraphWithInheritanceTest.Person.class,
|
||||
EntityGraphWithInheritanceTest.Student.class,
|
||||
EntityGraphWithInheritanceTest.Teacher.class,
|
||||
EntityGraphWithInheritanceTest.School.class,
|
||||
EntityGraphWithInheritanceTest.Course.class,
|
||||
EntityGraphWithInheritanceTest.FreeCourse.class,
|
||||
EntityGraphWithInheritanceTest.PayingCourse.class
|
||||
}
|
||||
)
|
||||
@SessionFactory
|
||||
@JiraKey(value = "HHH-18714")
|
||||
public class EntityGraphWithInheritanceTest {
|
||||
|
||||
private static Long STUDENT_ID = 1L;
|
||||
|
||||
@BeforeAll
|
||||
public void setup(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
var frenchTeacher = new Teacher();
|
||||
frenchTeacher.firstname = "John 2";
|
||||
frenchTeacher.lastname = "DOE";
|
||||
frenchTeacher.id = 2L;
|
||||
|
||||
var frenchCourse = new PayingCourse();
|
||||
frenchCourse.id = 1L;
|
||||
frenchCourse.name = "French";
|
||||
frenchCourse.moneyReceiver = frenchTeacher;
|
||||
|
||||
var student = new Student();
|
||||
student.id = STUDENT_ID;
|
||||
student.firstname = "John";
|
||||
student.lastname = "Doe";
|
||||
|
||||
var school = new School();
|
||||
school.name = "Fake";
|
||||
school.id = 1l;
|
||||
|
||||
student.school = school;
|
||||
|
||||
student.courses = new ArrayList<>() {{
|
||||
add( frenchCourse );
|
||||
}};
|
||||
|
||||
session.persist( student );
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public void clean(SessionFactoryScope scope) {
|
||||
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithoutGraph(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
var person = session.find(
|
||||
Person.class,
|
||||
STUDENT_ID
|
||||
);
|
||||
|
||||
session.detach( person );
|
||||
|
||||
assertThat( person ).isInstanceOf( Student.class );
|
||||
|
||||
var student = (Student) person;
|
||||
|
||||
assertThat( Hibernate.isInitialized( student.courses ) ).isFalse();
|
||||
assertThat( Hibernate.isInitialized( student.school ) ).isFalse();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWhenGraphHasSubclassSubgraph(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
||||
var graph = session.createEntityGraph( Person.class );
|
||||
|
||||
var studentSubGraph = graph.addTreatedSubgraph( Student.class );
|
||||
studentSubGraph.addAttributeNode( "school" );
|
||||
studentSubGraph.addAttributeNode( "courses" );
|
||||
|
||||
|
||||
var person = session.find(
|
||||
Person.class,
|
||||
STUDENT_ID,
|
||||
Collections.singletonMap( GraphSemantic.FETCH.getJakartaHintName(), graph )
|
||||
);
|
||||
|
||||
session.detach( person );
|
||||
|
||||
assertThat( person ).isInstanceOf( Student.class );
|
||||
|
||||
var student = (Student) person;
|
||||
|
||||
assertThat( Hibernate.isInitialized( student.school ) ).isTrue();
|
||||
assertThat( Hibernate.isInitialized( student.courses ) ).isTrue();
|
||||
assertThat( student.courses ).allSatisfy( course -> {
|
||||
assertThat( course ).isInstanceOf( PayingCourse.class );
|
||||
var payingCourse = (PayingCourse) course;
|
||||
assertThat( Hibernate.isInitialized( payingCourse.moneyReceiver ) ).isFalse();
|
||||
} );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWhenGraphHasSubclassSubgraphLinkedWithSubgraphThatHasInheritance(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
||||
var graph = session.createEntityGraph( Person.class );
|
||||
|
||||
var studentSubGraph = graph.addTreatedSubgraph( Student.class );
|
||||
studentSubGraph.addAttributeNode( "school" );
|
||||
studentSubGraph.addAttributeNode( "courses" );
|
||||
|
||||
var payingCourseSubgraph = studentSubGraph
|
||||
.addSubgraph( "courses", PayingCourse.class );
|
||||
|
||||
payingCourseSubgraph.addSubgraph( "moneyReceiver" );
|
||||
|
||||
var person = session.find(
|
||||
Person.class,
|
||||
STUDENT_ID,
|
||||
Collections.singletonMap( GraphSemantic.FETCH.getJakartaHintName(), graph )
|
||||
);
|
||||
|
||||
session.detach( person );
|
||||
|
||||
assertThat( person ).isInstanceOf( Student.class );
|
||||
|
||||
var student = (Student) person;
|
||||
|
||||
assertThat( Hibernate.isInitialized( student.school ) ).isTrue();
|
||||
assertThat( Hibernate.isInitialized( student.courses ) ).isTrue();
|
||||
assertThat( student.courses ).allSatisfy( course -> {
|
||||
assertThat( course ).isInstanceOf( PayingCourse.class );
|
||||
var payingCourse = (PayingCourse) course;
|
||||
assertThat( Hibernate.isInitialized( payingCourse.moneyReceiver ) ).isTrue();
|
||||
} );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Entity(name = "Person")
|
||||
@Inheritance(strategy = InheritanceType.JOINED)
|
||||
@DiscriminatorColumn(name = "person_type", discriminatorType = DiscriminatorType.STRING)
|
||||
public static abstract class Person {
|
||||
@Id
|
||||
Long id;
|
||||
String firstname;
|
||||
String lastname;
|
||||
|
||||
}
|
||||
|
||||
@Entity(name = "Student")
|
||||
@DiscriminatorValue("student")
|
||||
public static class Student extends Person {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
School school;
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
List<Course> courses;
|
||||
}
|
||||
|
||||
|
||||
@Entity(name = "Teacher")
|
||||
@DiscriminatorValue("teacher")
|
||||
public static class Teacher extends Person {
|
||||
}
|
||||
|
||||
|
||||
@Entity(name = "School")
|
||||
public static class School {
|
||||
@Id
|
||||
Long id;
|
||||
String name;
|
||||
}
|
||||
|
||||
@Entity(name = "Course")
|
||||
@DiscriminatorColumn(name = "course_type", discriminatorType = DiscriminatorType.STRING)
|
||||
public static abstract class Course {
|
||||
@Id
|
||||
Long id;
|
||||
String name;
|
||||
}
|
||||
|
||||
@Entity(name = "FreeCourse")
|
||||
public static class FreeCourse extends Course {
|
||||
}
|
||||
|
||||
@Entity(name = "PayingCourse")
|
||||
@DiscriminatorValue("paying")
|
||||
public static class PayingCourse extends Course {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
Teacher moneyReceiver;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
* Copyright Red Hat Inc. and Hibernate Authors
|
||||
*/
|
||||
package org.hibernate.orm.test.entitygraph.named.multiple;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.graph.GraphSemantic;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.DiscriminatorColumn;
|
||||
import jakarta.persistence.DiscriminatorType;
|
||||
import jakarta.persistence.DiscriminatorValue;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.NamedAttributeNode;
|
||||
import jakarta.persistence.NamedEntityGraph;
|
||||
import jakarta.persistence.NamedSubgraph;
|
||||
import jakarta.persistence.OneToMany;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
/**
|
||||
* @author Lansana DIOMANDE
|
||||
*/
|
||||
@DomainModel(
|
||||
annotatedClasses = {
|
||||
NamedEntityGraphsWithInheritanceTest.Person.class,
|
||||
NamedEntityGraphsWithInheritanceTest.Student.class,
|
||||
NamedEntityGraphsWithInheritanceTest.Teacher.class,
|
||||
NamedEntityGraphsWithInheritanceTest.School.class,
|
||||
NamedEntityGraphsWithInheritanceTest.Course.class,
|
||||
NamedEntityGraphsWithInheritanceTest.FreeCourse.class,
|
||||
NamedEntityGraphsWithInheritanceTest.PayingCourse.class
|
||||
}
|
||||
)
|
||||
@SessionFactory
|
||||
@JiraKey(value = "HHH-18714")
|
||||
public class NamedEntityGraphsWithInheritanceTest {
|
||||
|
||||
|
||||
private static Long STUDENT_ID = 1L;
|
||||
|
||||
@BeforeAll
|
||||
public void setup(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
var frenchTeacher = new Teacher();
|
||||
frenchTeacher.firstname = "John 2";
|
||||
frenchTeacher.lastname = "DOE";
|
||||
frenchTeacher.id = 2L;
|
||||
|
||||
var frenchCourse = new PayingCourse();
|
||||
frenchCourse.id = 1L;
|
||||
frenchCourse.name = "French";
|
||||
frenchCourse.moneyReceiver = frenchTeacher;
|
||||
|
||||
var student = new Student();
|
||||
student.id = STUDENT_ID;
|
||||
student.firstname = "John";
|
||||
student.lastname = "Doe";
|
||||
|
||||
var school = new School();
|
||||
school.name = "Fake";
|
||||
school.id = 1l;
|
||||
|
||||
student.school = school;
|
||||
|
||||
student.courses = new ArrayList<>() {{
|
||||
add( frenchCourse );
|
||||
}};
|
||||
|
||||
session.persist( student );
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public void clean(SessionFactoryScope scope) {
|
||||
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWhenGraphHasSubclassSubgraphLinkedWithSubgraphThatHasInheritance(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
|
||||
var graph = session.getEntityGraph(
|
||||
"graph_having_subclasssubgraph_linked_with_subgraph_that_has_inheritance" );
|
||||
|
||||
var person = session.find(
|
||||
Person.class,
|
||||
STUDENT_ID,
|
||||
Collections.singletonMap( GraphSemantic.FETCH.getJakartaHintName(), graph )
|
||||
);
|
||||
|
||||
session.detach( person );
|
||||
|
||||
assertThat( person ).isInstanceOf( Student.class );
|
||||
|
||||
var student = (Student) person;
|
||||
|
||||
assertThat( Hibernate.isInitialized( student.school ) ).isTrue();
|
||||
assertThat( Hibernate.isInitialized( student.courses ) ).isTrue();
|
||||
assertThat( student.courses ).allSatisfy( course -> {
|
||||
assertThat( course ).isInstanceOf( PayingCourse.class );
|
||||
var payingCourse = (PayingCourse) course;
|
||||
assertThat( Hibernate.isInitialized( payingCourse.moneyReceiver ) ).isTrue();
|
||||
} );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@NamedEntityGraph(
|
||||
name = "graph_having_subclasssubgraph_linked_with_subgraph_that_has_inheritance",
|
||||
subgraphs = {
|
||||
@NamedSubgraph(name = "course_sub_graph", type = PayingCourse.class, attributeNodes = {
|
||||
@NamedAttributeNode("moneyReceiver")
|
||||
})
|
||||
},
|
||||
subclassSubgraphs = {
|
||||
@NamedSubgraph(
|
||||
name = "notUsed",
|
||||
type = Student.class,
|
||||
attributeNodes = {
|
||||
@NamedAttributeNode(value = "school"),
|
||||
@NamedAttributeNode(value = "courses", subgraph = "course_sub_graph")
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
@Entity(name = "Person")
|
||||
@Inheritance(strategy = InheritanceType.JOINED)
|
||||
@DiscriminatorColumn(name = "person_type", discriminatorType = DiscriminatorType.STRING)
|
||||
public static abstract class Person {
|
||||
@Id
|
||||
Long id;
|
||||
String firstname;
|
||||
String lastname;
|
||||
|
||||
}
|
||||
|
||||
@Entity(name = "Student")
|
||||
@DiscriminatorValue("student")
|
||||
public static class Student extends Person {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
School school;
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
List<Course> courses;
|
||||
}
|
||||
|
||||
|
||||
@Entity(name = "Teacher")
|
||||
@DiscriminatorValue("teacher")
|
||||
public static class Teacher extends Person {
|
||||
}
|
||||
|
||||
|
||||
@Entity(name = "School")
|
||||
public static class School {
|
||||
@Id
|
||||
Long id;
|
||||
String name;
|
||||
}
|
||||
|
||||
@Entity(name = "Course")
|
||||
@DiscriminatorColumn(name = "course_type", discriminatorType = DiscriminatorType.STRING)
|
||||
public static abstract class Course {
|
||||
@Id
|
||||
Long id;
|
||||
String name;
|
||||
}
|
||||
|
||||
@Entity(name = "FreeCourse")
|
||||
public static class FreeCourse extends Course {
|
||||
}
|
||||
|
||||
@Entity(name = "PayingCourse")
|
||||
@DiscriminatorValue("paying")
|
||||
public static class PayingCourse extends Course {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
Teacher moneyReceiver;
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user