Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Optional;
import java.util.Set;

import org.hibernate.envers.DefaultRevisionEntity;

Expand All @@ -32,22 +33,26 @@
* @author Oliver Gierke
* @author Philip Huegelmeyer
* @author Jens Schauder
* @author Miguel Ángel Ruiz
*/
public final class DefaultRevisionMetadata implements RevisionMetadata<Integer> {

private final DefaultRevisionEntity entity;
private final RevisionType revisionType;
private final Set<String> changedFields;

public DefaultRevisionMetadata(DefaultRevisionEntity entity) {
this(entity, RevisionType.UNKNOWN);
this(entity, RevisionType.UNKNOWN, Set.of());
}

public DefaultRevisionMetadata(DefaultRevisionEntity entity, RevisionType revisionType) {
public DefaultRevisionMetadata(DefaultRevisionEntity entity, RevisionType revisionType, Set<String> changedFields) {

Assert.notNull(entity, "DefaultRevisionEntity must not be null");
Assert.notNull(changedFields, "Changed fields set must not be null");

this.entity = entity;
this.revisionType = revisionType;
this.changedFields = changedFields;
}

public Optional<Integer> getRevisionNumber() {
Expand All @@ -74,6 +79,10 @@ public RevisionType getRevisionType() {
return revisionType;
}

public Set<String> getChangedFields() {
return changedFields;
}

@Override
public boolean equals(Object o) {

Expand All @@ -84,12 +93,13 @@ public boolean equals(Object o) {
return false;
}
DefaultRevisionMetadata that = (DefaultRevisionMetadata) o;
return getRevisionNumber().equals(that.getRevisionNumber())
&& getRevisionInstant().equals(that.getRevisionInstant()) && revisionType.equals(that.getRevisionType());
return getRevisionNumber().equals(that.getRevisionNumber()) && getRevisionInstant().equals(
that.getRevisionInstant()) && revisionType.equals(that.getRevisionType()) && getChangedFields().equals(
that.getChangedFields());
}

@Override
public String toString() {
return "DefaultRevisionMetadata{" + "entity=" + entity + ", revisionType=" + revisionType + '}';
return "DefaultRevisionMetadata{" + "entity=" + entity + ", revisionType=" + revisionType + ", changedFields=" + changedFields + '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import org.hibernate.Hibernate;
import org.hibernate.envers.AuditReader;
Expand All @@ -33,6 +34,7 @@
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.query.AuditQuery;
import org.hibernate.envers.query.AuditQueryCreator;
import org.hibernate.envers.query.criteria.AuditProperty;
import org.hibernate.envers.query.order.AuditOrder;
import org.springframework.data.domain.Page;
Expand Down Expand Up @@ -65,6 +67,7 @@
* @author Donghun Shin
* @author Greg Turnquist
* @author Aref Behboodi
* @author Miguel Ángel Ruiz
*/
@Transactional(readOnly = true)
public class EnversRevisionRepositoryImpl<T, ID, N extends Number & Comparable<N>>
Expand Down Expand Up @@ -132,7 +135,14 @@ public Optional<Revision<N, T>> findRevision(ID id, N revisionNumber) {
@SuppressWarnings("unchecked")
public Revisions<N, T> findRevisions(ID id) {

List<Object[]> resultList = createBaseQuery(id).getResultList();
return findRevisions(id, Set.of());
}

@SuppressWarnings("unchecked")
@Override
public Revisions<N, T> findRevisions(ID id, Set<String> changedFields) {

List<Object[]> resultList = createBaseQuery(id, changedFields).getResultList();
List<Revision<N, T>> revisionList = new ArrayList<>(resultList.size());

for (Object[] objects : resultList) {
Expand Down Expand Up @@ -172,7 +182,14 @@ private List<AuditOrder> mapPropertySort(Sort sort) {
@SuppressWarnings("unchecked")
public Page<Revision<N, T>> findRevisions(ID id, Pageable pageable) {

AuditQuery baseQuery = createBaseQuery(id);
return findRevisions(id, Set.of(), pageable);
}

@SuppressWarnings("unchecked")
@Override
public Page<Revision<N, T>> findRevisions(ID id, Set<String> changedFields, Pageable pageable) {

AuditQuery baseQuery = createBaseQuery(id, changedFields);

List<AuditOrder> orderMapped = (pageable.getSort() instanceof RevisionSort revisionSort)
? List.of(mapRevisionSort(revisionSort))
Expand All @@ -185,7 +202,7 @@ public Page<Revision<N, T>> findRevisions(ID id, Pageable pageable) {
.setMaxResults(pageable.getPageSize()) //
.getResultList();

Long count = (Long) createBaseQuery(id) //
Long count = (Long) createBaseQuery(id, changedFields) //
.addProjection(AuditEntity.revisionNumber().count()).getSingleResult();

List<Revision<N, T>> revisions = new ArrayList<>();
Expand All @@ -198,12 +215,24 @@ public Page<Revision<N, T>> findRevisions(ID id, Pageable pageable) {

private AuditQuery createBaseQuery(ID id) {

return createBaseQuery(id, Set.of());
}

private AuditQuery createBaseQuery(ID id, Set<String> changedFields) {

Assert.notNull(changedFields, "Changed fields must not be null!");

Class<T> type = entityInformation.getJavaType();
AuditReader reader = AuditReaderFactory.get(entityManager);

return reader.createQuery() //
.forRevisionsOfEntity(type, false, true) //
.add(AuditEntity.id().eq(id));
AuditQueryCreator auditQueryCreator = reader.createQuery();

AuditQuery auditQuery = changedFields.isEmpty() ? auditQueryCreator.forRevisionsOfEntity(type, false, true) //
: auditQueryCreator.forRevisionsOfEntityWithChanges(type, true);

changedFields.forEach(fieldName -> auditQuery.add(AuditEntity.property(fieldName).hasChanged()));

return auditQuery.add(AuditEntity.id().eq(id));
}

@SuppressWarnings("unchecked")
Expand All @@ -217,13 +246,14 @@ static class QueryResult<T> {
private final T entity;
private final Object metadata;
private final RevisionMetadata.RevisionType revisionType;
private final Set<String> changedFields;

QueryResult(Object[] data) {

Assert.notNull(data, "Data must not be null");
Assert.isTrue( //
data.length == 3, //
() -> String.format("Data must have length three, but has length %d.", data.length));
data.length >= 3, //
() -> String.format("Data must have at least length three, but has length %d.", data.length));
Assert.isTrue( //
data[2] instanceof RevisionType, //
() -> String.format("The third array element must be of type Revision type, but is of type %s",
Expand All @@ -232,12 +262,23 @@ static class QueryResult<T> {
entity = (T) data[0];
metadata = data[1];
revisionType = convertRevisionType((RevisionType) data[2]);
Set<String> changedFieldsTemp = Set.of();

if (data.length == 4) {
Assert.isTrue( //
data[3] instanceof Set<?>, //
() -> String.format("The fourth array element must be of type Set, but is of type %s", data[3].getClass()));

changedFieldsTemp = (Set<String>) data[3];
}

changedFields = changedFieldsTemp;
}

RevisionMetadata<?> createRevisionMetadata() {

return metadata instanceof DefaultRevisionEntity defaultRevisionEntity //
? new DefaultRevisionMetadata(defaultRevisionEntity, revisionType) //
? new DefaultRevisionMetadata(defaultRevisionEntity, revisionType, changedFields) //
: new AnnotationRevisionMetadata<>(Hibernate.unproxy(metadata), RevisionNumber.class, RevisionTimestamp.class,
revisionType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.springframework.data.envers.repository.support;

import org.assertj.core.util.IterableUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -23,6 +24,8 @@
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.envers.Config;
import org.springframework.data.envers.sample.Continent;
import org.springframework.data.envers.sample.ContinentRepository;
import org.springframework.data.envers.sample.Country;
import org.springframework.data.envers.sample.CountryRepository;
import org.springframework.data.envers.sample.License;
Expand All @@ -35,9 +38,12 @@

import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.history.RevisionMetadata.RevisionType.*;
Expand All @@ -48,6 +54,7 @@
* @author Oliver Gierke
* @author Jens Schauder
* @author Niklas Loechte
* @author Miguel Ángel Ruiz
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = Config.class)
Expand All @@ -57,12 +64,15 @@ class RepositoryIntegrationTests {
LicenseRepository licenseRepository;
@Autowired
CountryRepository countryRepository;
@Autowired
ContinentRepository continentRepository;

@BeforeEach
void setUp() {

licenseRepository.deleteAll();
countryRepository.deleteAll();
continentRepository.deleteAll();
}

@Test
Expand Down Expand Up @@ -94,6 +104,15 @@ void testLifeCycle() {

countryRepository.save(de);

Continent europe = new Continent();
europe.name = "Asia";

continentRepository.save(europe);

europe.name = "Europe";

continentRepository.save(europe);

Optional<Revision<Integer, License>> revision = licenseRepository.findLastChangeRevision(license.id);

assertThat(revision).hasValueSatisfying(it -> {
Expand All @@ -105,6 +124,17 @@ void testLifeCycle() {
assertThat(latestRevision.getRequiredRevisionNumber()).isEqualTo(it.getRequiredRevisionNumber());
assertThat(latestRevision.getEntity()).isEqualTo(it.getEntity());
});

Revisions<Integer, Continent> revisionsWithModifiedFlag = continentRepository.findRevisions(europe.id,
Set.of("name"));

assertThat(revisionsWithModifiedFlag).matches(revisions -> {
Collection<? extends Revision<Integer, Continent>> revisionCollection = IterableUtil.toCollection(revisions);

Set<String> continentNames = revisionCollection.stream().map(Revision::getEntity).map(continent -> continent.name)
.collect(Collectors.toSet());
return continentNames.size() == revisionCollection.size();
});
}

@Test // #1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2012-2024 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 org.hibernate.envers.Audited;

import java.time.Instant;

/**
* Sample domain class with modified flag.
*
* @author Miguel Ángel Ruiz
*/
@Audited(withModifiedFlag = true)
@Entity
public class Continent extends AbstractEntity {

public Instant timestamp;

public String name;

public String toString() {
return "Continent(timestamp=" + this.timestamp + ", name=" + this.name + ")";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2012-2024 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 org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.history.RevisionRepository;

/**
* Repository for {@link Continent} objects.
*
* @author Miguel Ángel Ruiz
*/
public interface ContinentRepository extends RevisionRepository<Continent, Long, Integer>, JpaRepository<Continent, Long> {

}