From f76ccba98ce29ad2a14bcef91a2c8eadacc1a72c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kraus?= Date: Fri, 17 Jan 2025 16:53:57 +0100 Subject: [PATCH] Issue #2343 - EclipseLink disallows @Version attribute of type java.time.LocalDateTime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomáš Kraus --- .../descriptors/AbstractTsLockingPolicy.java | 347 ++++++++++++++++++ .../descriptors/InstantLockingPolicy.java | 192 ++++++++++ .../LocalDateTimeLockingPolicy.java | 191 ++++++++++ .../descriptors/TimestampLockingPolicy.java | 275 ++++---------- .../descriptors/VersionLockingPolicy.java | 86 +++-- .../persistence32/InstantVersionEntity.java | 97 +++++ .../jpa/persistence32/LdtVersionEntity.java | 96 +++++ .../persistence32/TimestampVersionEntity.java | 97 +++++ .../main/resources/META-INF/persistence.xml | 17 +- ...Pokemon.java => AbstractPokemonSuite.java} | 12 +- .../persistence32/AbstractVersionSuite.java | 89 +++++ .../jpa/persistence32/EntityGraphTest.java | 4 +- .../EntityManagerFactoryTest.java | 4 +- .../jpa/persistence32/EntityManagerTest.java | 4 +- .../tests/jpa/persistence32/QueryTest.java | 6 +- .../SchemaManagerCreateTest.java | 8 +- .../persistence32/SchemaManagerDropTest.java | 8 +- .../SchemaManagerTruncateOnExistingTest.java | 8 +- .../persistence32/UnionCriteriaQueryTest.java | 4 +- .../tests/jpa/persistence32/VersionTest.java | 207 +++++++++++ .../accessors/mappings/VersionAccessor.java | 17 +- 21 files changed, 1505 insertions(+), 264 deletions(-) create mode 100644 foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/AbstractTsLockingPolicy.java create mode 100644 foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/InstantLockingPolicy.java create mode 100644 foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/LocalDateTimeLockingPolicy.java create mode 100644 jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/InstantVersionEntity.java create mode 100644 jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/LdtVersionEntity.java create mode 100644 jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/TimestampVersionEntity.java rename jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/{AbstractPokemon.java => AbstractPokemonSuite.java} (89%) create mode 100644 jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractVersionSuite.java create mode 100644 jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/VersionTest.java diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/AbstractTsLockingPolicy.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/AbstractTsLockingPolicy.java new file mode 100644 index 00000000000..e26c0a64a36 --- /dev/null +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/AbstractTsLockingPolicy.java @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2025 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.descriptors; + +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.Map; + +import org.eclipse.persistence.expressions.Expression; +import org.eclipse.persistence.expressions.ExpressionBuilder; +import org.eclipse.persistence.internal.helper.DatabaseField; +import org.eclipse.persistence.internal.sessions.AbstractRecord; +import org.eclipse.persistence.internal.sessions.AbstractSession; +import org.eclipse.persistence.queries.ModifyQuery; + +/** + * Common timestamp version policy used for optimistic locking. + */ +public abstract class AbstractTsLockingPolicy extends VersionLockingPolicy { + + /** Time from the server. */ + public static final int SERVER_TIME = 1; + /** Local time. */ + public static final int LOCAL_TIME = 2; + + // Mapping of timestamp class name to corresponding AbstractTsLockingPolicy factory method + private static final Map FACTORY = Map.of( + Timestamp.class.getName(), AbstractTsLockingPolicy::createTimestampLockingPolicy, + LocalDateTime.class.getName(), AbstractTsLockingPolicy::createLocalDateTimeLockingPolicy, + Instant.class.getName(), AbstractTsLockingPolicy::createInstantLockingPolicy + ); + + /** + * AbstractTsLockingPolicy factory method. + * + * @param typeName raw {@code MetadataClass} name + * @param field version field + */ + public static AbstractTsLockingPolicy create(String typeName, DatabaseField field) { + LockingPolicySupplier factory = FACTORY.get(typeName); + if (factory == null) { + throw new UnsupportedOperationException(String.format("Cannot create AbstractTsLockingPolicy for %s", typeName)); + } + return factory.create(field); + } + + private int retrieveTimeFrom; + + /** + * Creates an instance of timestamp version policy used for optimistic locking. + * Defaults to using the time retrieved from the server. + */ + public AbstractTsLockingPolicy() { + super(); + this.useServerTime(); + } + + /** + * Creates an instance of timestamp version policy used for optimistic locking. + * Defaults to using the time retrieved from the server. + * + * @param fieldName the field where the write lock value will be stored + */ + public AbstractTsLockingPolicy(String fieldName) { + super(fieldName); + this.useServerTime(); + } + + /** + * Creates an instance of timestamp version policy used for optimistic locking. + * Defaults to using the time retrieved from the server. + * + * @param field the field where the write lock value will be stored + */ + public AbstractTsLockingPolicy(DatabaseField field) { + super(field); + this.useServerTime(); + } + + /* + * Following methods mapping removes unsafe casts in child classes. + */ + + /** + * This method compares two writeLockValues. + * The writeLockValues should be non-null and of {@link LocalDateTime}, + * {@link Instant} or {@link Timestamp} type. + * + * @param value1 the 1st value to compare + * @param value2 the 2nd value to compare + * @return {@code -1} if value1 is less (older) than value2, + * {@code 0} if value1 equals value2, + * {@code 1} if value1 is greater (newer) than value2. + * @throws NullPointerException if the passed value is null + * @throws ClassCastException if the passed value is of a wrong type. + */ + abstract int compareTsLockValues(T value1, T value2); + + @Override + @SuppressWarnings("unchecked") + public int compareWriteLockValues(Object value1, Object value2) { + return compareTsLockValues((T) value1, (T) value2); + } + + /** + * Return the default timestamp locking filed java type. + * + * @return the default timestamp locking filed java type + */ + abstract Class getDefaultTsLockFieldType(); + + @Override + @SuppressWarnings("unchecked") + protected Class getDefaultLockingFieldType() { + return (Class) getDefaultTsLockFieldType(); + } + + /** + * Return base value that is older than all other values, it is used in the place of + * {@code null} in some situations. + * + * @return timestamp base value + */ + abstract T getBaseTsValue(); + + @Override + @SuppressWarnings("unchecked") + public C getBaseValue() { + return (C) getBaseTsValue(); + } + + /** + * Return initial locking value. + * + * @param session the database session + * @return the initial locking value + */ + abstract T getInitialTsWriteValue(AbstractSession session); + + @Override + @SuppressWarnings("unchecked") + protected C getInitialWriteValue(AbstractSession session) { + return (C) getInitialTsWriteValue(session); + } + + /** + * Returns new write lock value from either the cache or the object stored in the query. + * + * @param query modify query + * @return the new timestamp value + */ + abstract T getNewTsLockValue(ModifyQuery query); + + @Override + @SuppressWarnings("unchecked") + public C getNewLockValue(ModifyQuery query) { + return (C) getNewTsLockValue(query); + } + + /** + * Return the value that should be stored in the identity map. + * If the value is stored in the object, then return a null. + * + * @param row the data row, e.g. database row + * @param session the database session + * @return the value that should be stored in the identity map + */ + abstract T getTsValueToPutInCache(AbstractRecord row, AbstractSession session); + + @Override + @SuppressWarnings("unchecked") + public C getValueToPutInCache(AbstractRecord row, AbstractSession session) { + return (C) getTsValueToPutInCache(row, session); + } + + /** + * Return the optimistic lock value for the object. + * + * @param domainObject the domain object (entity instance) + * @param primaryKey the primary key + * @param session the database session + * @return the optimistic lock value for the object + */ + abstract T getWriteTsLockValue(Object domainObject, Object primaryKey, AbstractSession session); + + @Override + @SuppressWarnings("unchecked") + public C getWriteLockValue(Object domainObject, Object primaryKey, AbstractSession session) { + return (C) getWriteTsLockValue(domainObject, primaryKey, session); + } + + /** + * Compares two version values from the current value and from the object (or cache). + * + * @param current the current value + * @param domainObject the domain object (entity instance) + * @param primaryKey the primary key + * @param session the database session + * @return value of {@code true} if the {@code first} is newer than the {@code second} + * or {@code false} otherwise + */ + abstract boolean isNewerTsVersion(T current, Object domainObject, Object primaryKey, AbstractSession session); + + /** + * INTERNAL: + * Compares the value with the value from the object (or cache). + * Will return true if the currentValue is newer than the domainObject. + */ + @Override + @SuppressWarnings("unchecked") + public boolean isNewerVersion(Object current, Object domainObject, Object primaryKey, AbstractSession session) { + return isNewerTsVersion((T) current, domainObject, primaryKey, session); + } + + /** + * Compares two version values from the row and from the object (or cache). + * + * @param row the data row, e.g. database row + * @param domainObject the domain object (entity instance) + * @param primaryKey the primary key + * @param session the database session + * @return value of {@code true} if the {@code first} is newer than the {@code second} + * or {@code false} otherwise + */ + abstract boolean isNewerTsVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session); + + @Override + public boolean isNewerVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session) { + return isNewerTsVersion(row, domainObject, primaryKey, session); + } + + /** + * Compares two version values. + * + * @param first first version value + * @param second second version value + * @return value of {@code true} if the {@code first} is newer than the {@code second} + * or {@code false} otherwise + */ + abstract boolean isNewerTsVersion(T first, T second) ; + + @Override + @SuppressWarnings("unchecked") + public boolean isNewerVersion(Object first, Object second) { + return isNewerTsVersion((T) first, (T) second); + } + + /** + * Return an expression that updates the write lock. + * + * @param builder the expression builder + * @param session the database session + */ + @Override + public Expression getWriteLockUpdateExpression(ExpressionBuilder builder, AbstractSession session) { + return builder.currentTimeStamp(); + } + + /** + * Timestamp versioning should not be able to do this. + * Override the superclass behavior. + * + * @param value the source value + */ + @Override + protected Number incrementWriteLockValue(Number value) { + return null; + } + + /** + * Set time source policy. + * + * @param useServer set this policy to get the time from the server when {@code true} + * or from the local machine when {@code false} + */ + public void setUsesServerTime(boolean useServer) { + if (useServer) { + useServerTime(); + } else { + useLocalTime(); + } + } + + /** + * Set time source policy to get the time from the local machine. + */ + public void useLocalTime() { + retrieveTimeFrom = LOCAL_TIME; + } + + /** + * Set time source policy to get the time from the server. + */ + public void useServerTime() { + retrieveTimeFrom = SERVER_TIME; + } + + /** + * Return whether time source policy uses local time. + * + * @return value of {@code true} when policy uses local time or {@code false} otherwise. + */ + public boolean usesLocalTime() { + return retrieveTimeFrom == LOCAL_TIME; + } + + /** + * Return whether time source policy uses server time. + * + * @return value of {@code true} when policy uses server time or {@code false} otherwise. + */ + public boolean usesServerTime() { + return retrieveTimeFrom == SERVER_TIME; + } + + // TimestampLockingPolicy factory method + private static TimestampLockingPolicy createTimestampLockingPolicy(DatabaseField field) { + return new TimestampLockingPolicy(field); + } + + // LocalDateTimeLockingPolicy factory method + private static LocalDateTimeLockingPolicy createLocalDateTimeLockingPolicy(DatabaseField field) { + return new LocalDateTimeLockingPolicy(field); + } + + // InstantLockingPolicy factory method + private static InstantLockingPolicy createInstantLockingPolicy(DatabaseField field) { + return new InstantLockingPolicy(field); + } + + // AbstractTsLockingPolicy factory interface + @FunctionalInterface + private interface LockingPolicySupplier { + AbstractTsLockingPolicy create(DatabaseField field); + } + +} diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/InstantLockingPolicy.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/InstantLockingPolicy.java new file mode 100644 index 00000000000..896a4146968 --- /dev/null +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/InstantLockingPolicy.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2025 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.descriptors; + +import java.sql.Timestamp; +import java.time.Instant; + +import org.eclipse.persistence.exceptions.OptimisticLockException; +import org.eclipse.persistence.internal.helper.ClassConstants; +import org.eclipse.persistence.internal.helper.DatabaseField; +import org.eclipse.persistence.internal.sessions.AbstractRecord; +import org.eclipse.persistence.internal.sessions.AbstractSession; +import org.eclipse.persistence.queries.ModifyQuery; + +/** + * Version policy used for optimistic locking with {@link Instant} field. + */ +public class InstantLockingPolicy extends AbstractTsLockingPolicy { + + /** + * Create a new instance of version policy used for optimistic locking + * with {@link Instant} field. + * Defaults to using the time retrieved from the server. + */ + public InstantLockingPolicy() { + super(); + } + + /** + * Create a new instance of version policy used for optimistic locking + * with {@link Instant} field. + * Defaults to using the time retrieved from the server. + * + * @param fieldName the field where the write lock value will be stored + */ + public InstantLockingPolicy(String fieldName) { + super(fieldName); + } + + /** + * Create a new instance of version policy used for optimistic locking + * with {@link Instant} field. + * Defaults to using the time retrieved from the server. + * + * @param field the field where the write lock value will be stored + */ + InstantLockingPolicy(DatabaseField field) { + super(field); + } + + @Override + int compareTsLockValues(Instant value1, Instant value2) { + return value1.compareTo(value2); + } + + @Override + Class getDefaultTsLockFieldType() { + return ClassConstants.TIME_INSTANT; + } + + @Override + Instant getBaseTsValue() { + // LocalDateTime is immutable so constant is safe + return Instant.MIN; + } + + @Override + Instant getInitialTsWriteValue(AbstractSession session) { + if (usesLocalTime()) { + return Instant.now(); + } + if (usesServerTime()) { + AbstractSession readSession = session.getSessionForClass(getDescriptor().getJavaClass()); + while (readSession.isUnitOfWork()) { + readSession = readSession.getParent() + .getSessionForClass(getDescriptor().getJavaClass()); + } + Timestamp ts = readSession.getDatasourceLogin() + .getDatasourcePlatform() + .getTimestampFromServer(session, readSession.getName()); + return ts.toInstant(); + } + return null; + } + + @Override + Instant getNewTsLockValue(ModifyQuery query) { + return getInitialTsWriteValue(query.getSession()); + } + + @Override + Instant getTsValueToPutInCache(AbstractRecord row, AbstractSession session) { + if (isStoredInCache()) { + return session.getDatasourcePlatform() + .convertObject(row.get(getWriteLockField()), ClassConstants.TIME_INSTANT); + } else { + return null; + } + } + + @Override + Instant getWriteTsLockValue(Object domainObject, Object primaryKey, AbstractSession session) { + Instant writeLockFieldValue = null; + if (isStoredInCache()) { + writeLockFieldValue = (Instant) session.getIdentityMapAccessorInstance() + .getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); + } else { + //CR#2281 notStoredInCache prevent ClassCastException + Object lockValue = lockValueFromObject(domainObject); + if (lockValue != null) { + if (lockValue instanceof Instant) { + writeLockFieldValue = (Instant) lockValueFromObject(domainObject); + } else { + throw OptimisticLockException.needToMapJavaSqlTimestampWhenStoredInObject(); + } + } + } + return writeLockFieldValue; + } + + @Override + boolean isNewerTsVersion(Instant current, Object domainObject, Object primaryKey, AbstractSession session) { + Instant writeLockFieldValue; + if (isStoredInCache()) { + writeLockFieldValue = (Instant) session.getIdentityMapAccessorInstance() + .getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); + } else { + writeLockFieldValue = (Instant)lockValueFromObject(domainObject); + } + + return isNewerTsVersion(current, writeLockFieldValue); + + } + + @Override + boolean isNewerTsVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session) { + Instant writeLockFieldValue; + Instant newWriteLockFieldValue = session.getDatasourcePlatform() + .convertObject(row.get(getWriteLockField()), ClassConstants.TIME_INSTANT); + if (isStoredInCache()) { + writeLockFieldValue = (Instant) session.getIdentityMapAccessorInstance() + .getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); + } else { + writeLockFieldValue = (Instant) lockValueFromObject(domainObject); + } + return isNewerTsVersion(newWriteLockFieldValue, writeLockFieldValue); + } + + @Override + boolean isNewerTsVersion(Instant first, Instant second) { + // 2.5.1.6 if the write lock value is null, then what ever we have is treated as newer. + if (first == null) { + return false; + } + // bug 6342382: first is not null, second is null, so we know first>second. + if (second == null) { + return true; + } + return first.isAfter(second); + } + + @Override + public int getVersionDifference(Object currentValue, Object domainObject, Object primaryKeys, AbstractSession session) { + Instant writeLockFieldValue; + Instant newWriteLockFieldValue = (Instant)currentValue; + if (newWriteLockFieldValue == null) { + return 0;//merge it as either the object is new or being forced merged. + } + if (isStoredInCache()) { + writeLockFieldValue = (Instant) session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKeys, domainObject.getClass(), getDescriptor()); + } else { + writeLockFieldValue = (Instant) lockValueFromObject(domainObject); + } + if ((newWriteLockFieldValue.equals(writeLockFieldValue))) { + return 0; + } + if ((writeLockFieldValue != null) && (!newWriteLockFieldValue.isAfter(writeLockFieldValue))) { + return -1; + } + return 1; + } + +} diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/LocalDateTimeLockingPolicy.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/LocalDateTimeLockingPolicy.java new file mode 100644 index 00000000000..f4277d5cf0d --- /dev/null +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/LocalDateTimeLockingPolicy.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2025 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.descriptors; + +import java.sql.Timestamp; +import java.time.LocalDateTime; + +import org.eclipse.persistence.exceptions.OptimisticLockException; +import org.eclipse.persistence.internal.helper.ClassConstants; +import org.eclipse.persistence.internal.helper.DatabaseField; +import org.eclipse.persistence.internal.sessions.AbstractRecord; +import org.eclipse.persistence.internal.sessions.AbstractSession; +import org.eclipse.persistence.queries.ModifyQuery; +/** + * Version policy used for optimistic locking with {@link LocalDateTime} field. + */ +public class LocalDateTimeLockingPolicy extends AbstractTsLockingPolicy { + + /** + * Create a new instance of version policy used for optimistic locking + * with {@link LocalDateTime} field. + * Defaults to using the time retrieved from the server. + */ + public LocalDateTimeLockingPolicy() { + super(); + } + + /** + * Create a new instance of version policy used for optimistic locking + * with {@link LocalDateTime} field. + * Defaults to using the time retrieved from the server. + * + * @param fieldName the field where the write lock value will be stored + */ + public LocalDateTimeLockingPolicy(String fieldName) { + super(fieldName); + } + + /** + * Create a new instance of version policy used for optimistic locking + * with {@link LocalDateTime} field. + * Defaults to using the time retrieved from the server. + * + * @param field the field where the write lock value will be stored + */ + LocalDateTimeLockingPolicy(DatabaseField field) { + super(field); + } + + @Override + int compareTsLockValues(LocalDateTime value1, LocalDateTime value2) { + return value1.compareTo(value2); + } + + @Override + Class getDefaultTsLockFieldType() { + return ClassConstants.LOCAL_DATETIME; + } + + @Override + LocalDateTime getBaseTsValue() { + // LocalDateTime is immutable so constant is safe + return LocalDateTime.MIN; + } + + @Override + LocalDateTime getInitialTsWriteValue(AbstractSession session) { + if (usesLocalTime()) { + return LocalDateTime.now(); + } + if (usesServerTime()) { + AbstractSession readSession = session.getSessionForClass(getDescriptor().getJavaClass()); + while (readSession.isUnitOfWork()) { + readSession = readSession.getParent() + .getSessionForClass(getDescriptor().getJavaClass()); + } + Timestamp ts = readSession.getDatasourceLogin() + .getDatasourcePlatform() + .getTimestampFromServer(session, readSession.getName()); + return ts.toLocalDateTime(); + } + return null; + } + + @Override + LocalDateTime getNewTsLockValue(ModifyQuery query) { + return getInitialTsWriteValue(query.getSession()); + } + + @Override + LocalDateTime getTsValueToPutInCache(AbstractRecord row, AbstractSession session) { + if (isStoredInCache()) { + return session.getDatasourcePlatform() + .convertObject(row.get(getWriteLockField()), ClassConstants.LOCAL_DATETIME); + } else { + return null; + } + } + + @Override + LocalDateTime getWriteTsLockValue(Object domainObject, Object primaryKey, AbstractSession session) { + LocalDateTime writeLockFieldValue = null; + if (isStoredInCache()) { + writeLockFieldValue = (LocalDateTime) session.getIdentityMapAccessorInstance() + .getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); + } else { + //CR#2281 notStoredInCache prevent ClassCastException + Object lockValue = lockValueFromObject(domainObject); + if (lockValue != null) { + if (lockValue instanceof LocalDateTime) { + writeLockFieldValue = (LocalDateTime) lockValueFromObject(domainObject); + } else { + throw OptimisticLockException.needToMapJavaSqlTimestampWhenStoredInObject(); + } + } + } + return writeLockFieldValue; + } + + @Override + boolean isNewerTsVersion(LocalDateTime current, Object domainObject, Object primaryKey, AbstractSession session) { + LocalDateTime writeLockFieldValue; + if (isStoredInCache()) { + writeLockFieldValue = (LocalDateTime) session.getIdentityMapAccessorInstance() + .getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); + } else { + writeLockFieldValue = (LocalDateTime)lockValueFromObject(domainObject); + } + + return isNewerTsVersion(current, writeLockFieldValue); + + } + + @Override + boolean isNewerTsVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session) { + LocalDateTime writeLockFieldValue; + LocalDateTime newWriteLockFieldValue = session.getDatasourcePlatform() + .convertObject(row.get(getWriteLockField()), ClassConstants.LOCAL_DATETIME); + if (isStoredInCache()) { + writeLockFieldValue = (LocalDateTime) session.getIdentityMapAccessorInstance() + .getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); + } else { + writeLockFieldValue = (LocalDateTime) lockValueFromObject(domainObject); + } + return isNewerTsVersion(newWriteLockFieldValue, writeLockFieldValue); + } + + @Override + boolean isNewerTsVersion(LocalDateTime first, LocalDateTime second) { + // 2.5.1.6 if the write lock value is null, then what ever we have is treated as newer. + if (first == null) { + return false; + } + // bug 6342382: first is not null, second is null, so we know first>second. + if (second == null) { + return true; + } + return first.isAfter(second); + } + + @Override + public int getVersionDifference(Object currentValue, Object domainObject, Object primaryKeys, AbstractSession session) { + LocalDateTime writeLockFieldValue; + LocalDateTime newWriteLockFieldValue = (LocalDateTime)currentValue; + if (newWriteLockFieldValue == null) { + return 0;//merge it as either the object is new or being forced merged. + } + if (isStoredInCache()) { + writeLockFieldValue = (LocalDateTime) session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKeys, domainObject.getClass(), getDescriptor()); + } else { + writeLockFieldValue = (LocalDateTime) lockValueFromObject(domainObject); + } + if ((newWriteLockFieldValue.equals(writeLockFieldValue))) { + return 0; + } + if ((writeLockFieldValue != null) && (!newWriteLockFieldValue.isAfter(writeLockFieldValue))) { + return -1; + } + return 1; + } + +} diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/TimestampLockingPolicy.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/TimestampLockingPolicy.java index f0ef3fc39cd..98d74fb2843 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/TimestampLockingPolicy.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/TimestampLockingPolicy.java @@ -15,316 +15,183 @@ // Oracle - initial API and implementation from Oracle TopLink package org.eclipse.persistence.descriptors; +import java.sql.Timestamp; + import org.eclipse.persistence.exceptions.OptimisticLockException; -import org.eclipse.persistence.expressions.Expression; -import org.eclipse.persistence.expressions.ExpressionBuilder; import org.eclipse.persistence.internal.helper.ClassConstants; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.sessions.AbstractRecord; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.queries.ModifyQuery; -import java.sql.Timestamp; - /** *

Purpose: Used to allow a single version timestamp to be used for optimistic locking. * * @since TOPLink/Java 2.0 */ -public class TimestampLockingPolicy extends VersionLockingPolicy { - protected int retrieveTimeFrom; - public final static int SERVER_TIME = 1; - public final static int LOCAL_TIME = 2; +public class TimestampLockingPolicy extends AbstractTsLockingPolicy { /** - * PUBLIC: * Create a new TimestampLockingPolicy. * Defaults to using the time retrieved from the server. */ public TimestampLockingPolicy() { super(); - this.useServerTime(); } /** - * PUBLIC: * Create a new TimestampLockingPolicy. * Defaults to using the time retrieved from the server. - * @param fieldName the field where the write lock value will be stored. + * + * @param fieldName the field where the write lock value will be stored */ public TimestampLockingPolicy(String fieldName) { super(fieldName); - this.useServerTime(); } /** - * INTERNAL: * Create a new TimestampLockingPolicy. * Defaults to using the time retrieved from the server. - * @param field the field where the write lock value will be stored. + * + * @param field the field where the write lock value will be stored */ - public TimestampLockingPolicy(DatabaseField field) { + TimestampLockingPolicy(DatabaseField field) { super(field); - this.useServerTime(); } - /** - * INTERNAL: - * This method compares two writeLockValues. - * The writeLockValues should be non-null and of type java.sql.Timestamp. - * Returns: - * -1 if value1 is less (older) than value2; - * 0 if value1 equals value2; - * 1 if value1 is greater (newer) than value2. - * Throws: - * NullPointerException if the passed value is null; - * ClassCastException if the passed value is of a wrong type. - */ @Override - public int compareWriteLockValues(Object value1, Object value2) { - java.sql.Timestamp timestampValue1 = (java.sql.Timestamp)value1; - java.sql.Timestamp timestampValue2 = (java.sql.Timestamp)value2; - return timestampValue1.compareTo(timestampValue2); + int compareTsLockValues(Timestamp value1, Timestamp value2) { + return value1.compareTo(value2); } - /** - * INTERNAL: - * Return the default timestamp locking filed java type, default is Timestamp. - */ @Override - @SuppressWarnings({"unchecked"}) - protected Class getDefaultLockingFieldType() { - return (Class) ClassConstants.TIMESTAMP; + Class getDefaultTsLockFieldType() { + return ClassConstants.TIMESTAMP; } - /** - * INTERNAL: - * This is the base value that is older than all other values, it is used in the place of - * null in some situations. - */ @Override - @SuppressWarnings({"unchecked"}) - public T getBaseValue(){ - return (T) new Timestamp(0); + Timestamp getBaseTsValue(){ + return new Timestamp(0); } - /** - * INTERNAL: - * returns the initial locking value - */ @Override - @SuppressWarnings({"unchecked"}) - protected T getInitialWriteValue(AbstractSession session) { + Timestamp getInitialTsWriteValue(AbstractSession session) { if (usesLocalTime()) { - return (T) new Timestamp(System.currentTimeMillis()); + return new Timestamp(System.currentTimeMillis()); } if (usesServerTime()) { AbstractSession readSession = session.getSessionForClass(getDescriptor().getJavaClass()); while (readSession.isUnitOfWork()) { - readSession = readSession.getParent().getSessionForClass(getDescriptor().getJavaClass()); + readSession = readSession.getParent() + .getSessionForClass(getDescriptor().getJavaClass()); } - return (T) readSession.getDatasourceLogin().getDatasourcePlatform().getTimestampFromServer(session, readSession.getName()); + return readSession.getDatasourceLogin() + .getDatasourcePlatform() + .getTimestampFromServer(session, readSession.getName()); } return null; - } - /** - * INTERNAL: - * Returns the new Timestamp value. - */ @Override - public T getNewLockValue(ModifyQuery query) { - return getInitialWriteValue(query.getSession()); + Timestamp getNewTsLockValue(ModifyQuery query) { + return getInitialTsWriteValue(query.getSession()); } - /** - * INTERNAL: - * Return the value that should be stored in the identity map. If the value - * is stored in the object, then return a null. - */ @Override - @SuppressWarnings({"unchecked"}) - public T getValueToPutInCache(AbstractRecord row, AbstractSession session) { + Timestamp getTsValueToPutInCache(AbstractRecord row, AbstractSession session) { if (isStoredInCache()) { - return (T) session.getDatasourcePlatform().convertObject(row.get(getWriteLockField()), ClassConstants.TIMESTAMP); + return session.getDatasourcePlatform() + .convertObject(row.get(getWriteLockField()), ClassConstants.TIMESTAMP); } else { return null; } } - /** - * INTERNAL: - * Return the number of versions different between these objects. - */ - @Override - public int getVersionDifference(Object currentValue, Object domainObject, Object primaryKeys, AbstractSession session) { - java.sql.Timestamp writeLockFieldValue; - java.sql.Timestamp newWriteLockFieldValue = (java.sql.Timestamp)currentValue; - if (newWriteLockFieldValue == null) { - return 0;//merge it as either the object is new or being forced merged. - } - if (isStoredInCache()) { - writeLockFieldValue = (java.sql.Timestamp)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKeys, domainObject.getClass(), getDescriptor()); - } else { - writeLockFieldValue = (java.sql.Timestamp)lockValueFromObject(domainObject); - } - if ((writeLockFieldValue != null) && (newWriteLockFieldValue.equals(writeLockFieldValue))) { - return 0; - } - if ((writeLockFieldValue != null) && !(newWriteLockFieldValue.after(writeLockFieldValue))) { - return -1; - } - - return 1; - } - - /** - * INTERNAL: - * This method will return the optimistic lock value for the object. - */ @Override - @SuppressWarnings({"unchecked"}) - public T getWriteLockValue(Object domainObject, Object primaryKey, AbstractSession session) { - java.sql.Timestamp writeLockFieldValue = null; + Timestamp getWriteTsLockValue(Object domainObject, Object primaryKey, AbstractSession session) { + Timestamp writeLockFieldValue = null; if (isStoredInCache()) { - writeLockFieldValue = (java.sql.Timestamp)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); + writeLockFieldValue = (Timestamp) session.getIdentityMapAccessorInstance() + .getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); } else { //CR#2281 notStoredInCache prevent ClassCastException Object lockValue = lockValueFromObject(domainObject); if (lockValue != null) { - if (lockValue instanceof java.sql.Timestamp) { - writeLockFieldValue = (java.sql.Timestamp)lockValueFromObject(domainObject); + if (lockValue instanceof Timestamp) { + writeLockFieldValue = (Timestamp) lockValueFromObject(domainObject); } else { throw OptimisticLockException.needToMapJavaSqlTimestampWhenStoredInObject(); } } } - return (T) writeLockFieldValue; - } - - /** - * INTERNAL: - * Return an expression that updates the write lock - */ - @Override - public Expression getWriteLockUpdateExpression(ExpressionBuilder builder, AbstractSession session) { - return builder.currentTimeStamp(); - } - - /** - * INTERNAL: - * Timestamp versioning should not be able to do this. Override the superclass behavior. - */ - @Override - protected Number incrementWriteLockValue(Number numberValue) { - return null; + return writeLockFieldValue; } - /** - * INTERNAL: - * Compares the value with the value from the object (or cache). - * Will return true if the currentValue is newer than the domainObject. - */ @Override - public boolean isNewerVersion(Object currentValue, Object domainObject, Object primaryKey, AbstractSession session) { - java.sql.Timestamp writeLockFieldValue; - java.sql.Timestamp newWriteLockFieldValue = (java.sql.Timestamp)currentValue; + boolean isNewerTsVersion(Timestamp current, Object domainObject, Object primaryKey, AbstractSession session) { + Timestamp writeLockFieldValue; if (isStoredInCache()) { - writeLockFieldValue = (java.sql.Timestamp)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); + writeLockFieldValue = (Timestamp) session.getIdentityMapAccessorInstance() + .getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); } else { - writeLockFieldValue = (java.sql.Timestamp)lockValueFromObject(domainObject); + writeLockFieldValue = (Timestamp)lockValueFromObject(domainObject); } - return isNewerVersion(newWriteLockFieldValue, writeLockFieldValue); + return isNewerTsVersion(current, writeLockFieldValue); + } - /** - * INTERNAL: - * Compares the value from the row and from the object (or cache). - * Will return true if the row is newer than the object. - */ @Override - public boolean isNewerVersion(AbstractRecord databaseRow, Object domainObject, Object primaryKey, AbstractSession session) { - java.sql.Timestamp writeLockFieldValue; - java.sql.Timestamp newWriteLockFieldValue = session.getDatasourcePlatform().convertObject(databaseRow.get(getWriteLockField()), ClassConstants.TIMESTAMP); + boolean isNewerTsVersion(AbstractRecord row, Object domainObject, Object primaryKey, AbstractSession session) { + Timestamp writeLockFieldValue; + Timestamp newWriteLockFieldValue = session.getDatasourcePlatform() + .convertObject(row.get(getWriteLockField()), ClassConstants.TIMESTAMP); if (isStoredInCache()) { - writeLockFieldValue = (java.sql.Timestamp)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); + writeLockFieldValue = (Timestamp) session.getIdentityMapAccessorInstance() + .getWriteLockValue(primaryKey, domainObject.getClass(), getDescriptor()); } else { - writeLockFieldValue = (java.sql.Timestamp)lockValueFromObject(domainObject); + writeLockFieldValue = (Timestamp) lockValueFromObject(domainObject); } - - return isNewerVersion(newWriteLockFieldValue, writeLockFieldValue); + return isNewerTsVersion(newWriteLockFieldValue, writeLockFieldValue); } - /** - * INTERNAL: - * Compares two values. - * Will return true if the firstLockFieldValue is newer than the secondWriteLockFieldValue. - */ @Override - public boolean isNewerVersion(Object firstLockFieldValue, Object secondWriteLockFieldValue) { - java.sql.Timestamp firstValue = (java.sql.Timestamp)firstLockFieldValue; - java.sql.Timestamp secondValue = (java.sql.Timestamp)secondWriteLockFieldValue; - + boolean isNewerTsVersion(Timestamp first, Timestamp second) { // 2.5.1.6 if the write lock value is null, then what ever we have is treated as newer. - if (firstValue == null) { + if (first == null) { return false; } - // bug 6342382: first is not null, second is null, so we know first>second. - if(secondValue == null) { + if (second == null) { return true; } - - if (firstValue.after(secondValue)){ - return true; - } - return false; + return first.after(second); } /** - * PUBLIC: - * Set if policy uses server time. + * INTERNAL: + * Return the number of versions different between these objects. */ - public void setUsesServerTime(boolean usesServerTime) { - if (usesServerTime) { - useServerTime(); + @Override + public int getVersionDifference(Object currentValue, Object domainObject, Object primaryKeys, AbstractSession session) { + Timestamp writeLockFieldValue; + Timestamp newWriteLockFieldValue = (Timestamp)currentValue; + if (newWriteLockFieldValue == null) { + return 0;//merge it as either the object is new or being forced merged. + } + if (isStoredInCache()) { + writeLockFieldValue = (Timestamp)session.getIdentityMapAccessorInstance().getWriteLockValue(primaryKeys, domainObject.getClass(), getDescriptor()); } else { - useLocalTime(); + writeLockFieldValue = (Timestamp)lockValueFromObject(domainObject); } + if ((writeLockFieldValue != null) && (newWriteLockFieldValue.equals(writeLockFieldValue))) { + return 0; + } + if ((writeLockFieldValue != null) && !(newWriteLockFieldValue.after(writeLockFieldValue))) { + return -1; + } + return 1; } - /** - * PUBLIC: - * set this policy to get the time from the local machine. - */ - public void useLocalTime() { - retrieveTimeFrom = LOCAL_TIME; - } - - /** - * PUBLIC: - * set this policy to get the time from the server. - */ - public void useServerTime() { - retrieveTimeFrom = SERVER_TIME; - } - - /** - * PUBLIC: - * Return true if policy uses local time. - */ - public boolean usesLocalTime() { - return (retrieveTimeFrom == LOCAL_TIME); - } - - /** - * PUBLIC: - * Return true if policy uses server time. - */ - public boolean usesServerTime() { - return (retrieveTimeFrom == SERVER_TIME); - } } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/VersionLockingPolicy.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/VersionLockingPolicy.java index f7142852efd..0290422db90 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/VersionLockingPolicy.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/descriptors/VersionLockingPolicy.java @@ -14,6 +14,11 @@ // Oracle - initial API and implementation from Oracle TopLink package org.eclipse.persistence.descriptors; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + import org.eclipse.persistence.exceptions.DescriptorException; import org.eclipse.persistence.exceptions.OptimisticLockException; import org.eclipse.persistence.expressions.Expression; @@ -37,11 +42,6 @@ import org.eclipse.persistence.queries.ObjectLevelModifyQuery; import org.eclipse.persistence.queries.WriteObjectQuery; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - /** *

Purpose: Used to allow a single version number to be used for optimistic locking. @@ -200,14 +200,16 @@ public boolean supportsWriteLockValuesComparison() { /** * INTERNAL: * This method compares two writeLockValues. - * The writeLockValues should be non-null and of type Number. - * Returns: - * -1 if value1 is less (older) than value2; - * 0 if value1 equals value2; - * 1 if value1 is greater (newer) than value2. - * Throws: - * NullPointerException if the passed value is null; - * ClassCastException if the passed value is of a wrong type. + * The writeLockValues should be non-null and of {@link java.time.LocalDateTime}, + * {@link java.time.Instant} or {@link java.sql.Timestamp} type. + * + * @param value1 the 1st value to compare + * @param value2 the 2nd value to compare + * @return {@code -1} if value1 is less (older) than value2, + * {@code 0} if value1 equals value2, + * {@code 1} if value1 is greater (newer) than value2. + * @throws NullPointerException if the passed value is null + * @throws ClassCastException if the passed value is of a wrong type. */ @Override public int compareWriteLockValues(Object value1, Object value2) { @@ -218,7 +220,9 @@ public int compareWriteLockValues(Object value1, Object value2) { /** * INTERNAL: - * Return the default version locking filed java type, default is BigDecimal + * Return the default timestamp locking filed java type. + * + * @return the default timestamp locking filed java type */ @SuppressWarnings({"unchecked"}) protected Class getDefaultLockingFieldType() { @@ -228,8 +232,10 @@ protected Class getDefaultLockingFieldType() { /** * INTERNAL: - * This is the base value that is older than all other values, it is used in the place of - * null in some situations. + * Return base value that is older than all other values, it is used in the place of + * {@code null} in some situations. + * + * @return timestamp base value */ @Override @SuppressWarnings({"unchecked"}) @@ -246,7 +252,10 @@ protected ClassDescriptor getDescriptor() { /** * INTERNAL: - * returns the initial locking value + * Return initial locking value. + * + * @param session the database session + * @return the initial locking value */ @SuppressWarnings({"unchecked"}) protected T getInitialWriteValue(AbstractSession session) { @@ -268,7 +277,10 @@ public LockOnChange getLockOnChangeMode(){ /** * INTERNAL: * This method gets the write lock value from either the cache or - * the object stored in the query. It then returns the new incremented value. + * the object stored in the query. It then returns the new incremented value. + * + * @param query modify query + * @return the new timestamp value */ @SuppressWarnings({"unchecked"}) public T getNewLockValue(ModifyQuery query) { @@ -307,6 +319,10 @@ protected List getUnmappedFields() { * INTERNAL: * Return the value that should be stored in the identity map. * If the value is stored in the object, then return a null. + * + * @param row the data row, e.g. database row + * @param session the database session + * @return the value that should be stored in the identity map */ @Override @SuppressWarnings({"unchecked"}) @@ -375,7 +391,12 @@ public Expression getWriteLockUpdateExpression(ExpressionBuilder builder, Abstra /** * INTERNAL: - * This method will return the optimistic lock value for the object + * Return the optimistic lock value for the object. + * + * @param domainObject the domain object (entity instance) + * @param primaryKey the primary key + * @param session the database session + * @return the optimistic lock value for the object */ @Override @SuppressWarnings({"unchecked"}) @@ -464,8 +485,14 @@ public boolean isCascaded() { /** * INTERNAL: - * Compares the value with the value from the object (or cache). - * Will return true if the currentValue is newer than the domainObject. + * Compares two version values from the current value and from the object (or cache). + * + * @param currentValue the current value + * @param domainObject the domain object (entity instance) + * @param primaryKey the primary key + * @param session the database session + * @return value of {@code true} if the {@code first} is newer than the {@code second} + * or {@code false} otherwise */ @Override public boolean isNewerVersion(Object currentValue, Object domainObject, Object primaryKey, AbstractSession session) { @@ -483,8 +510,14 @@ public boolean isNewerVersion(Object currentValue, Object domainObject, Object p /** * INTERNAL: - * Compares the value from the row and from the object (or cache). - * Will return true if the row is newer than the object. + * Compares two version values from the row and from the object (or cache). + * + * @param databaseRow the data row, e.g. database row + * @param domainObject the domain object (entity instance) + * @param primaryKey the primary key + * @param session the database session + * @return value of {@code true} if the {@code first} is newer than the {@code second} + * or {@code false} otherwise */ @Override public boolean isNewerVersion(AbstractRecord databaseRow, Object domainObject, Object primaryKey, AbstractSession session) { @@ -501,8 +534,13 @@ public boolean isNewerVersion(AbstractRecord databaseRow, Object domainObject, O /** * INTERNAL: - * Compares two values. + * Compares two version values. * Will return true if the firstLockFieldValue is newer than the secondWriteLockFieldValue. + * + * @param firstLockFieldValue first version value + * @param secondWriteLockFieldValue second version value + * @return value of {@code true} if the {@code first} is newer than the {@code second} + * or {@code false} otherwise */ public boolean isNewerVersion(Object firstLockFieldValue, Object secondWriteLockFieldValue) { Number firstValue = (Number)firstLockFieldValue;//domain object/clone diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/InstantVersionEntity.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/InstantVersionEntity.java new file mode 100644 index 00000000000..228f0154ea1 --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/InstantVersionEntity.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025 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.jpa.persistence32; + +import java.time.Instant; +import java.util.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +// Bug #2343 test to verify Instant as @Version attribute +@Entity +@Table(name="PERSISTENCE32_INSTANT_VERSION_ENTITY") +public class InstantVersionEntity { + + @Id + private int id; + + @Version + private Instant lastUpdated; + + private String name; + + public InstantVersionEntity(int id, Instant lastUpdated, String name) { + this.id = id; + this.lastUpdated = lastUpdated; + this.name = name; + } + + public InstantVersionEntity() { + this(-1, null, null); + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Instant getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(Instant lastUpdated) { + this.lastUpdated = lastUpdated; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return id == ((InstantVersionEntity) obj).id + && Objects.equals(name, ((InstantVersionEntity) obj).name) + && Objects.equals(lastUpdated, ((InstantVersionEntity) obj).lastUpdated); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, lastUpdated); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("InstantVersionEntity {id="); + sb.append(id); + sb.append(", name="); + sb.append(name); + sb.append(", lastUpdated="); + sb.append(lastUpdated); + sb.append("}"); + return sb.toString(); + } + +} diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/LdtVersionEntity.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/LdtVersionEntity.java new file mode 100644 index 00000000000..d18dd6dd3c9 --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/LdtVersionEntity.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2025 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.jpa.persistence32; + +import java.time.LocalDateTime; +import java.util.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +// Bug #2343 test to verify LocalDateTime as @Version attribute +@Entity +@Table(name="PERSISTENCE32_LDT_VERSION_ENTITY") +public class LdtVersionEntity { + + @Id + private int id; + + @Version + private LocalDateTime lastUpdated; + + private String name; + + public LdtVersionEntity(int id, LocalDateTime lastUpdated, String name) { + this.id = id; + this.lastUpdated = lastUpdated; + this.name = name; + } + + public LdtVersionEntity() { + this(-1, null, null); + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public LocalDateTime getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(LocalDateTime lastUpdated) { + this.lastUpdated = lastUpdated; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return id == ((LdtVersionEntity) obj).id + && Objects.equals(name, ((LdtVersionEntity) obj).name) + && Objects.equals(lastUpdated, ((LdtVersionEntity) obj).lastUpdated); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, lastUpdated); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("LdtVersionEntity {id="); + sb.append(id); + sb.append(", name="); + sb.append(name); + sb.append(", lastUpdated="); + sb.append(lastUpdated); + sb.append("}"); + return sb.toString(); + } +} diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/TimestampVersionEntity.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/TimestampVersionEntity.java new file mode 100644 index 00000000000..d918c9733c7 --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/TimestampVersionEntity.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025 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.jpa.persistence32; + +import java.sql.Timestamp; +import java.util.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +// Bug #2343 test to verify Timestamp as @Version attribute +@Entity +@Table(name="PERSISTENCE32_TIMESTAMP_VERSION_ENTITY") +public class TimestampVersionEntity { + + @Id + private int id; + + @Version + private Timestamp lastUpdated; + + private String name; + + public TimestampVersionEntity(int id, Timestamp lastUpdated, String name) { + this.id = id; + this.lastUpdated = lastUpdated; + this.name = name; + } + + public TimestampVersionEntity() { + this(-1, null, null); + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Timestamp getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(Timestamp lastUpdated) { + this.lastUpdated = lastUpdated; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return id == ((TimestampVersionEntity) obj).id + && Objects.equals(name, ((TimestampVersionEntity) obj).name) + && Objects.equals(lastUpdated, ((TimestampVersionEntity) obj).lastUpdated); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, lastUpdated); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("InstantVersionEntity {id="); + sb.append(id); + sb.append(", name="); + sb.append(name); + sb.append(", lastUpdated="); + sb.append(lastUpdated); + sb.append("}"); + return sb.toString(); + } + +} diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/resources/META-INF/persistence.xml b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/resources/META-INF/persistence.xml index 2c82d626ed9..0034c667fed 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/resources/META-INF/persistence.xml +++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/resources/META-INF/persistence.xml @@ -1,6 +1,6 @@