diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionEntityInformation.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionEntityInformation.java index d8b96b28d9..493aab3e8c 100644 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionEntityInformation.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/DefaultRevisionEntityInformation.java @@ -22,6 +22,7 @@ * {@link RevisionEntityInformation} for {@link DefaultRevisionEntity}. * * @author Oliver Gierke + * @author Chaedong Im */ class DefaultRevisionEntityInformation implements RevisionEntityInformation { @@ -36,4 +37,8 @@ public boolean isDefaultRevisionEntity() { public Class getRevisionEntityClass() { return DefaultRevisionEntity.class; } + + public String getRevisionTimestampFieldName() { + return "timestamp"; + } } diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImpl.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImpl.java index 4515a74ed9..2d7bd98344 100755 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImpl.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImpl.java @@ -66,12 +66,14 @@ * @author Greg Turnquist * @author Aref Behboodi * @author Ngoc Nhan + * @author Chaedong Im */ @Transactional(readOnly = true) public class EnversRevisionRepositoryImpl> implements RevisionRepository { private final EntityInformation entityInformation; + private final RevisionEntityInformation revisionEntityInformation; private final EntityManager entityManager; /** @@ -90,14 +92,16 @@ public EnversRevisionRepositoryImpl(JpaEntityInformation entityInformation Assert.notNull(revisionEntityInformation, "RevisionEntityInformation must not be null!"); this.entityInformation = entityInformation; + this.revisionEntityInformation = revisionEntityInformation; this.entityManager = entityManager; } @SuppressWarnings("unchecked") public Optional> findLastChangeRevision(ID id) { + String timestampFieldName = getRevisionTimestampFieldName(); List singleResult = createBaseQuery(id) // - .addOrder(AuditEntity.revisionProperty("timestamp").desc()) // + .addOrder(AuditEntity.revisionProperty(timestampFieldName).desc()) // .addOrder(AuditEntity.revisionNumber().desc()) // .setMaxResults(1) // .getResultList(); @@ -213,6 +217,16 @@ private Revision createRevision(QueryResult queryResult) { return Revision.of((RevisionMetadata) queryResult.createRevisionMetadata(), queryResult.entity); } + private String getRevisionTimestampFieldName() { + if (revisionEntityInformation instanceof ReflectionRevisionEntityInformation reflection) { + return reflection.getRevisionTimestampFieldName(); + } else if (revisionEntityInformation instanceof DefaultRevisionEntityInformation defaultInfo) { + return defaultInfo.getRevisionTimestampFieldName(); + } else { + return "timestamp"; + } + } + @SuppressWarnings("unchecked") static class QueryResult { diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/ReflectionRevisionEntityInformation.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/ReflectionRevisionEntityInformation.java index 631dbca9f8..193cf5d513 100644 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/ReflectionRevisionEntityInformation.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/ReflectionRevisionEntityInformation.java @@ -16,6 +16,7 @@ package org.springframework.data.envers.repository.support; import org.hibernate.envers.RevisionNumber; +import org.hibernate.envers.RevisionTimestamp; import org.springframework.data.repository.history.support.RevisionEntityInformation; import org.springframework.data.util.AnnotationDetectionFieldCallback; @@ -27,11 +28,13 @@ * find out about the revision number type. * * @author Oliver Gierke + * @author Chaedong Im */ public class ReflectionRevisionEntityInformation implements RevisionEntityInformation { private final Class revisionEntityClass; private final Class revisionNumberType; + private final String revisionTimestampFieldName; /** * Creates a new {@link ReflectionRevisionEntityInformation} inspecting the given revision entity class. @@ -42,10 +45,14 @@ public ReflectionRevisionEntityInformation(Class revisionEntityClass) { Assert.notNull(revisionEntityClass, "Revision entity type must not be null"); - AnnotationDetectionFieldCallback fieldCallback = new AnnotationDetectionFieldCallback(RevisionNumber.class); - ReflectionUtils.doWithFields(revisionEntityClass, fieldCallback); + AnnotationDetectionFieldCallback revisionNumberFieldCallback = new AnnotationDetectionFieldCallback(RevisionNumber.class); + ReflectionUtils.doWithFields(revisionEntityClass, revisionNumberFieldCallback); - this.revisionNumberType = fieldCallback.getRequiredType(); + AnnotationDetectionFieldCallback revisionTimestampFieldCallback = new AnnotationDetectionFieldCallback(RevisionTimestamp.class); + ReflectionUtils.doWithFields(revisionEntityClass, revisionTimestampFieldCallback); + + this.revisionNumberType = revisionNumberFieldCallback.getRequiredType(); + this.revisionTimestampFieldName = revisionTimestampFieldCallback.getRequiredField().getName(); this.revisionEntityClass = revisionEntityClass; } @@ -61,4 +68,8 @@ public Class getRevisionEntityClass() { public Class getRevisionNumberType() { return this.revisionNumberType; } + + public String getRevisionTimestampFieldName() { + return this.revisionTimestampFieldName; + } } diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImplUnitTests.java b/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImplUnitTests.java index 625e099d5a..9c48210e43 100644 --- a/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImplUnitTests.java +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryImplUnitTests.java @@ -21,6 +21,8 @@ import org.hibernate.envers.DefaultRevisionEntity; import org.hibernate.envers.RevisionType; import org.junit.jupiter.api.Test; +import org.springframework.data.envers.sample.CustomRevisionEntity; +import org.springframework.data.envers.sample.CustomRevisionEntityWithDifferentTimestamp; import org.springframework.data.history.AnnotationRevisionMetadata; import org.springframework.data.history.RevisionMetadata; @@ -28,6 +30,7 @@ * Unit tests for {@link EnversRevisionRepositoryImpl}. * * @author Jens Schauder + * @author Chaedong Im */ class EnversRevisionRepositoryImplUnitTests { @@ -57,4 +60,27 @@ void revisionTypeOfDefaultRevisionMetadataIsProperlySet() { assertThat(revisionMetadata.getRevisionType()).isEqualTo(RevisionMetadata.RevisionType.DELETE); } + @Test // gh-2850 + void reflectionRevisionEntityInformationDetectsStandardTimestampField() { + + ReflectionRevisionEntityInformation revisionInfo = new ReflectionRevisionEntityInformation(CustomRevisionEntity.class); + + assertThat(revisionInfo.getRevisionTimestampFieldName()).isEqualTo("timestamp"); + } + + @Test // gh-2850 + void reflectionRevisionEntityInformationDetectsCustomTimestampField() { + + ReflectionRevisionEntityInformation revisionInfo = new ReflectionRevisionEntityInformation(CustomRevisionEntityWithDifferentTimestamp.class); + + assertThat(revisionInfo.getRevisionTimestampFieldName()).isEqualTo("myCustomTimestamp"); + } + + @Test // gh-2850 + void defaultRevisionEntityInformationReturnsStandardTimestampFieldName() { + + DefaultRevisionEntityInformation revisionInfo = new DefaultRevisionEntityInformation(); + + assertThat(revisionInfo.getRevisionTimestampFieldName()).isEqualTo("timestamp"); + } } diff --git a/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CustomRevisionEntityWithDifferentTimestamp.java b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CustomRevisionEntityWithDifferentTimestamp.java new file mode 100644 index 0000000000..c6825707a9 --- /dev/null +++ b/spring-data-envers/src/test/java/org/springframework/data/envers/sample/CustomRevisionEntityWithDifferentTimestamp.java @@ -0,0 +1,56 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.envers.sample; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import org.hibernate.envers.RevisionEntity; +import org.hibernate.envers.RevisionNumber; +import org.hibernate.envers.RevisionTimestamp; + +/** + * Custom revision entity with a non-standard timestamp field name to test dynamic timestamp property detection. + * + * @author Chaedong Im + */ +@Entity +@RevisionEntity +public class CustomRevisionEntityWithDifferentTimestamp { + + @Id @GeneratedValue @RevisionNumber + private int revisionId; + + @RevisionTimestamp + private long myCustomTimestamp; // Non-standard field name + + public int getRevisionId() { + return revisionId; + } + + public void setRevisionId(int revisionId) { + this.revisionId = revisionId; + } + + public long getMyCustomTimestamp() { + return myCustomTimestamp; + } + + public void setMyCustomTimestamp(long myCustomTimestamp) { + this.myCustomTimestamp = myCustomTimestamp; + } +}