From c578479557b69090ebf6924b6eeda6ba396e9d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 7 Feb 2023 11:29:45 +0100 Subject: [PATCH 1/2] Upgrade to Hibernate ORM 5.6.15.Final Fixes #30234 --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 4f9252bbf2f6f..3c8ac034323cb 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -97,7 +97,7 @@ 3.12.0 1.15 1.5.1 - 5.6.14.Final + 5.6.15.Final 1.12.18 1.1.9.Final 8.0.0.Final From b189ead53c15f15f5408fcb97121c134782227c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 16 Jan 2023 13:34:38 +0100 Subject: [PATCH 2/2] Test updates of @Basic attributes with Hibernate ORM Reproducer for https://hibernate.atlassian.net/browse/HHH-16049 --- .../lazyloading/AbstractLazyBasicTest.java | 193 +++++++++++++++--- 1 file changed, 168 insertions(+), 25 deletions(-) diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/AbstractLazyBasicTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/AbstractLazyBasicTest.java index 0efe1e06bdc0d..5e5db8f8c5acc 100644 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/AbstractLazyBasicTest.java +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/lazyloading/AbstractLazyBasicTest.java @@ -1,13 +1,21 @@ package io.quarkus.hibernate.orm.lazyloading; import static io.quarkus.hibernate.orm.TransactionTestUtils.inTransaction; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.transaction.UserTransaction; +import org.hibernate.resource.jdbc.spi.StatementInspector; import org.junit.jupiter.api.Test; +import io.quarkus.hibernate.orm.PersistenceUnitExtension; + public abstract class AbstractLazyBasicTest { @Inject @@ -24,11 +32,61 @@ public AbstractLazyBasicTest(AccessDelegate delegate) { } @Test - public void update_all_nullToNonNull() { + public void update_all_nullToNull() { initNull(); + // Updating lazy properties always results in updates, even if the value didn't change, + // because we don't know of their previous value. + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { + delegate.updateAllProperties(em, entityId, null, null, null); + })); inTransaction(transaction, () -> { - delegate.updateAllProperties(em, entityId, "updated1", "updated2", "updated3"); + delegate.testLazyLoadingAndPersistedValues(em, entityId, null, null, null); + }); + } + + @Test + public void update_allLazy_nullToNull() { + initNull(); + // Updating lazy properties always results in updates, even if the value didn't change, + // because we don't know of their previous value. + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { + delegate.updateAllLazyProperties(em, entityId, null, null); + })); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, null, null, null); }); + } + + @Test + public void update_oneEager_nullToNull() { + initNull(); + StatementSpy.checkNoUpdate(() -> inTransaction(transaction, () -> { + delegate.updateOneEagerProperty(em, entityId, null); + })); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, null, null, null); + }); + } + + @Test + public void update_oneLazy_nullToNull() { + initNull(); + // Updating lazy properties always results in updates, even if the value didn't change, + // because we don't know of their previous value. + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { + delegate.updateOneLazyProperty(em, entityId, null); + })); + inTransaction(transaction, () -> { + delegate.testLazyLoadingAndPersistedValues(em, entityId, null, null, null); + }); + } + + @Test + public void update_all_nullToNonNull() { + initNull(); + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { + delegate.updateAllProperties(em, entityId, "updated1", "updated2", "updated3"); + })); inTransaction(transaction, () -> { delegate.testLazyLoadingAndPersistedValues(em, entityId, "updated1", "updated2", "updated3"); }); @@ -37,9 +95,9 @@ public void update_all_nullToNonNull() { @Test public void update_allLazy_nullToNonNull() { initNull(); - inTransaction(transaction, () -> { + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { delegate.updateAllLazyProperties(em, entityId, "updated1", "updated2"); - }); + })); inTransaction(transaction, () -> { delegate.testLazyLoadingAndPersistedValues(em, entityId, null, "updated1", "updated2"); }); @@ -48,9 +106,9 @@ public void update_allLazy_nullToNonNull() { @Test public void update_oneEager_nullToNonNull() { initNull(); - inTransaction(transaction, () -> { + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { delegate.updateOneEagerProperty(em, entityId, "updated1"); - }); + })); inTransaction(transaction, () -> { delegate.testLazyLoadingAndPersistedValues(em, entityId, "updated1", null, null); }); @@ -59,64 +117,112 @@ public void update_oneEager_nullToNonNull() { @Test public void update_oneLazy_nullToNonNull() { initNull(); - inTransaction(transaction, () -> { + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { delegate.updateOneLazyProperty(em, entityId, "updated2"); - }); + })); inTransaction(transaction, () -> { delegate.testLazyLoadingAndPersistedValues(em, entityId, null, "updated2", null); }); } @Test - public void update_all_nonNullToNonNull() { + public void update_all_nonNullToNonNull_differentValue() { initNonNull(); - inTransaction(transaction, () -> { + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { delegate.updateAllProperties(em, entityId, "updated1", "updated2", "updated3"); - }); + })); inTransaction(transaction, () -> { delegate.testLazyLoadingAndPersistedValues(em, entityId, "updated1", "updated2", "updated3"); }); } @Test - public void update_allLazy_nonNullToNonNull() { + public void update_all_nonNullToNonNull_sameValue() { initNonNull(); + // Updating lazy properties always results in updates, even if the value didn't change, + // because we don't know of their previous value. + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { + delegate.updateAllProperties(em, entityId, "initial1", "initial2", "initial3"); + })); inTransaction(transaction, () -> { - delegate.updateAllLazyProperties(em, entityId, "updated1", "updated2"); + delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", "initial2", "initial3"); }); + } + + @Test + public void update_allLazy_nonNullToNonNull_differentValue() { + initNonNull(); + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { + delegate.updateAllLazyProperties(em, entityId, "updated1", "updated2"); + })); inTransaction(transaction, () -> { delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", "updated1", "updated2"); }); } @Test - public void update_oneEager_nonNullToNonNull() { + public void update_allLazy_nonNullToNonNull_sameValue() { initNonNull(); + // Updating lazy properties always results in updates, even if the value didn't change, + // because we don't know of their previous value. + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { + delegate.updateAllLazyProperties(em, entityId, "initial2", "initial3"); + })); inTransaction(transaction, () -> { - delegate.updateOneEagerProperty(em, entityId, "updated1"); + delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", "initial2", "initial3"); }); + } + + @Test + public void update_oneEager_nonNullToNonNull_differentValue() { + initNonNull(); + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { + delegate.updateOneEagerProperty(em, entityId, "updated1"); + })); inTransaction(transaction, () -> { delegate.testLazyLoadingAndPersistedValues(em, entityId, "updated1", "initial2", "initial3"); }); } @Test - public void update_oneLazy_nonNullToNonNull() { + public void update_oneEager_nonNullToNonNull_sameValue() { initNonNull(); + StatementSpy.checkNoUpdate(() -> inTransaction(transaction, () -> { + delegate.updateOneEagerProperty(em, entityId, "initial1"); + })); inTransaction(transaction, () -> { - delegate.updateOneLazyProperty(em, entityId, "updated2"); + delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", "initial2", "initial3"); }); + } + + @Test + public void update_oneLazy_nonNullToNonNull_differentValue() { + initNonNull(); + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { + delegate.updateOneLazyProperty(em, entityId, "updated2"); + })); inTransaction(transaction, () -> { delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", "updated2", "initial3"); }); } @Test - public void update_all_nonNullToNull() { + public void update_oneLazy_nonNullToNonNull_sameValue() { initNonNull(); + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { + delegate.updateOneLazyProperty(em, entityId, "initial2"); + })); inTransaction(transaction, () -> { - delegate.updateAllProperties(em, entityId, null, null, null); + delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", "initial2", "initial3"); }); + } + + @Test + public void update_all_nonNullToNull() { + initNonNull(); + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { + delegate.updateAllProperties(em, entityId, null, null, null); + })); inTransaction(transaction, () -> { delegate.testLazyLoadingAndPersistedValues(em, entityId, null, null, null); }); @@ -125,9 +231,9 @@ public void update_all_nonNullToNull() { @Test public void update_allLazy_nonNullToNull() { initNonNull(); - inTransaction(transaction, () -> { + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { delegate.updateAllLazyProperties(em, entityId, null, null); - }); + })); inTransaction(transaction, () -> { delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", null, null); }); @@ -136,9 +242,9 @@ public void update_allLazy_nonNullToNull() { @Test public void update_oneEager_nonNullToNull() { initNonNull(); - inTransaction(transaction, () -> { + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { delegate.updateOneEagerProperty(em, entityId, null); - }); + })); inTransaction(transaction, () -> { delegate.testLazyLoadingAndPersistedValues(em, entityId, null, "initial2", "initial3"); }); @@ -147,9 +253,9 @@ public void update_oneEager_nonNullToNull() { @Test public void update_oneLazy_nonNullToNull() { initNonNull(); - inTransaction(transaction, () -> { + StatementSpy.checkAtLeastOneUpdate(() -> inTransaction(transaction, () -> { delegate.updateOneLazyProperty(em, entityId, null); - }); + })); inTransaction(transaction, () -> { delegate.testLazyLoadingAndPersistedValues(em, entityId, "initial1", null, "initial3"); }); @@ -197,4 +303,41 @@ void testLazyLoadingAndPersistedValues(EntityManager entityManager, long entityI String expectedLazyProperty1, String expectedLazyProperty2); } + + @PersistenceUnitExtension + public static class StatementSpy implements StatementInspector { + private static final ThreadLocal> statements = new ThreadLocal<>(); + + public static void checkAtLeastOneUpdate(Runnable runnable) { + check(runnable, list -> assertThat(list) + .isNotEmpty() // Something is wrong if we didn't even load an entity + .anySatisfy(sql -> assertThat(sql).containsIgnoringCase("update"))); + } + + public static void checkNoUpdate(Runnable runnable) { + check(runnable, list -> assertThat(list) + .isNotEmpty() // Something is wrong if we didn't even load an entity + .allSatisfy(sql -> assertThat(sql).doesNotContainIgnoringCase("update"))); + } + + public static void check(Runnable runnable, Consumer> assertion) { + List list = new ArrayList<>(); + if (statements.get() != null) { + throw new IllegalStateException("Cannot nest checkNoUpdate()"); + } + statements.set(list); + runnable.run(); + statements.remove(); + assertion.accept(list); + } + + @Override + public String inspect(String sql) { + List list = statements.get(); + if (list != null) { + list.add(sql); + } + return sql; + } + } }