From 33b1ab27f72d4b0c9af586e998eb2a5b46002785 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 21 Jun 2025 10:32:51 +0200 Subject: [PATCH 1/3] HHH-19555 add tests showing things actually working nicely --- ...ToOneImplicitJoinTableRestrictionTest.java | 91 +++++++++++++++++++ .../ManyToOneImplicitJoinTableTest.java | 30 +++++- 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableRestrictionTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableRestrictionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableRestrictionTest.java new file mode 100644 index 000000000000..a692265f3829 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableRestrictionTest.java @@ -0,0 +1,91 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.manytoone.jointable; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToOne; +import org.hibernate.annotations.SQLRestriction; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +@Jpa(annotatedClasses = + {ManyToOneImplicitJoinTableRestrictionTest.X.class, + ManyToOneImplicitJoinTableRestrictionTest.Y.class}) +class ManyToOneImplicitJoinTableRestrictionTest { + @JiraKey("HHH-19555") @Test + void test(EntityManagerFactoryScope scope) { + scope.inTransaction( s -> { + X x = new X(); + Y y = new Y(); + x.id = -1; + y.x = x; + s.persist( x ); + s.persist( y ); + } ); + scope.inTransaction( s -> { + Y y = s.find( Y.class, 0L ); + y.name = "Gavin"; + assertNull(y.x); + } ); + scope.inTransaction( s -> { + Y y = s.find( Y.class, 0L ); + assertEquals("Gavin", y.name); + assertNull(y.x); + var id = s.createNativeQuery( "select x_id from Y_X", long.class ).getSingleResult(); + assertEquals( -1L, id ); + } ); + scope.inTransaction( s -> { + Y y = s.find( Y.class, 0L ); + X x = new X(); + x.id = 1; + s.persist( x ); + y.x = x; + // uses a SQL merge to update the join table + } ); + scope.inTransaction( s -> { + Y y = s.find( Y.class, 0L ); + assertEquals("Gavin", y.name); + assertNotNull(y.x); + assertEquals( 1L, y.x.id ); + var id = s.createNativeQuery( "select x_id from Y_X", long.class ).getSingleResult(); + assertEquals( 1L, id ); + } ); + scope.inTransaction( s -> { + Y y = s.find( Y.class, 0L ); + y.x = null; + // uses a SQL merge to update the join table + } ); + scope.inTransaction( s -> { + Y y = s.find( Y.class, 0L ); + assertEquals("Gavin", y.name); + assertNull(y.x); + var id = s.createNativeQuery( "select x_id from Y_X", long.class ).getSingleResultOrNull(); + assertNull( id ); + } ); + } + + @Entity(name="Y") + static class Y { + @Id + long id; + String name; + @JoinTable + @ManyToOne X x; + } + @Entity(name="X") + @SQLRestriction("id>0") + static class X { + @Id + long id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableTest.java index a3b486f76d17..e9c0c44d77b2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableTest.java @@ -11,7 +11,8 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.AssertionsKt.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; @Jpa(annotatedClasses = {ManyToOneImplicitJoinTableTest.X.class, @@ -21,6 +22,7 @@ class ManyToOneImplicitJoinTableTest { void test(EntityManagerFactoryScope scope) { scope.inTransaction( s -> { X x = new X(); + x.id = 1; Y y = new Y(); y.x = x; s.persist( x ); @@ -34,8 +36,34 @@ void test(EntityManagerFactoryScope scope) { Y y = s.find( Y.class, 0L ); assertEquals("Gavin", y.name); assertNotNull(y.x); + assertEquals( 1L, y.x.id ); + } ); + scope.inTransaction( s -> { + Y y = s.find( Y.class, 0L ); + X x = new X(); + x.id = -1; + s.persist( x ); + y.x = x; + // uses a SQL merge to update the join table + } ); + scope.inTransaction( s -> { + Y y = s.find( Y.class, 0L ); + assertEquals("Gavin", y.name); + assertNotNull(y.x); + assertEquals( -1L, y.x.id ); + } ); + scope.inTransaction( s -> { + Y y = s.find( Y.class, 0L ); + y.x = null; + // uses a SQL merge to update the join table + } ); + scope.inTransaction( s -> { + Y y = s.find( Y.class, 0L ); + assertEquals("Gavin", y.name); + assertNull(y.x); } ); } + @Entity(name="Y") static class Y { @Id From 028610e339c0e6d82aad435967ddf631716470a5 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 21 Jun 2025 10:59:48 +0200 Subject: [PATCH 2/3] HHH-19555 improve javadoc for SQLRestriction --- .../org/hibernate/annotations/SQLRestriction.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/SQLRestriction.java b/hibernate-core/src/main/java/org/hibernate/annotations/SQLRestriction.java index 3d15fca6cf8c..ca36dc202e4f 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/SQLRestriction.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/SQLRestriction.java @@ -37,8 +37,21 @@ * List<Document> documents; * *

+ * If a restriction declared by an entity should be applied to a to-one + * association to that entity type, the association should be mapped to + * an {@linkplain jakarta.persistence.JoinTable association table}. + *

+ * @ManyToOne
+ * @JoinTable(name = "application_document")
+ * Document document;
+ * 
+ * The {@code SQLRestriction} annotation may not be directly applied to + * a field or property annotated {@link jakarta.persistence.OneToOne} or + * {@link jakarta.persistence.ManyToOne}, and restrictions on foreign + * key associations are dangerous. + *

* The {@link SQLJoinTableRestriction} annotation lets a restriction be - * applied to an {@linkplain jakarta.persistence.JoinTable association table}: + * applied to the columns of an association table: *

  * @ManyToMany
  * @JoinTable(name = "collaborations")

From 8bbb5dce2c435b2acf86ace1da9ac1cb9f0dc394 Mon Sep 17 00:00:00 2001
From: Jan Schatteman 
Date: Mon, 7 Jul 2025 14:33:07 +0200
Subject: [PATCH 3/3] HHH-19555 - disable test because Sybase doesn't offer
 support for upserts

Signed-off-by: Jan Schatteman 
---
 .../jointable/ManyToOneImplicitJoinTableRestrictionTest.java   | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableRestrictionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableRestrictionTest.java
index a692265f3829..d1daab59b9e8 100644
--- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableRestrictionTest.java
+++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableRestrictionTest.java
@@ -9,9 +9,11 @@
 import jakarta.persistence.JoinTable;
 import jakarta.persistence.ManyToOne;
 import org.hibernate.annotations.SQLRestriction;
+import org.hibernate.dialect.SybaseDialect;
 import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
 import org.hibernate.testing.orm.junit.JiraKey;
 import org.hibernate.testing.orm.junit.Jpa;
+import org.hibernate.testing.orm.junit.SkipForDialect;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -21,6 +23,7 @@
 @Jpa(annotatedClasses =
 		{ManyToOneImplicitJoinTableRestrictionTest.X.class,
 		ManyToOneImplicitJoinTableRestrictionTest.Y.class})
+@SkipForDialect(dialectClass = SybaseDialect.class, matchSubTypes = true, reason = "Sybase doesn't have support for upserts")
 class ManyToOneImplicitJoinTableRestrictionTest {
 	@JiraKey("HHH-19555") @Test
 	void test(EntityManagerFactoryScope scope) {