HHH-7555 - Add ability to query revision entity instances of an entity class without instantiating the entity instances.

(backport from wip/6.0)
This commit is contained in:
Chris Cranford 2018-02-21 14:10:47 -05:00
parent 4a3f7c19c0
commit fea7f348ee
4 changed files with 120 additions and 7 deletions

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.envers.query;
import org.hibernate.Incubating;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.NotAuditedException;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
@ -171,7 +172,8 @@ public class AuditQueryCreator {
auditReaderImplementor,
c,
selectEntitiesOnly,
selectDeletedEntities
selectDeletedEntities,
false
);
}
@ -211,7 +213,60 @@ public class AuditQueryCreator {
c,
entityName,
selectEntitiesOnly,
selectDeletedEntities
selectDeletedEntities,
false
);
}
/**
* Creates a query that selects the revision entities associated with the specified entity. You may also
* specify whether the revision entities list should include those for deletions of the entity class.
*
* @param clazz Class of the entities for which to query.
* @param selectDeletedEntities If true, the result will include revision entities where deletions occurred.
*
* @return A query of revision entities based on the specified entity class. The results of the query will
* be stored in ascending order by the revision number unless an order is specified.
*
* @since 5.3
*/
@Incubating
public AuditQuery forRevisionsOfEntity(Class<?> clazz, boolean selectDeletedEntities) {
clazz = getTargetClassIfProxied( clazz );
return new RevisionsOfEntityQuery(
enversService,
auditReaderImplementor,
clazz,
false,
selectDeletedEntities,
true
);
}
/**
* Creates a query that selects the revision entities associated with the specified entity. You may also
* specify whether the revision entities list should include those for deletions of the entity class.
*
* @param clazz Class of the entities for which to query.
* @param entityName Name of the entity (for cases where it cannot be guessed based on class clazz).
* @param selectDeletedEntities If true, the result will include revision entities where deletions occurred.
*
* @return A query of revision entities based on the specified entity class. The results of the query will
* be stored in ascending order by the revision number unless an order is specified.
*
* @since 5.3
*/
@Incubating
public AuditQuery forRevisionsOfEntity(Class<?> clazz, String entityName, boolean selectDeletedEntities) {
clazz = getTargetClassIfProxied( clazz );
return new RevisionsOfEntityQuery(
enversService,
auditReaderImplementor,
clazz,
entityName,
false,
selectDeletedEntities,
true
);
}

View File

@ -21,6 +21,7 @@ import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.exception.NotAuditedException;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
@ -79,6 +80,9 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
entityClassName = cls.getName();
this.entityName = entityName;
versionsEntityName = enversService.getAuditEntitiesConfiguration().getAuditEntityName( entityName );
if ( !enversService.getEntitiesConfigurations().isVersioned( entityName ) ) {
throw new NotAuditedException( entityName, "Entity [" + entityName + "] is not versioned" );
}
aliasToEntityNameMap.put( REFERENCED_ENTITY_ALIAS, entityName );
qb = new QueryBuilder( versionsEntityName, REFERENCED_ENTITY_ALIAS );

View File

@ -9,6 +9,7 @@ package org.hibernate.envers.query.internal.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.persistence.criteria.JoinType;
@ -30,17 +31,20 @@ import org.hibernate.proxy.HibernateProxy;
public class RevisionsOfEntityQuery extends AbstractAuditQuery {
private final boolean selectEntitiesOnly;
private final boolean selectDeletedEntities;
private final boolean selectRevisionInfoOnly;
public RevisionsOfEntityQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
Class<?> cls,
boolean selectEntitiesOnly,
boolean selectDeletedEntities) {
boolean selectDeletedEntities,
boolean selectRevisionInfoOnly) {
super( enversService, versionsReader, cls );
this.selectEntitiesOnly = selectEntitiesOnly;
this.selectDeletedEntities = selectDeletedEntities;
this.selectRevisionInfoOnly = selectRevisionInfoOnly && !selectEntitiesOnly;
}
public RevisionsOfEntityQuery(
@ -48,11 +52,13 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
AuditReaderImplementor versionsReader,
Class<?> cls, String entityName,
boolean selectEntitiesOnly,
boolean selectDeletedEntities) {
boolean selectDeletedEntities,
boolean selectRevisionInfoOnly) {
super( enversService, versionsReader, cls, entityName );
this.selectEntitiesOnly = selectEntitiesOnly;
this.selectDeletedEntities = selectDeletedEntities;
this.selectRevisionInfoOnly = selectRevisionInfoOnly && !selectEntitiesOnly;
}
private Number getRevisionNumber(Map versionsEntity) {
@ -121,6 +127,9 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
if ( hasProjection() ) {
return queryResult;
}
else if ( selectRevisionInfoOnly ) {
return queryResult.stream().map( e -> ( (Object[]) e )[1] ).collect( Collectors.toList() );
}
else {
List entities = new ArrayList();
String revisionTypePropertyName = verEntCfg.getRevisionTypePropName();

View File

@ -7,14 +7,23 @@
package org.hibernate.envers.test.integration.auditReader;
import java.util.Arrays;
import java.util.List;
import javax.persistence.EntityManager;
import org.hibernate.envers.enhanced.SequenceIdRevisionEntity;
import org.hibernate.envers.exception.NotAuditedException;
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
import org.hibernate.envers.test.Priority;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* A test which checks the correct behavior of AuditReader.isEntityClassAudited(Class entityClass).
*
@ -45,13 +54,17 @@ public class AuditReaderAPITest extends BaseEnversJPAFunctionalTestCase {
ent1.setStr1( "str2" );
ent2.setStr1( "str2" );
em.getTransaction().commit();
em.getTransaction().begin();
ent1 = em.find( AuditedTestEntity.class, 1 );
em.remove( ent1 );
em.getTransaction().commit();
}
@Test
public void testIsEntityClassAuditedForAuditedEntity() {
assert getAuditReader().isEntityClassAudited( AuditedTestEntity.class );
assert Arrays.asList( 1, 2 ).equals( getAuditReader().getRevisions( AuditedTestEntity.class, 1 ) );
assertTrue( getAuditReader().isEntityClassAudited( AuditedTestEntity.class ) );
assertEquals( Arrays.asList( 1, 2, 3 ), getAuditReader().getRevisions( AuditedTestEntity.class, 1 ) );
}
@Test
@ -68,5 +81,37 @@ public class AuditReaderAPITest extends BaseEnversJPAFunctionalTestCase {
}
}
@Test
@TestForIssue( jiraKey = "HHH-7555" )
public void testFindRevisionEntitiesWithoutDeletions() {
List<?> revisionInfos = getAuditReader().createQuery()
.forRevisionsOfEntity( AuditedTestEntity.class, false )
.getResultList();
assertEquals( 2, revisionInfos.size() );
revisionInfos.forEach( e -> assertTyping( SequenceIdRevisionEntity.class, e ) );
}
@Test
@TestForIssue( jiraKey = "HHH-7555" )
public void testFindRevisionEntitiesWithDeletions() {
List<?> revisionInfos = getAuditReader().createQuery()
.forRevisionsOfEntity( AuditedTestEntity.class, true )
.getResultList();
assertEquals( 3, revisionInfos.size() );
revisionInfos.forEach( e -> assertTyping( SequenceIdRevisionEntity.class, e ) );
}
@Test
@TestForIssue( jiraKey = "HHH-7555" )
public void testFindRevisionEntitiesNonAuditedEntity() {
try {
List<?> revisionInfos = getAuditReader().createQuery()
.forRevisionsOfEntity( NotAuditedTestEntity.class, false )
.getResultList();
fail( "Expected a NotAuditedException" );
}
catch ( NotAuditedException e ) {
// expected
}
}
}