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; package org.hibernate.envers.query;
import org.hibernate.Incubating;
import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.NotAuditedException; import org.hibernate.envers.exception.NotAuditedException;
import org.hibernate.envers.internal.reader.AuditReaderImplementor; import org.hibernate.envers.internal.reader.AuditReaderImplementor;
@ -171,7 +172,8 @@ public class AuditQueryCreator {
auditReaderImplementor, auditReaderImplementor,
c, c,
selectEntitiesOnly, selectEntitiesOnly,
selectDeletedEntities selectDeletedEntities,
false
); );
} }
@ -211,7 +213,60 @@ public class AuditQueryCreator {
c, c,
entityName, entityName,
selectEntitiesOnly, 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.LockOptions;
import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.exception.NotAuditedException;
import org.hibernate.envers.internal.entities.EntityInstantiator; import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.reader.AuditReaderImplementor; import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.QueryBuilder; import org.hibernate.envers.internal.tools.query.QueryBuilder;
@ -79,6 +80,9 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
entityClassName = cls.getName(); entityClassName = cls.getName();
this.entityName = entityName; this.entityName = entityName;
versionsEntityName = enversService.getAuditEntitiesConfiguration().getAuditEntityName( 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 ); aliasToEntityNameMap.put( REFERENCED_ENTITY_ALIAS, entityName );
qb = new QueryBuilder( versionsEntityName, REFERENCED_ENTITY_ALIAS ); 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import javax.persistence.criteria.JoinType; import javax.persistence.criteria.JoinType;
@ -30,17 +31,20 @@ import org.hibernate.proxy.HibernateProxy;
public class RevisionsOfEntityQuery extends AbstractAuditQuery { public class RevisionsOfEntityQuery extends AbstractAuditQuery {
private final boolean selectEntitiesOnly; private final boolean selectEntitiesOnly;
private final boolean selectDeletedEntities; private final boolean selectDeletedEntities;
private final boolean selectRevisionInfoOnly;
public RevisionsOfEntityQuery( public RevisionsOfEntityQuery(
EnversService enversService, EnversService enversService,
AuditReaderImplementor versionsReader, AuditReaderImplementor versionsReader,
Class<?> cls, Class<?> cls,
boolean selectEntitiesOnly, boolean selectEntitiesOnly,
boolean selectDeletedEntities) { boolean selectDeletedEntities,
boolean selectRevisionInfoOnly) {
super( enversService, versionsReader, cls ); super( enversService, versionsReader, cls );
this.selectEntitiesOnly = selectEntitiesOnly; this.selectEntitiesOnly = selectEntitiesOnly;
this.selectDeletedEntities = selectDeletedEntities; this.selectDeletedEntities = selectDeletedEntities;
this.selectRevisionInfoOnly = selectRevisionInfoOnly && !selectEntitiesOnly;
} }
public RevisionsOfEntityQuery( public RevisionsOfEntityQuery(
@ -48,11 +52,13 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
AuditReaderImplementor versionsReader, AuditReaderImplementor versionsReader,
Class<?> cls, String entityName, Class<?> cls, String entityName,
boolean selectEntitiesOnly, boolean selectEntitiesOnly,
boolean selectDeletedEntities) { boolean selectDeletedEntities,
boolean selectRevisionInfoOnly) {
super( enversService, versionsReader, cls, entityName ); super( enversService, versionsReader, cls, entityName );
this.selectEntitiesOnly = selectEntitiesOnly; this.selectEntitiesOnly = selectEntitiesOnly;
this.selectDeletedEntities = selectDeletedEntities; this.selectDeletedEntities = selectDeletedEntities;
this.selectRevisionInfoOnly = selectRevisionInfoOnly && !selectEntitiesOnly;
} }
private Number getRevisionNumber(Map versionsEntity) { private Number getRevisionNumber(Map versionsEntity) {
@ -121,6 +127,9 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
if ( hasProjection() ) { if ( hasProjection() ) {
return queryResult; return queryResult;
} }
else if ( selectRevisionInfoOnly ) {
return queryResult.stream().map( e -> ( (Object[]) e )[1] ).collect( Collectors.toList() );
}
else { else {
List entities = new ArrayList(); List entities = new ArrayList();
String revisionTypePropertyName = verEntCfg.getRevisionTypePropName(); String revisionTypePropertyName = verEntCfg.getRevisionTypePropName();

View File

@ -7,14 +7,23 @@
package org.hibernate.envers.test.integration.auditReader; package org.hibernate.envers.test.integration.auditReader;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import org.hibernate.envers.enhanced.SequenceIdRevisionEntity;
import org.hibernate.envers.exception.NotAuditedException; import org.hibernate.envers.exception.NotAuditedException;
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
import org.hibernate.envers.test.Priority; import org.hibernate.envers.test.Priority;
import org.hibernate.testing.TestForIssue;
import org.junit.Test; 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). * A test which checks the correct behavior of AuditReader.isEntityClassAudited(Class entityClass).
* *
@ -45,13 +54,17 @@ public class AuditReaderAPITest extends BaseEnversJPAFunctionalTestCase {
ent1.setStr1( "str2" ); ent1.setStr1( "str2" );
ent2.setStr1( "str2" ); ent2.setStr1( "str2" );
em.getTransaction().commit(); em.getTransaction().commit();
em.getTransaction().begin();
ent1 = em.find( AuditedTestEntity.class, 1 );
em.remove( ent1 );
em.getTransaction().commit();
} }
@Test @Test
public void testIsEntityClassAuditedForAuditedEntity() { public void testIsEntityClassAuditedForAuditedEntity() {
assert getAuditReader().isEntityClassAudited( AuditedTestEntity.class ); assertTrue( getAuditReader().isEntityClassAudited( AuditedTestEntity.class ) );
assertEquals( Arrays.asList( 1, 2, 3 ), getAuditReader().getRevisions( AuditedTestEntity.class, 1 ) );
assert Arrays.asList( 1, 2 ).equals( getAuditReader().getRevisions( AuditedTestEntity.class, 1 ) );
} }
@Test @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
}
}
} }