creating test case
This commit is contained in:
parent
97c300a6ad
commit
9f6116bc83
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue