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());
+ }
+ }
+}