diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/sessions/UnitOfWorkImpl.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/sessions/UnitOfWorkImpl.java index 8fed6ff5efe..8f642d48831 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/sessions/UnitOfWorkImpl.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/sessions/UnitOfWorkImpl.java @@ -1074,6 +1074,9 @@ public Object cloneAndRegisterObject(Object original, CacheKey parentCacheKey, C } } try { + if (isConsideredInvalid(original, parentCacheKey, descriptor) && unitOfWorkCacheKey.getObject() != null) { + original = unitOfWorkCacheKey.getObject(); + } // bug:6167576 Must acquire the lock before cloning. workingClone = builder.instantiateWorkingCopyClone(original, this); // PERF: Cache the primary key if implements PersistenceEntity. diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/java/org/eclipse/persistence/testing/models/jpa21/advanced/weave/IsolatedEntity.java b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/java/org/eclipse/persistence/testing/models/jpa21/advanced/weave/IsolatedEntity.java new file mode 100644 index 00000000000..a06961f9b86 --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/java/org/eclipse/persistence/testing/models/jpa21/advanced/weave/IsolatedEntity.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. 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.testing.models.jpa21.advanced.weave; + +import java.util.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +@Entity +@Table(name="JPA21_ISOLATED_ENTITY") +public class IsolatedEntity { + + @Id + protected String id; + + @Version + protected Integer version; + + public IsolatedEntity() { + super(); + } + + public IsolatedEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + IsolatedEntity that = (IsolatedEntity) o; + return Objects.equals(id, that.id) && Objects.equals(version, that.version); + } + + @Override + public int hashCode() { + return Objects.hash(id, version); + } +} diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/java/org/eclipse/persistence/testing/models/jpa21/advanced/weave/Location.java b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/java/org/eclipse/persistence/testing/models/jpa21/advanced/weave/Location.java new file mode 100644 index 00000000000..f913e3dfd2f --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/java/org/eclipse/persistence/testing/models/jpa21/advanced/weave/Location.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. 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.testing.models.jpa21.advanced.weave; + +import java.util.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +@Entity +@Table(name="JPA21_LOCATION") +public class Location { + + @Id + protected Long id; + + protected String locationId; + + @ManyToOne(fetch = FetchType.LAZY) + protected Node node; + + @Version + protected Integer version; + + public Location(long id, String locationId) { + this.id = id; + this.locationId = locationId; + } + + protected Location() { + } + + public Long getId() { + return this.id; + } + + public String getLocationId() { + return this.locationId; + } + + public Node getNode() { + return node; + } + + public void setNode(Node node) { + this.node = node; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Location location = (Location) o; + return Objects.equals(id, location.id) && Objects.equals(locationId, location.locationId) && Objects.equals(node, location.node) && Objects.equals(version, location.version); + } + + @Override + public int hashCode() { + return Objects.hash(id, locationId, node, version); + } +} \ No newline at end of file diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/java/org/eclipse/persistence/testing/models/jpa21/advanced/weave/Node.java b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/java/org/eclipse/persistence/testing/models/jpa21/advanced/weave/Node.java new file mode 100644 index 00000000000..8107349db36 --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/java/org/eclipse/persistence/testing/models/jpa21/advanced/weave/Node.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. 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.testing.models.jpa21.advanced.weave; + +import java.util.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +@Entity +@Table(name="JPA21_NODE") +public class Node { + + @Id + protected String id; + + protected int availableBufferCapacity = 10; + + @Version + protected Integer version; + + public Node(String id) { + this.id = id; + } + + protected Node() { + } + + public String getId() { + return id; + } + + public int getAvailableBufferCapacity() { + return availableBufferCapacity; + } + + public void reserveBufferCapacity() { + availableBufferCapacity--; + } + + public Integer getVersion() { + return version; + } + +/* + public void setVersion(Integer version) { + this.version = version; + } +*/ + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Node node = (Node) o; + return availableBufferCapacity == node.availableBufferCapacity && Objects.equals(id, node.id) && Objects.equals(version, node.version); + } + + @Override + public int hashCode() { + return Objects.hash(id, availableBufferCapacity, version); + } +} \ No newline at end of file diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/java/org/eclipse/persistence/testing/models/jpa21/advanced/weave/Order.java b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/java/org/eclipse/persistence/testing/models/jpa21/advanced/weave/Order.java new file mode 100644 index 00000000000..10bfd86e401 --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/java/org/eclipse/persistence/testing/models/jpa21/advanced/weave/Order.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. 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.testing.models.jpa21.advanced.weave; + +import java.util.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +@Entity +@Table(name="JPA21_ORDER_1") +public class Order { + + @Id + protected Long id; + + @ManyToOne(fetch = FetchType.LAZY) + protected Node node; + + @Version + protected Integer version; + + protected Order() { + } + + public Order(long id) { + + this.id = id; + } + + public Long getId() { + return id; + } + + public Node getNode() { + return node; + } + + public void setNode(Node destinationNode) { + this.node = destinationNode; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Order order = (Order) o; + return Objects.equals(id, order.id) && Objects.equals(node, order.node) && Objects.equals(version, order.version); + } + + @Override + public int hashCode() { + return Objects.hash(id, node, version); + } +} \ No newline at end of file diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/resources/META-INF/persistence.xml b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/resources/META-INF/persistence.xml index b6040dd857a..e1c69c83816 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/resources/META-INF/persistence.xml +++ b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/main/resources/META-INF/persistence.xml @@ -210,4 +210,17 @@ + + org.eclipse.persistence.jpa.PersistenceProvider + org.eclipse.persistence.testing.models.jpa21.advanced.weave.IsolatedEntity + org.eclipse.persistence.testing.models.jpa21.advanced.weave.Location + org.eclipse.persistence.testing.models.jpa21.advanced.weave.Node + org.eclipse.persistence.testing.models.jpa21.advanced.weave.Order + true + DISABLE_SELECTIVE + + + + + diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/test/java/org/eclipse/persistence/testing/tests/advanced2/weave/WeaveVersionTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/test/java/org/eclipse/persistence/testing/tests/advanced2/weave/WeaveVersionTest.java new file mode 100644 index 00000000000..5958158a296 --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.advanced2/src/test/java/org/eclipse/persistence/testing/tests/advanced2/weave/WeaveVersionTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. 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.testing.tests.advanced2.weave; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.eclipse.persistence.testing.framework.jpa.junit.JUnitTestCase; +import org.eclipse.persistence.testing.models.jpa21.advanced.weave.IsolatedEntity; +import org.eclipse.persistence.testing.models.jpa21.advanced.weave.Location; +import org.eclipse.persistence.testing.models.jpa21.advanced.weave.Node; +import org.eclipse.persistence.testing.models.jpa21.advanced.weave.Order; + +public class WeaveVersionTest extends JUnitTestCase { + + private static final String NODE_01 = "NODE_01"; + private static final String LOCATION_01 = "LOCATION_01"; + private static final String PROCESS_01 = "PROCESS_01"; + + public WeaveVersionTest() { + } + + public WeaveVersionTest(String name) { + super(name); + setPuName(getPersistenceUnitName()); + } + + @Override + public String getPersistenceUnitName() { + return "pu-with-dynamic-weaving"; + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.setName("WeaveVersionTest"); + suite.addTest(new WeaveVersionTest("testWeavedEntitiesWithVersionL2Cache")); + return suite; + } + + @Override + public void setUp() { + EntityManager em = createEntityManager(); + beginTransaction(em); + try { + Node node = new Node(NODE_01); + em.persist(node); + Location destinationLocation = new Location(1L, LOCATION_01); + destinationLocation.setNode(node); + em.persist(destinationLocation); + commitTransaction(em); + } finally { + if (this.isTransactionActive(em)) { + rollbackTransaction(em); + } + closeEntityManager(em); + } + } + + //Sequence of steps which triggered org.eclipse.persistence.exceptions.OptimisticLockException before related fix was applied. + //Required conditions are: JPA L2 cache enabled, Weaving enabled and @Version field annotation is used + public void testWeavedEntitiesWithVersionL2Cache() { + EntityManagerFactory emf = getEntityManagerFactory(); + cleanup(); + emf.getCache().evictAll(); + + //Step 01 + { + final long toKey = 1L; + + EntityManager em = createEntityManager(); + beginTransaction(em); + try { + Order order = new Order(toKey); + em.persist(order); + em.flush(); + Location location = em + .createQuery("SELECT l FROM Location l WHERE l.locationId=:locationId", Location.class) + .setParameter("locationId", LOCATION_01) + .getSingleResult(); + order.setNode(location.getNode()); + commitTransaction(em); + } finally { + if (this.isTransactionActive(em)) { + rollbackTransaction(em); + } + closeEntityManager(em); + } + } + + //Step 02 + { + final long toKey = 1L; + + EntityManager em = createEntityManager(); + beginTransaction(em); + try { + IsolatedEntity serialProcess = new IsolatedEntity(PROCESS_01); + em.persist(serialProcess); + em.flush(); + Order order = em.find(Order.class, toKey); + order.getNode().reserveBufferCapacity(); + em.merge(order); + em.merge(order.getNode()); + commitTransaction(em); + } finally { + if (this.isTransactionActive(em)) { + rollbackTransaction(em); + } + closeEntityManager(em); + } + + } + cleanup(); + emf.getCache().evictAll(); + + //Step 03 + { + final long toKey = 2L; + + EntityManager em = createEntityManager(); + beginTransaction(em); + try { + Order order = new Order(toKey); + em.persist(order); + em.flush(); + Location destinationLocation = em + .createQuery("SELECT l FROM Location l WHERE l.locationId=:locationId", Location.class) + .setParameter("locationId", LOCATION_01) + .getSingleResult(); + order.setNode(destinationLocation.getNode()); + commitTransaction(em); + } finally { + if (this.isTransactionActive(em)) { + rollbackTransaction(em); + } + closeEntityManager(em); + } + } + + //Step 04 + { + final long toKey = 2L; + + EntityManager em = createEntityManager(); + beginTransaction(em); + try { + IsolatedEntity serialProcess = new IsolatedEntity(PROCESS_01); + em.persist(serialProcess); + em.flush(); + Order order = em.find(Order.class, toKey); + order.getNode().reserveBufferCapacity(); + em.merge(order); + em.merge(order.getNode()); + } finally { + if (this.isTransactionActive(em)) { + rollbackTransaction(em); + } + closeEntityManager(em); + } + } + } + + public void cleanup() { + EntityManager em = createEntityManager(); + em.getTransaction().begin(); + try { + em.createQuery("delete from IsolatedEntity s").executeUpdate(); + em.createQuery("delete from Order t").executeUpdate(); + em.createQuery("UPDATE Node tn SET tn.availableBufferCapacity =10").executeUpdate(); + commitTransaction(em); + } finally { + if (this.isTransactionActive(em)) { + rollbackTransaction(em); + } + closeEntityManager(em); + } + } + + @Override + public void tearDown() { + EntityManager em = createEntityManager(); + em.getTransaction().begin(); + try { + em.createQuery("delete from Location l").executeUpdate(); + em.createQuery("delete from Order t").executeUpdate(); + em.createQuery("delete from Node t").executeUpdate(); + em.createQuery("delete from IsolatedEntity s").executeUpdate(); + commitTransaction(em); + } finally { + if (this.isTransactionActive(em)) { + rollbackTransaction(em); + } + closeEntityManager(em); + } + } +}