Initial draft of Envers support for Spring Data JPA.

Added an implementation of the Spring Data Commons historiography API based on Hibernate Envers. The implementation is a contribution of @hygl to a large degree.
This commit is contained in:
Oliver Gierke
2012-03-21 14:53:45 +01:00
commit 5cf6501592
19 changed files with 1410 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012 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
*
* http://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.repository.support;
import org.hibernate.envers.DefaultRevisionEntity;
import org.springframework.data.repository.history.support.RevisionEntityInformation;
/**
* {@link RevisionEntityInformation} for {@link DefaultRevisionEntity}.
*
* @author Oliver Gierke
*/
class DefaultRevisionEntityInformation implements RevisionEntityInformation {
/*
* (non-Javadoc)
* @see org.springframework.data.repository.history.support.RevisionEntityInformation#getRevisionNumberType()
*/
public Class<?> getRevisionNumberType() {
return Integer.class;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.history.support.RevisionEntityInformation#isDefaultRevisionEntity()
*/
public boolean isDefaultRevisionEntity() {
return true;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.history.support.RevisionEntityInformation#getRevisionEntityClass()
*/
public Class<?> getRevisionEntityClass() {
return DefaultRevisionEntity.class;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2012 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
*
* http://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.repository.support;
import org.hibernate.envers.DefaultRevisionEntity;
import org.joda.time.DateTime;
import org.springframework.data.history.RevisionMetadata;
import org.springframework.util.Assert;
/**
* {@link RevisionMetadata} working with a {@link DefaultRevisionEntity}.
*
* @author Oliver Gierke
* @author Philip Huegelmeyer
*/
public class DefaultRevisionMetadata implements RevisionMetadata<Integer> {
private final DefaultRevisionEntity entity;
/**
* Creates a new {@link DefaultRevisionMetadata}.
*
* @param entity must not be {@literal null}.
*/
public DefaultRevisionMetadata(DefaultRevisionEntity entity) {
Assert.notNull(entity);
this.entity = entity;
}
/*
* (non-Javadoc)
* @see org.springframework.data.history.RevisionMetadata#getRevisionNumber()
*/
public Integer getRevisionNumber() {
return entity.getId();
}
/*
* (non-Javadoc)
* @see org.springframework.data.history.RevisionMetadata#getRevisionDate()
*/
public DateTime getRevisionDate() {
return new DateTime(entity.getTimestamp());
}
/*
* (non-Javadoc)
* @see org.springframework.data.history.RevisionMetadata#getDelegate()
*/
@SuppressWarnings("unchecked")
public <T> T getDelegate() {
return (T) entity;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2012 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
*
* http://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.repository.support;
import java.io.Serializable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.history.RevisionRepository;
/**
* Convenience interface to allow pulling in {@link JpaRepository} and {@link RevisionRepository} functionality in one
* go.
*
* @author Oliver Gierke
*/
@NoRepositoryBean
public interface EnversRevisionRepository<T, ID extends Serializable, N extends Number & Comparable<N>> extends
RevisionRepository<T, ID, N>, JpaRepository<T, ID> {
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2012 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
*
* http://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.repository.support;
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.hibernate.envers.DefaultRevisionEntity;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.core.GenericTypeResolver;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.history.RevisionRepository;
import org.springframework.data.repository.history.support.RevisionEntityInformation;
/**
* {@link FactoryBean} creating {@link RevisionRepository} instances.
*
* @author Oliver Gierke
*/
public class EnversRevisionRepositoryFactoryBean extends
JpaRepositoryFactoryBean<EnversRevisionRepository<Object, Serializable, Long>, Object, Serializable> {
private Class<?> revisionEntityClass;
/**
* Configures the revision entity class. Will default to {@link DefaultRevisionEntity}.
*
* @param revisionEntityClass
*/
public void setRevisionEntityClass(Class<?> revisionEntityClass) {
this.revisionEntityClass = revisionEntityClass;
}
/* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean#createRepositoryFactory(javax.persistence.EntityManager)
*/
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new RevisionRepositoryFactory(entityManager, revisionEntityClass);
}
/**
* Repository factory creating {@link RevisionRepository} instances.
*
* @author Oliver Gierke
*/
private static class RevisionRepositoryFactory extends JpaRepositoryFactory {
private final RevisionEntityInformation revisionEntityInformation;
/**
* Creates a new {@link RevisionRepositoryFactory} using the given {@link EntityManager} and revision entity class.
*
* @param entityManager must not be {@literal null}.
* @param revisionEntityClass can be {@literal null}, will default to {@link DefaultRevisionEntity}.
*/
public RevisionRepositoryFactory(EntityManager entityManager, Class<?> revisionEntityClass) {
super(entityManager);
revisionEntityClass = revisionEntityClass == null ? DefaultRevisionEntity.class : revisionEntityClass;
this.revisionEntityInformation = DefaultRevisionEntity.class.equals(revisionEntityClass) ? new DefaultRevisionEntityInformation()
: new ReflectionRevisionEntityInformation(revisionEntityClass);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.JpaRepositoryFactory#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata, javax.persistence.EntityManager)
*/
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
protected <T, ID extends Serializable> JpaRepository<?, ?> getTargetRepository(RepositoryMetadata metadata,
EntityManager entityManager) {
JpaEntityInformation<T, Serializable> entityInformation = (JpaEntityInformation<T, Serializable>) getEntityInformation(metadata
.getDomainClass());
return new EnversRevisionRepositoryImpl(entityInformation, revisionEntityInformation, entityManager);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.JpaRepositoryFactory#getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
*/
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return EnversRevisionRepositoryImpl.class;
}
/* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepository(java.lang.Class, java.lang.Object)
*/
@Override
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {
if (RevisionRepository.class.isAssignableFrom(repositoryInterface)) {
Class<?>[] typeArguments = GenericTypeResolver.resolveTypeArguments(repositoryInterface,
RevisionRepository.class);
Class<?> revisionNumberType = typeArguments[2];
if (!revisionEntityInformation.getRevisionNumberType().equals(revisionNumberType)) {
throw new IllegalStateException(String.format(
"Configured a revision entity type of %s with a revision type of %s "
+ "but the repository interface is typed to a revision type of %s!", repositoryInterface,
revisionEntityInformation.getRevisionNumberType(), revisionNumberType));
}
}
return super.getRepository(repositoryInterface, customImplementation);
}
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright 2012 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
*
* http://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.repository.support;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.persistence.EntityManager;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.AuditReaderFactory;
import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.RevisionNumber;
import org.hibernate.envers.RevisionTimestamp;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.history.AnnotationRevisionMetadata;
import org.springframework.data.history.Revision;
import org.springframework.data.history.RevisionMetadata;
import org.springframework.data.history.Revisions;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.history.RevisionRepository;
import org.springframework.data.repository.history.support.RevisionEntityInformation;
import org.springframework.util.Assert;
/**
* Repository implementation using Hibernate Envers to implement revision specific query methods.
*
* @author Oliver Gierke
* @author Philipp Huegelmeyer
*/
public class EnversRevisionRepositoryImpl<T, ID extends Serializable, N extends Number & Comparable<N>> extends
SimpleJpaRepository<T, ID> implements RevisionRepository<T, ID, N> {
private final EntityInformation<T, ?> entityInformation;
private final RevisionEntityInformation revisionEntityInformation;
private final EntityManager entityManager;
/**
* Creates a new {@link EnversRevisionRepositoryImpl} using the given {@link JpaEntityInformation},
* {@link RevisionEntityInformation} and {@link EntityManager}.
*
* @param entityInformation must not be {@literal null}.
* @param revisionEntityInformation must not be {@literal null}.
* @param entityManager must not be {@literal null}.
*/
public EnversRevisionRepositoryImpl(JpaEntityInformation<T, ?> entityInformation,
RevisionEntityInformation revisionEntityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
Assert.notNull(revisionEntityInformation);
this.entityInformation = entityInformation;
this.revisionEntityInformation = revisionEntityInformation;
this.entityManager = entityManager;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.history.RevisionRepository#findLastChangeRevision(java.io.Serializable)
*/
@SuppressWarnings("unchecked")
public Revision<N, T> findLastChangeRevision(ID id) {
Class<T> type = entityInformation.getJavaType();
AuditReader reader = AuditReaderFactory.get(entityManager);
List<Number> revisions = reader.getRevisions(type, id);
if (revisions.isEmpty()) {
return null;
}
N latestRevision = (N) revisions.get(revisions.size() - 1);
Class<?> revisionEntityClass = revisionEntityInformation.getRevisionEntityClass();
Object revisionEntity = reader.findRevision(revisionEntityClass, latestRevision);
RevisionMetadata<N> metadata = (RevisionMetadata<N>) getRevisionMetadata(revisionEntity);
return new Revision<N, T>(metadata, reader.find(type, id, latestRevision));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.history.RevisionRepository#findRevisions(java.io.Serializable)
*/
@SuppressWarnings("unchecked")
public Revisions<N, T> findRevisions(ID id) {
Class<T> type = entityInformation.getJavaType();
AuditReader reader = AuditReaderFactory.get(entityManager);
List<? extends Number> revisionNumbers = reader.getRevisions(type, id);
return getEntitiesForRevisions((List<N>) revisionNumbers, id, reader);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.history.RevisionRepository#findRevisions(java.io.Serializable, org.springframework.data.domain.Pageable)
*/
@SuppressWarnings("unchecked")
public Page<Revision<N, T>> findRevisions(ID id, Pageable pageable) {
Class<T> type = entityInformation.getJavaType();
AuditReader reader = AuditReaderFactory.get(entityManager);
List<Number> revisionNumbers = reader.getRevisions(type, id);
if (pageable.getOffset() > revisionNumbers.size()) {
return new PageImpl<Revision<N, T>>(Collections.<Revision<N, T>> emptyList(), pageable, 0);
}
int upperBound = pageable.getOffset() + pageable.getPageSize();
upperBound = upperBound > revisionNumbers.size() ? revisionNumbers.size() : upperBound;
List<? extends Number> subList = revisionNumbers.subList(pageable.getOffset(), upperBound);
Revisions<N, T> revisions = getEntitiesForRevisions((List<N>) subList, id, reader);
return new PageImpl<Revision<N, T>>(revisions.getContent(), pageable, revisionNumbers.size());
}
/**
* Returns the entities in the given revisions for the entitiy with the given id.
*
* @param revisionNumbers
* @param id
* @param reader
* @return
*/
@SuppressWarnings("unchecked")
private Revisions<N, T> getEntitiesForRevisions(List<N> revisionNumbers, ID id, AuditReader reader) {
Class<T> type = entityInformation.getJavaType();
Map<N, T> revisions = new HashMap<N, T>(revisionNumbers.size());
Class<?> revisionEntityClass = revisionEntityInformation.getRevisionEntityClass();
Map<Number, Object> revisionEntities = (Map<Number, Object>) reader.findRevisions(revisionEntityClass,
new HashSet<Number>(revisionNumbers));
for (Number number : revisionNumbers) {
revisions.put((N) number, reader.find(type, id, number));
}
return new Revisions<N, T>(toRevisions(revisions, revisionEntities));
}
@SuppressWarnings("unchecked")
private List<Revision<N, T>> toRevisions(Map<N, T> source, Map<Number, Object> revisionEntities) {
List<Revision<N, T>> result = new ArrayList<Revision<N, T>>();
for (Entry<N, T> revision : source.entrySet()) {
N revisionNumber = revision.getKey();
T entity = revision.getValue();
RevisionMetadata<N> metadata = (RevisionMetadata<N>) getRevisionMetadata(revisionEntities.get(revisionNumber));
result.add(new Revision<N, T>(metadata, entity));
}
Collections.sort(result);
return Collections.unmodifiableList(result);
}
/**
* Returns the {@link RevisionMetadata} wrapper depending on the type of the given object.
*
* @param object
* @return
*/
private RevisionMetadata<?> getRevisionMetadata(Object object) {
if (object instanceof DefaultRevisionEntity) {
return new DefaultRevisionMetadata((DefaultRevisionEntity) object);
} else {
return new AnnotationRevisionMetadata<N>(object, RevisionNumber.class, RevisionTimestamp.class);
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2012 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
*
* http://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.repository.support;
import org.hibernate.envers.RevisionNumber;
import org.springframework.data.repository.history.support.RevisionEntityInformation;
import org.springframework.data.util.AnnotationDetectionFieldCallback;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* {@link RevisionEntityInformation} that uses reflection to inspect a property annotated with {@link RevisionNumber} to
* find out about the revision number type.
*
* @author Oliver Gierke
*/
public class ReflectionRevisionEntityInformation implements RevisionEntityInformation {
private final Class<?> revisionEntityClass;
private final Class<?> revisionNumberType;
/**
* Creates a new {@link ReflectionRevisionEntityInformation} inspecting the given revision entity class.
*
* @param revisionEntityClass must not be {@literal null}.
*/
public ReflectionRevisionEntityInformation(Class<?> revisionEntityClass) {
Assert.notNull(revisionEntityClass);
AnnotationDetectionFieldCallback fieldCallback = new AnnotationDetectionFieldCallback(RevisionNumber.class);
ReflectionUtils.doWithFields(revisionEntityClass, fieldCallback);
this.revisionNumberType = fieldCallback.getType();
this.revisionEntityClass = revisionEntityClass;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.history.support.RevisionEntityInformation#getRevisionNumberType()
*/
public Class<?> getRevisionNumberType() {
return revisionNumberType;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.history.support.RevisionEntityInformation#isDefaultRevisionEntity()
*/
public boolean isDefaultRevisionEntity() {
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.history.support.RevisionEntityInformation#getRevisionEntityClass()
*/
public Class<?> getRevisionEntityClass() {
return revisionEntityClass;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2012 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
*
* http://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;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.hibernate.envers.strategy.ValidityAuditStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Spring JavaConfig configuration for general infrastructure.
*
* @author Oliver Gierke
*/
@Configuration
public class Config {
@Bean
public DataSource dataSource() throws SQLException {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}
@Bean
public PlatformTransactionManager transactionManager() throws SQLException {
EntityManagerFactory entityManagerFactory = entityManagerFactoryBean().getObject();
return new JpaTransactionManager(entityManagerFactory);
}
@Bean
public AbstractEntityManagerFactoryBean entityManagerFactoryBean() throws SQLException {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setDatabase(Database.H2);
jpaVendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setJpaVendorAdapter(jpaVendorAdapter);
bean.setPackagesToScan(Config.class.getPackage().getName());
bean.setDataSource(dataSource());
bean.setJpaPropertyMap(jpaProperties());
return bean;
}
private Map<String, String> jpaProperties() {
return Collections.singletonMap("org.hibernate.envers.audit_strategy", ValidityAuditStrategy.class.getName());
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2012 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
*
* http://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.repository.support;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.HashSet;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.envers.Config;
import org.springframework.data.envers.sample.Country;
import org.springframework.data.envers.sample.CountryRepository;
import org.springframework.data.envers.sample.License;
import org.springframework.data.envers.sample.LicenseRepository;
import org.springframework.data.history.Revision;
import org.springframework.data.history.Revisions;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
/**
* Integration tests for repositories.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class RepositoryIntegrationTest {
@ImportResource("classpath:application-context.xml")
@Configuration
static class RepoConfig extends Config {
}
@Autowired
LicenseRepository licenseRepository;
@Autowired
CountryRepository countryRepository;
@Before
public void setUp() {
licenseRepository.deleteAll();
countryRepository.deleteAll();
}
@Test
public void testname() {
License license = new License();
license.name = "Schnitzel";
licenseRepository.save(license);
Country de = new Country();
de.code = "de";
de.name = "Deutschland";
countryRepository.save(de);
Country se = new Country();
se.code = "se";
se.name = "Schweden";
countryRepository.save(se);
license.laender = new HashSet<Country>();
license.laender.addAll(Arrays.asList(de, se));
licenseRepository.save(license);
de.name = "Daenemark";
countryRepository.save(de);
Revision<Integer, License> revision = licenseRepository.findLastChangeRevision(license.id);
assertThat(revision, is(notNullValue()));
Page<Revision<Integer, License>> revisions = licenseRepository.findRevisions(license.id, new PageRequest(0, 10));
Revisions<Integer, License> wrapper = new Revisions<Integer, License>(revisions.getContent());
assertThat(wrapper.getLatestRevision(), is(revision));
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012 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
*
* http://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 javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import org.springframework.util.ObjectUtils;
@MappedSuperclass
abstract class AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof AbstractEntity)) {
return false;
}
AbstractEntity that = (AbstractEntity) obj;
return ObjectUtils.nullSafeEquals(this.id, that.id);
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2012 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
*
* http://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 javax.persistence.Entity;
import org.hibernate.envers.Audited;
/**
* Sample domain class.
*
* @author Oliver Gierke
*/
@Audited
@Entity
public class Country extends AbstractEntity {
public String code;
public String name;
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012 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
*
* http://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 Country} objects.
*
* @author Oliver Gierke
*/
public interface CountryRepository extends RevisionRepository<Country, Long, Integer>, JpaRepository<Country, Long> {
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012 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
*
* http://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 java.util.Set;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.Version;
import org.hibernate.envers.Audited;
/**
* Sample domain class.
*
* @author Philip Huegelmeyer
*/
@Audited
@Entity
public class License extends AbstractEntity {
@Version
public Integer version;
public String name;
@ManyToMany
public Set<Country> laender;
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2012 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
*
* http://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 License} objects.
*
* @author Oliver Gierke
*/
public interface LicenseRepository extends RevisionRepository<License, Long, Integer>, JpaRepository<License, Long> {
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd">
<jpa:repositories base-package="org.springframework.data.envers" factory-class="org.springframework.data.envers.repository.support.EnversRevisionRepositoryFactoryBean" />
</beans>

View File

@@ -0,0 +1,7 @@
log4j.rootCategory=WARN, console
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{ABSOLUTE} %5p %40.40c:%4L - %m%n
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.logger.org.springframework=INFO