Skip to content

Commit

Permalink
Bug 578262: Bulk update queries reuse the same version locking value
Browse files Browse the repository at this point in the history
Signed-off-by: Will Dazey <dazeydev.3@gmail.com>
  • Loading branch information
dazey3 committed Jan 27, 2022
1 parent 1bef069 commit 5f242dd
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand Down Expand Up @@ -206,7 +207,7 @@ public <T> T getWriteLockValue(Object domainObject, Object primaryKey, AbstractS
*/
@Override
public Expression getWriteLockUpdateExpression(ExpressionBuilder builder, AbstractSession session) {
return builder.value(getInitialWriteValue(session));
return builder.currentTimeStamp();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2015, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015 IBM Corporation. All rights reserved.
* Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand All @@ -18,6 +18,9 @@

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Query;

import java.sql.Timestamp;

import org.eclipse.persistence.descriptors.TimestampLockingPolicy;
import org.eclipse.persistence.internal.jpa.EntityManagerImpl;
Expand All @@ -26,6 +29,7 @@
import org.eclipse.persistence.jpa.test.framework.EmfRunner;
import org.eclipse.persistence.jpa.test.framework.Property;
import org.eclipse.persistence.jpa.test.locking.model.ClassDescriptorCustomizer;
import org.eclipse.persistence.jpa.test.locking.model.EcallRegistration;
import org.eclipse.persistence.jpa.test.locking.model.TimestampDog;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.sessions.Session;
Expand Down Expand Up @@ -59,6 +63,9 @@ public class TestTimestampVersionLocking {
@Property(name = "eclipselink.descriptor.customizer.TimestampDog", value = "org.eclipse.persistence.jpa.test.locking.model.ClassDescriptorCustomizer") })
private EntityManagerFactory emfFalseCustomized;

@Emf(name = "emf2", createTables = DDLGen.DROP_CREATE, classes = { EcallRegistration.class })
private EntityManagerFactory emf2;

/**
* Check that setting the property "true" will get the local system time
* instead of the default behavior of contacting the server.
Expand Down Expand Up @@ -203,6 +210,200 @@ public void testLocalTimestampPropertyFalseCustomizer() throws Exception {
}
}

/**
* Test bulk update queries do not reuse the same timestamp value across multiple executions
*/
@Test
public void testUpdateAllQueryWithTimestampLocking() {

int flag1 = 0;
int flag2 = 1;
String pk1 = "11004";
String pk2 = "11005";

// Populate entities with initial timestamp version values
EntityManager em = emf2.createEntityManager();
try {
em.getTransaction().begin();

EcallRegistration e1 = new EcallRegistration(pk1, flag1);
EcallRegistration e2 = new EcallRegistration(pk2, flag2);

em.persist(e1);
em.persist(e2);

em.getTransaction().commit();
} finally {
if(em.isOpen()) {
em.clear();
em.close();
}
}

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}

/*
* Update flag1 value via bulk Update query.
* This should update the version locking timestamp value for entity1.
*/
em = emf2.createEntityManager();
try {
flag1 = flag1++;

em.getTransaction().begin();

Query query = em.createNamedQuery("updateActiveEcallAvailableFlag", EcallRegistration.class);
query.setParameter("flag", flag1);
query.setParameter("pk", pk1);

query.executeUpdate();

em.getTransaction().commit();
} finally {
if(em.isOpen()) {
em.clear();
em.close();
}
}

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}

/*
* Update flag2 value via bulk Update query.
* This should update the version locking timestamp value for entity2.
*/
em = emf2.createEntityManager();
try {
flag2 = flag2++;

em.getTransaction().begin();

Query query = em.createNamedQuery("updateActiveEcallAvailableFlag", EcallRegistration.class);
query.setParameter("flag", flag2);
query.setParameter("pk", pk2);

query.executeUpdate();

em.getTransaction().commit();
} finally {
if(em.isOpen()) {
em.clear();
em.close();
}
}

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}

/*
* Validate that even though both bulk updates used the same UpdateAllQuery, both entities have
* different version locking timestamps in the database.
*/
em = emf2.createEntityManager();
try {
em.getTransaction().begin();

EcallRegistration e1 = em.find(EcallRegistration.class, pk1);
EcallRegistration e2 = em.find(EcallRegistration.class, pk2);

Assert.assertNotNull(e1);
Assert.assertNotNull(e2);

Assert.assertNotEquals(e1.getSysUpdateTimestamp(), e2.getSysUpdateTimestamp());
Assert.assertTrue("Expected entity2.sysUpdateTimestamp [" + e2.getSysUpdateTimestamp() + "] "
+ "to be after entity1.sysUpdateTimestamp [" + e1.getSysUpdateTimestamp() + "]",
e2.getSysUpdateTimestamp().after(e1.getSysUpdateTimestamp()));

em.getTransaction().commit();
} finally {
if(em.isOpen()) {
em.clear();
em.close();
}
}
}

/**
* Test that bulk update queries do not update the managed entities version
* values as documented in the specification
*
* JPA Spec section 4.10: Bulk Update and Delete Operations
*
* Bulk update maps directly to a database update operation, bypassing optimistic locking checks.
* Portable applications must manually update the value of the version column, if desired, and/or
* manually validate the value of the version column.
*/
@Test
public void testTimestampLockingUpdateWithUpdateAllQuery() {

int flag1 = 0;
String pk1 = "11006";

EntityManager em = emf2.createEntityManager();
try {
// Populate an entity
em.getTransaction().begin();

EcallRegistration persist1 = new EcallRegistration(pk1, flag1);

em.persist(persist1);

em.getTransaction().commit();

// Find managed instance, record current timestamp version value
em.getTransaction().begin();

EcallRegistration find1 = em.find(EcallRegistration.class, pk1);
Assert.assertNotNull(find1);

Timestamp ver1 = find1.getSysUpdateTimestamp();
Assert.assertNotNull(ver1);

em.getTransaction().commit();

// Execute UpdateAllQuery to update version locking field in db
flag1 = flag1++;

em.getTransaction().begin();

Query query = em.createNamedQuery("updateActiveEcallAvailableFlag", EcallRegistration.class);
query.setParameter("flag", flag1);
query.setParameter("pk", pk1);

query.executeUpdate();

em.getTransaction().commit();

// Verify that the UpdateAllQuery didn't update the managed instance
em.getTransaction().begin();

EcallRegistration find2 = em.find(EcallRegistration.class, pk1);
Assert.assertNotNull(find2);

Timestamp ver2 = find2.getSysUpdateTimestamp();
Assert.assertNotNull(ver2);
Assert.assertEquals(ver1, ver2);

em.getTransaction().commit();
} finally {
if(em.isOpen()) {
em.clear();
em.close();
}
}
}

/**
* Add this to Session Event Manager to listen for query executions. This
* class will listen for the execution of a specified query and track it.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
package org.eclipse.persistence.jpa.test.locking.model;

import java.sql.Timestamp;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Version;

@Entity
@NamedQuery(name="updateActiveEcallAvailableFlag", query="UPDATE EcallRegistration e SET e.ecallAvailableFlag = :flag WHERE e.pk = :pk")
public class EcallRegistration {

@Id private String pk;

private int ecallAvailableFlag;

@Version @Column(name = "sys_update_timestamp")
private Timestamp sysUpdateTimestamp;

public EcallRegistration() { }

public EcallRegistration(String pk, int ecallAvailableFlag) {
this.pk = pk;
this.ecallAvailableFlag = ecallAvailableFlag;
}

public String getPk() {
return pk;
}

public void setPk(String pk) {
this.pk = pk;
}

public int getEcallAvailableFlag() {
return ecallAvailableFlag;
}

public void setEcallAvailableFlag(int ecallAvailableFlag) {
this.ecallAvailableFlag = ecallAvailableFlag;
}

public Timestamp getSysUpdateTimestamp() {
return this.sysUpdateTimestamp;
}

public void setSysUpdateTimestamp(Timestamp sysUpdateTimestamp) {
this.sysUpdateTimestamp = sysUpdateTimestamp;
}
}

0 comments on commit 5f242dd

Please sign in to comment.