diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java index bf26e3d616..bf8b133df9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java @@ -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 loadedClass) { private final Map, String> entityProxyInterfaceMap = new HashMap<>(); private final Map> nameToImportMap = new ConcurrentHashMap<>(); - private final Map knownInvalidnameToImportMap = new ConcurrentHashMap<>(); + private final Map knownInvalidnameToImportMap = new ConcurrentHashMap<>(); public JpaMetamodelImpl( @@ -143,13 +143,14 @@ public JpaCompliance getJpaCompliance() { public ManagedDomainType managedType(String typeName) { final ManagedDomainType 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 EntityDomainType resolveHqlEntityReference(String entityName) { } @Override - @Nullable public ManagedDomainType findManagedType(Class cls) { + @Nullable + public ManagedDomainType findManagedType(Class cls) { //noinspection unchecked return (ManagedDomainType) managedTypeByClass.get( cls ); } @@ -242,7 +245,8 @@ public ManagedDomainType managedType(Class cls) { } @Override - @Nullable public EntityDomainType findEntityType(Class cls) { + @Nullable + public EntityDomainType findEntityType(Class cls) { final ManagedType type = managedTypeByClass.get( cls ); if ( !( type instanceof EntityDomainType ) ) { return null; @@ -281,7 +285,7 @@ public EmbeddableDomainType embeddable(Class cls) { private Collection> 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> getEmbeddables() { @Override public @Nullable Set getEnumTypesForValue(String enumValue) { - return allowedEnumLiteralsToEnumTypeNames.get( enumValue); + return allowedEnumLiteralsToEnumTypeNames.get( enumValue ); } @Override @@ -388,7 +392,8 @@ public void addNamedEntityGraph(String graphName, RootGraphImplementor en } } - @Override @SuppressWarnings("unchecked") + @Override + @SuppressWarnings("unchecked") public RootGraphImplementor findEntityGraphByName(String name) { return (RootGraphImplementor) entityGraphMap.get( name ); } @@ -494,17 +499,48 @@ private void applyNamedEntityGraphs(Collection 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 RootGraphImpl createEntityGraph( + NamedEntityGraph namedEntityGraph, + String registeredName, + EntityDomainType entityType, + boolean includeAllAttributes) { + final RootGraphImpl 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 RootGraphImpl createRootGraph( String name, EntityDomainType entityType, boolean includeAllAttributes) { final RootGraphImpl 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 void applyNamedSubgraphs( NamedEntityGraph namedEntityGraph, String subgraphName, - SubGraphImplementor subgraph) { + AttributeNodeImplementor 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 SubGraphImplementor makeAttributeNodeSubgraph( + AttributeNodeImplementor attributeNode, + Boolean isKeySubGraph, + Class subGraphType) { + + if ( isKeySubGraph ) { + return subGraphType != null ? attributeNode.makeKeySubGraph( subGraphType ) + : attributeNode.makeKeySubGraph(); + } + + return subGraphType != null ? + attributeNode.makeSubGraph( subGraphType ) : + attributeNode.makeSubGraph(); + } + private Class resolveRequestedClass(String entityName) { try { return getServiceRegistry().requireService( ClassLoaderService.class ) @@ -631,7 +695,10 @@ public EntityDomainType resolveEntityReference(Class 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 EntityDomainType locateOrBuildEntityType( MetadataContext context, final TypeConfiguration typeConfiguration) { @SuppressWarnings("unchecked") - final EntityDomainType entityType = - (EntityDomainType) - context.locateEntityType( persistentClass ); + final EntityDomainType entityType = (EntityDomainType) context.locateEntityType( persistentClass ); return entityType == null ? buildEntityType( persistentClass, context, typeConfiguration ) : entityType; @@ -818,8 +902,7 @@ private MappedSuperclassDomainType locateOrBuildMappedSuperclassType( TypeConfiguration typeConfiguration) { @SuppressWarnings("unchecked") final MappedSuperclassDomainType mappedSuperclassType = - (MappedSuperclassDomainType) - context.locateMappedSuperclassType( mappedSuperclass ); + (MappedSuperclassDomainType) context.locateMappedSuperclassType( mappedSuperclass ); return mappedSuperclassType == null ? buildMappedSuperclassType( mappedSuperclass, context, typeConfiguration ) : mappedSuperclassType; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/EntityGraphWithInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/EntityGraphWithInheritanceTest.java new file mode 100644 index 0000000000..55102f314c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/EntityGraphWithInheritanceTest.java @@ -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 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; + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/multiple/NamedEntityGraphsWithInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/multiple/NamedEntityGraphsWithInheritanceTest.java new file mode 100644 index 0000000000..ac4a1be569 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/multiple/NamedEntityGraphsWithInheritanceTest.java @@ -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 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; + } + + +}