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] 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; + } + } }