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 d31c01799cc3..61860fd2d292 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -477,7 +477,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 5498d8d4a204..02276925acb5 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 @@ -57,7 +57,6 @@ import static java.util.Collections.unmodifiableMap; import static org.hibernate.metamodel.internal.InjectionHelper.injectField; - /** * Defines a context for storing information during the building of the {@link MappingMetamodelImpl}. *

@@ -274,9 +273,10 @@ public Map> getIdentifiableTypesByName() { attribute = factoryFunction.apply( entityType, genericProperty ); if ( !property.isGeneric() ) { final PersistentAttribute concreteAttribute = factoryFunction.apply( entityType, property ); - @SuppressWarnings("unchecked") - final AttributeContainer attributeContainer = (AttributeContainer) entityType; - attributeContainer.getInFlightAccess().addConcreteGenericAttribute( concreteAttribute ); + if ( concreteAttribute != null ) { + @SuppressWarnings("unchecked") final AttributeContainer attributeContainer = (AttributeContainer) entityType; + attributeContainer.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..0ef835cbaccc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/manytomany/generic/ManyToManyGenericTest.java @@ -0,0 +1,96 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +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..7e8ba3949557 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/manytomany/generic/ManyToManyNonGenericTest.java @@ -0,0 +1,106 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +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.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 "node [%s] parent of %s".formatted(id, children.stream().map(n -> n.id).toList()); + } + } +}