diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index 21c5068dd8cc..732eddc9193e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -496,7 +496,7 @@ public Generator createGenerator(RuntimeModelCreationContext context) { } public Property copy() { - final Property property = new Property(); + final Property property = this instanceof SyntheticProperty ? new SyntheticProperty() : new Property(); property.setName( getName() ); property.setValue( getValue() ); property.setCascade( getCascade() ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java index bdeca9ff7707..863cd8f9213b 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/MetadataContext.java @@ -279,8 +279,10 @@ public Map> getIdentifiableTypesByName() { attribute = factoryFunction.apply( entityType, genericProperty ); if ( !property.isGeneric() ) { final PersistentAttribute concreteAttribute = factoryFunction.apply( entityType, property ); - //noinspection unchecked - ( (AttributeContainer) entityType ).getInFlightAccess().addConcreteGenericAttribute( concreteAttribute ); + if ( concreteAttribute != null ) { + //noinspection unchecked + ( (AttributeContainer) entityType ).getInFlightAccess().addConcreteGenericAttribute( concreteAttribute ); + } } } else { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/manytomany/generic/ManyToManyGenericTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/manytomany/generic/ManyToManyGenericTest.java new file mode 100644 index 000000000000..d5385faf4d09 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/manytomany/generic/ManyToManyGenericTest.java @@ -0,0 +1,98 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.manytomany.generic; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@Jpa( + annotatedClasses = { + ManyToManyGenericTest.NodeTree.class, + ManyToManyGenericTest.GenericNode.class + } +) +public class ManyToManyGenericTest { + + @Test + void testSelfReferencingGeneric(final EntityManagerFactoryScope scope) { + final UUID treeId = scope.fromTransaction(em -> { + final NodeTree tree = new NodeTree(); + final GenericNode root = new GenericNode<>(); + root.tree = tree; + final GenericNode branch = new GenericNode<>(); + branch.tree = tree; + tree.nodes.add(root); + tree.nodes.add(branch); + root.children.add(branch); + em.persist(tree); + return tree.id; + }); + + final NodeTree nodeTree = scope.fromEntityManager(em -> em.find(NodeTree.class, treeId)); + + assertThat(nodeTree, is(notNullValue())); + assertThat(nodeTree.id, is(treeId)); + assertThat(nodeTree.nodes, iterableWithSize(2)); + assertThat(nodeTree.nodes, containsInAnyOrder(List.of( + hasProperty("children", iterableWithSize(1)), + hasProperty("children", emptyIterable()) + ))); + } + + @Entity(name = "tree") + public static class NodeTree { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + public UUID id; + + @OneToMany(mappedBy = "tree", fetch = FetchType.EAGER, cascade = CascadeType.ALL) + public Set> nodes = new HashSet<>(); + } + + @Entity(name = "node") + public static class GenericNode { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + public UUID id; + + @ManyToOne(optional = false) + @JoinColumn(name = "TREE_ID") + public NodeTree tree; + + @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.DETACH}) + @JoinTable(name = "NODE_CHILDREN", + joinColumns = {@JoinColumn(name = "TREE_ID", referencedColumnName = "TREE_ID"), @JoinColumn(name = "NODE_ID", referencedColumnName = "ID")}, + inverseJoinColumns = {@JoinColumn(name = "CHILD_ID", referencedColumnName = "ID")} + ) + private final Set> children = new HashSet<>(); + + public Set> getChildren() { + return children; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/manytomany/generic/ManyToManyNonGenericTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/manytomany/generic/ManyToManyNonGenericTest.java new file mode 100644 index 000000000000..8a10874c8b92 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/manytomany/generic/ManyToManyNonGenericTest.java @@ -0,0 +1,109 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.manytomany.generic; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.emptyIterable; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.iterableWithSize; +import static org.hamcrest.Matchers.notNullValue; + +@Jpa( + annotatedClasses = { + ManyToManyNonGenericTest.NodeTree.class, + ManyToManyNonGenericTest.Node.class + } +) +public class ManyToManyNonGenericTest { + + @Test + void testSelfReferencingGeneric(final EntityManagerFactoryScope scope) { + final UUID treeId = scope.fromTransaction(em -> { + final NodeTree tree = new NodeTree(); + final Node root = new Node(); + root.tree = tree; + final Node branch = new Node(); + branch.tree = tree; + tree.nodes.add(root); + tree.nodes.add(branch); + root.children.add(branch); + em.persist(tree); + return tree.id; + }); + + final NodeTree nodeTree = scope.fromEntityManager(em -> em.find(NodeTree.class, treeId)); + + assertThat(nodeTree, is(notNullValue())); + assertThat(nodeTree.id, is(treeId)); + assertThat(nodeTree.nodes, iterableWithSize(2)); + assertThat(nodeTree.nodes, containsInAnyOrder(List.of( + hasProperty("children", iterableWithSize(1)), + hasProperty("children", emptyIterable()) + ))); + } + + @Entity(name = "tree") + public static class NodeTree { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + public UUID id; + + @OneToMany(mappedBy = "tree", fetch = FetchType.EAGER, cascade = CascadeType.ALL) + public Set nodes = new HashSet<>(); + } + + @Entity(name = "node") + public static class Node { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + public UUID id; + + @ManyToOne(optional = false) + @JoinColumn(name = "TREE_ID") + public NodeTree tree; + + @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.DETACH}) + @JoinTable(name = "NODE_CHILDREN", + joinColumns = {@JoinColumn(name = "TREE_ID", referencedColumnName = "TREE_ID"), @JoinColumn(name = "NODE_ID", referencedColumnName = "ID")}, + inverseJoinColumns = {@JoinColumn(name = "CHILD_ID", referencedColumnName = "ID")} + ) + private final Set children = new HashSet<>(); + + public Set getChildren() { + return children; + } + + @Override + public String toString() { + return String.format("node [%s] parent of %s", id, children.stream().map(n -> n.id).collect(Collectors.toList())); + } + } +}