From 9f6116bc83230d34f9bc9827ab3692559a2d27b0 Mon Sep 17 00:00:00 2001 From: Fabio Souza Date: Tue, 22 Oct 2019 16:46:10 -0700 Subject: [PATCH] creating test case --- .../java/org/hibernate/jpa/test/lock/App.java | 55 +++++++++++ .../hibernate/jpa/test/lock/Installation.java | 74 +++++++++++++++ .../lock/LeftJoinOptimisticLockingTest.java | 95 +++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/lock/App.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Installation.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LeftJoinOptimisticLockingTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/App.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/App.java new file mode 100644 index 0000000000..db1ec9bbd0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/App.java @@ -0,0 +1,55 @@ +package org.hibernate.jpa.test.lock; + +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Version; + +@Entity +public class App { + + @Id + private UUID uuid; + + @Version + @Column(name = "opt_lock", nullable = false) + private long optLock = 0; + + @Column(name = "name") + private String name; + + + public UUID getUuid() { + return uuid; + } + + public App setUuid(final UUID uuid) { + this.uuid = uuid; + return this; + } + + public String getName() { + return name; + } + + public App setName(final String name) { + this.name = name; + return this; + } + + public long getOptLock() { + return optLock; + } + + @Override + public String toString() { + return "App{" + + "uuid=" + uuid + + ", optLock=" + optLock + + ", name='" + name + '\'' + + '}'; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Installation.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Installation.java new file mode 100644 index 0000000000..b7cf3ce2e6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/Installation.java @@ -0,0 +1,74 @@ +package org.hibernate.jpa.test.lock; + +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Version; + +@Entity +public class Installation { + + @Id + @Column(name = "installation_uuid") + private UUID uuid; + + @Version + @Column(name = "opt_lock", nullable = false) + private long optLock = 0; + + @ManyToOne(fetch = FetchType.LAZY, optional = false, targetEntity = App.class) + @JoinColumn(name = "app_uuid", foreignKey = @ForeignKey(name = "installation_app_fk"), nullable = false) + private App app; + + @Column(name = "details") + private String details; + + public App getApp() { + return app; + } + + public Installation setApp(final App app) { + this.app = app; + return this; + } + + public String getDetails() { + return details; + } + + public Installation setDetails(final String details) { + this.details = details; + return this; + } + + public UUID getUuid() { + return uuid; + } + + public Installation setUuid(final UUID uuid) { + this.uuid = uuid; + return this; + } + + public long getOptLock() { + return optLock; + } + + + @Override + public String toString() { + return "Installation{" + + "uuid=" + uuid + + ", optLock=" + optLock + + ", app=" + app + + ", details='" + details + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LeftJoinOptimisticLockingTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LeftJoinOptimisticLockingTest.java new file mode 100644 index 0000000000..518d3db56f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LeftJoinOptimisticLockingTest.java @@ -0,0 +1,95 @@ +package org.hibernate.jpa.test.lock; + +import javax.persistence.Tuple; + +import java.util.UUID; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.query.NativeQuery; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Test; +import org.jboss.logging.Logger; + +/** + * This test demonstrates how a native query with LEFT join mixes up the version column when they have the same name + * in the involved tables. + * @author Fabio Souza + */ +public class LeftJoinOptimisticLockingTest extends BaseEntityManagerFunctionalTestCase { + + private static final Logger LOGGER = Logger.getLogger( LeftJoinOptimisticLockingTest.class ); + + @Test + public void test() { + // Creating an app + final App app = upsert(new App().setUuid(UUID.randomUUID()).setName("test")); + + // Creating installation + final Installation installationBefore = upsert( + new Installation() + .setUuid(UUID.randomUUID()) + .setDetails("details") + .setApp(app)); + + + // Creating an installation of that app and obtaining the opt_lock + final long installOptLockBefore = installationBefore.getOptLock(); + + // Bumping app "version" so it has a different value then the installation + upsert(app.setName("changed")); + // Bumping again so the difference is very obvious + upsert(app.setName("changed2")); + + TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + // Executing the left join query (app + installation) + final NativeQuery nativeQuery = (NativeQuery) entityManager.createNativeQuery("select a.*, inst.* from App a " + + "LEFT JOIN (select i.* from installation i) as inst " + + "ON a.uuid = inst.app_uuid", Tuple.class); + + nativeQuery.addEntity("a", App.class); + nativeQuery.addEntity("inst", Installation.class); + nativeQuery.getResultList(); + + // Finding the installation (no join). This should not cause any changes in the opt_lock + final Installation installationAfter = entityManager.find(Installation.class, installationBefore.getUuid()); + final long installationOptLockAfter = installationAfter.getOptLock(); + + // There were no changes so far, therefore, the opt_lock should be the same + LOGGER.info("Installation opt lock before: " + installOptLockBefore); + LOGGER.info("Installation opt lock after: " + installationOptLockAfter); + if (installationBefore != installationAfter) { + LOGGER.error("Installation opt lock obtained is different from after executing the LEFT join"); + } + + + // Changing and saving the installation + // Failing means that the left join query mixed up the opt_lock columns + entityManager.persist(installationAfter.setDetails("change")); + + } ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { App.class, Installation.class }; + } + + private App upsert(final App app) { + return TransactionUtil.doInJPA( this::entityManagerFactory, em -> { + final App existingApp = em.find(App.class, app.getUuid()); + final App mergedApp = existingApp != null ? existingApp.setName(app.getName()) : app; + em.persist(mergedApp); + return mergedApp; + } ); + } + + private Installation upsert(final Installation installation) { + return TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { + final Installation existingEntity = entityManager.find(Installation.class, installation.getUuid()); + final Installation mergedEntity = existingEntity != null ? existingEntity.setDetails(installation.getDetails()) : installation; + entityManager.persist(mergedEntity); + return mergedEntity; + } ); + } + +}