HHH-3555 - Extendend Envers Criteria API to express relation traversion

(traverseRelation) with different Join Types. Implementation is done for
inner joins and will be extended to support further join types in future
commits.
This commit is contained in:
Felix Feisst 2016-02-08 21:42:26 +01:00 committed by Steve Ebersole
parent 0a2a709f9e
commit 83f060bea7
42 changed files with 1012 additions and 98 deletions

View File

@ -94,7 +94,12 @@ public class EntitiesConfigurations {
}
public RelationDescription getRelationDescription(String entityName, String propertyName) {
final EntityConfiguration entCfg = entitiesConfigurations.get( entityName );
final EntityConfiguration entCfg;
if ( isVersioned( entityName ) ) {
entCfg = get( entityName );
} else {
entCfg = getNotVersionEntityConfiguration( entityName );
}
final RelationDescription relDesc = entCfg.getRelationDescription( propertyName );
if ( relDesc != null ) {
return relDesc;

View File

@ -70,7 +70,7 @@ public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGen
private QueryBuilder commonQueryPart(String versionsReferencedEntityName) {
// SELECT e FROM versionsEntity e
final QueryBuilder qb = new QueryBuilder( versionsReferencedEntityName, REFERENCED_ENTITY_ALIAS );
qb.addProjection( null, REFERENCED_ENTITY_ALIAS, false, false );
qb.addProjection( null, REFERENCED_ENTITY_ALIAS, null, false );
// WHERE
// e.id_ref_ed = :id_ref_ed
referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( qb.getRootParameters(), null, true );

View File

@ -70,7 +70,7 @@ public final class OneEntityQueryGenerator extends AbstractRelationQueryGenerato
private QueryBuilder commonQueryPart(String versionsMiddleEntityName) {
// SELECT ee FROM middleEntity ee
final QueryBuilder qb = new QueryBuilder( versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS );
qb.addProjection( null, MIDDLE_ENTITY_ALIAS, false, false );
qb.addProjection( null, MIDDLE_ENTITY_ALIAS, null, false );
// WHERE
// ee.originalId.id_ref_ing = :id_ref_ing
referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery(

View File

@ -115,11 +115,11 @@ public final class ThreeEntityQueryGenerator extends AbstractRelationQueryGenera
final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName;
// SELECT new list(ee) FROM middleEntity ee
final QueryBuilder qb = new QueryBuilder( versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS );
qb.addFrom( referencedIdData.getAuditEntityName(), REFERENCED_ENTITY_ALIAS );
qb.addFrom( indexIdData.getAuditEntityName(), INDEX_ENTITY_ALIAS );
qb.addFrom( referencedIdData.getAuditEntityName(), REFERENCED_ENTITY_ALIAS, false );
qb.addFrom( indexIdData.getAuditEntityName(), INDEX_ENTITY_ALIAS, false );
qb.addProjection(
"new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS + ", " + INDEX_ENTITY_ALIAS,
false, false
null, false
);
// WHERE
final Parameters rootParameters = qb.getRootParameters();

View File

@ -82,8 +82,8 @@ public final class TwoEntityOneAuditedQueryGenerator extends AbstractRelationQue
final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName;
// SELECT new list(ee) FROM middleEntity ee
final QueryBuilder qb = new QueryBuilder( versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS );
qb.addFrom( referencedIdData.getEntityName(), REFERENCED_ENTITY_ALIAS );
qb.addProjection( "new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS, false, false );
qb.addFrom( referencedIdData.getEntityName(), REFERENCED_ENTITY_ALIAS, false );
qb.addProjection( "new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS, null, false );
// WHERE
final Parameters rootParameters = qb.getRootParameters();
// ee.id_ref_ed = e.id_ref_ed

View File

@ -96,8 +96,8 @@ public final class TwoEntityQueryGenerator extends AbstractRelationQueryGenerato
final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName;
// SELECT new list(ee) FROM middleEntity ee
QueryBuilder qb = new QueryBuilder( versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS );
qb.addFrom( referencedIdData.getAuditEntityName(), REFERENCED_ENTITY_ALIAS );
qb.addProjection( "new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS, false, false );
qb.addFrom( referencedIdData.getAuditEntityName(), REFERENCED_ENTITY_ALIAS, false );
qb.addProjection( "new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS, null, false );
// WHERE
final Parameters rootParameters = qb.getRootParameters();
// ee.id_ref_ed = e.id_ref_ed

View File

@ -164,6 +164,17 @@ public class Parameters {
expressions.add( expression.toString() );
}
// compare properties from two different entities (aliases)
public void addWhere(final String aliasLeft, final String left, final String op, final String aliasRight, final String right) {
final StringBuilder expression = new StringBuilder();
expression.append( aliasLeft ).append( '.' ).append( left );
expression.append( ' ' ).append( op ).append( ' ' );
expression.append( aliasRight ).append( '.' ).append( right );
expressions.add( expression.toString() );
}
public void addWhereWithFunction(String left, String leftFunction, String op, Object paramValue){
final String paramName = generateQueryParam();
localQueryParamValues.put( paramName, paramValue );

View File

@ -17,7 +17,7 @@ import org.hibernate.envers.RevisionType;
import org.hibernate.envers.internal.entities.RevisionTypeType;
import org.hibernate.envers.internal.tools.MutableInteger;
import org.hibernate.envers.internal.tools.StringTools;
import org.hibernate.envers.tools.Pair;
import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.type.CustomType;
/**
@ -39,18 +39,18 @@ public class QueryBuilder {
*/
private final MutableInteger paramCounter;
/**
* Main "where" parameters for this query.
* "where" parameters for this query. Each parameter element of the list for one alias from the "from" part.
*/
private final Parameters rootParameters;
private final List<Parameters> parameters = new ArrayList<Parameters>();
/**
* A list of pairs (from entity name, alias name).
* A list of triples (from entity name, alias name, whether to select the entity).
*/
private final List<Pair<String, String>> froms;
private final List<Triple<String, String, Boolean>> froms;
/**
* A list of pairs (property name, order ascending?).
* A list of triples (alias, property name, order ascending?).
*/
private final List<Pair<String, Boolean>> orders;
private final List<Triple<String, String, Boolean>> orders;
/**
* A list of complete projection definitions: either a sole property name, or a function(property name).
*/
@ -70,13 +70,14 @@ public class QueryBuilder {
this.aliasCounter = aliasCounter;
this.paramCounter = paramCounter;
rootParameters = new Parameters( alias, "and", paramCounter );
final Parameters rootParameters = new Parameters( alias, "and", paramCounter );
parameters.add( rootParameters );
froms = new ArrayList<Pair<String, String>>();
orders = new ArrayList<Pair<String, Boolean>>();
froms = new ArrayList<Triple<String, String, Boolean>>();
orders = new ArrayList<Triple<String, String, Boolean>>();
projections = new ArrayList<String>();
addFrom( entityName, alias );
addFrom( entityName, alias, true );
}
// Only for deep copy purpose.
@ -85,10 +86,12 @@ public class QueryBuilder {
this.alias = other.alias;
this.aliasCounter = other.aliasCounter.deepCopy();
this.paramCounter = other.paramCounter.deepCopy();
this.rootParameters = other.rootParameters.deepCopy();
for (final Parameters params : other.parameters) {
this.parameters.add( params.deepCopy() );
}
froms = new ArrayList<Pair<String, String>>( other.froms );
orders = new ArrayList<Pair<String, Boolean>>( other.orders );
froms = new ArrayList<Triple<String, String, Boolean>>( other.froms );
orders = new ArrayList<Triple<String, String, Boolean>>( other.orders );
projections = new ArrayList<String>( other.projections );
}
@ -96,17 +99,25 @@ public class QueryBuilder {
return new QueryBuilder( this );
}
/**
* @return the main alias of this query builder
*/
public String getAlias() {
return alias;
}
/**
* Add an entity from which to select.
*
* @param entityName Name of the entity from which to select.
* @param alias Alias of the entity. Should be different than all other aliases.
* @param select whether the entity should be selected
*/
public void addFrom(String entityName, String alias) {
froms.add( Pair.make( entityName, alias ) );
public void addFrom(String entityName, String alias, boolean select) {
froms.add( Triple.make( entityName, alias, select ) );
}
private String generateAlias() {
public String generateAlias() {
return "_e" + aliasCounter.getAndIncrease();
}
@ -130,26 +141,27 @@ public class QueryBuilder {
}
public Parameters getRootParameters() {
return rootParameters;
return parameters.get( 0 );
}
public void addOrder(String propertyName, boolean ascending) {
orders.add( Pair.make( propertyName, ascending ) );
public Parameters addParameters(final String alias) {
final Parameters result = new Parameters( alias, Parameters.AND, paramCounter);
parameters.add( result );
return result;
}
public void addProjection(String function, String propertyName, boolean distinct) {
addProjection( function, propertyName, distinct, true );
public void addOrder(String alias, String propertyName, boolean ascending) {
orders.add( Triple.make( alias, propertyName, ascending ) );
}
public void addProjection(String function, String propertyName, boolean distinct, boolean addAlias) {
public void addProjection(String function, String alias, String propertyName, boolean distinct) {
final String effectivePropertyName = propertyName == null ? "" : ".".concat( propertyName );
if ( function == null ) {
projections.add( (distinct ? "distinct " : "") + (addAlias ? alias + "." : "") + propertyName );
}
else {
projections.add( (distinct ? "distinct " : "") + alias + effectivePropertyName );
} else {
projections.add(
function + "(" + (distinct ? "distinct " : "") + (addAlias ?
alias + "." :
"") + propertyName + ")"
function + "(" + (distinct ? "distinct " : "") + alias +
effectivePropertyName + ")"
);
}
}
@ -169,15 +181,23 @@ public class QueryBuilder {
}
else {
// all aliases separated with commas
StringTools.append( sb, getAliasList().iterator(), ", " );
StringTools.append( sb, getSelectAliasList().iterator(), ", " );
}
sb.append( " from " );
// all from entities with aliases, separated with commas
StringTools.append( sb, getFromList().iterator(), ", " );
// where part - rootParameters
if ( !rootParameters.isEmpty() ) {
sb.append( " where " );
rootParameters.build( sb, queryParamValues );
boolean first = true;
for (final Parameters params : parameters) {
if (!params.isEmpty()) {
if (first) {
sb.append( " where " );
first = false;
} else {
sb.append( " and " );
}
params.build( sb, queryParamValues );
}
}
// orders
if ( orders.size() > 0 ) {
@ -186,10 +206,12 @@ public class QueryBuilder {
}
}
private List<String> getAliasList() {
private List<String> getSelectAliasList() {
final List<String> aliasList = new ArrayList<String>();
for ( Pair<String, String> from : froms ) {
aliasList.add( from.getSecond() );
for ( Triple<String, String, Boolean> from : froms ) {
if ( from.getThird() ) {
aliasList.add( from.getSecond() );
}
}
return aliasList;
@ -201,7 +223,7 @@ public class QueryBuilder {
private List<String> getFromList() {
final List<String> fromList = new ArrayList<String>();
for ( Pair<String, String> from : froms ) {
for ( Triple<String, String, Boolean> from : froms ) {
fromList.add( from.getFirst() + " " + from.getSecond() );
}
@ -210,8 +232,8 @@ public class QueryBuilder {
private List<String> getOrderList() {
final List<String> orderList = new ArrayList<String>();
for ( Pair<String, Boolean> order : orders ) {
orderList.add( alias + "." + order.getFirst() + " " + (order.getSecond() ? "asc" : "desc") );
for ( Triple<String, String, Boolean> order : orders ) {
orderList.add( order.getFirst() + "." + order.getSecond() + " " + (order.getThird() ? "asc" : "desc") );
}
return orderList;

View File

@ -0,0 +1,56 @@
/**
*
*/
package org.hibernate.envers.query;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.LockMode;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.order.AuditOrder;
import org.hibernate.envers.query.projection.AuditProjection;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
public interface AuditAssociationQuery<Q extends AuditQuery> extends AuditQuery {
@Override
AuditAssociationQuery<Q> add(AuditCriterion criterion);
@Override
AuditAssociationQuery<Q> addOrder(AuditOrder order);
@Override
AuditAssociationQuery<Q> addProjection(AuditProjection projection);
@Override
AuditAssociationQuery<Q> setMaxResults(int maxResults);
@Override
AuditAssociationQuery<Q> setFirstResult(int firstResult);
@Override
AuditAssociationQuery<Q> setCacheable(boolean cacheable);
@Override
AuditAssociationQuery<Q> setCacheRegion(String cacheRegion);
@Override
AuditAssociationQuery<Q> setComment(String comment);
@Override
AuditAssociationQuery<Q> setFlushMode(FlushMode flushMode);
@Override
AuditAssociationQuery<Q> setCacheMode(CacheMode cacheMode);
@Override
AuditAssociationQuery<Q> setTimeout(int timeout);
@Override
AuditAssociationQuery<Q> setLockMode(LockMode lockMode);
Q up();
}

View File

@ -19,6 +19,8 @@ import org.hibernate.envers.query.internal.property.EntityPropertyName;
import org.hibernate.envers.query.internal.property.RevisionNumberPropertyName;
import org.hibernate.envers.query.internal.property.RevisionPropertyPropertyName;
import org.hibernate.envers.query.internal.property.RevisionTypePropertyName;
import org.hibernate.envers.query.projection.AuditProjection;
import org.hibernate.envers.query.projection.internal.EntityAuditProjection;
/**
* TODO: ilike
@ -114,4 +116,13 @@ public class AuditEntity {
public static AuditDisjunction disjunction() {
return new AuditDisjunction();
}
/**
* Adds a projection to the current entity itself. Useful for
* selecting entities which are reached through associations within the query.
* @param distinct whether to distinct select the entity
*/
public static AuditProjection selectEntity(boolean distinct) {
return new EntityAuditProjection( distinct );
}
}

View File

@ -9,6 +9,7 @@ package org.hibernate.envers.query;
import java.util.List;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.criteria.JoinType;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
@ -27,6 +28,8 @@ public interface AuditQuery {
Object getSingleResult() throws AuditException, NonUniqueResultException, NoResultException;
AuditAssociationQuery<? extends AuditQuery> traverseRelation(String associationName, JoinType joinType);
AuditQuery add(AuditCriterion criterion);
AuditQuery addProjection(AuditProjection projection);

View File

@ -49,6 +49,7 @@ public class AggregatedAuditExpression implements AuditCriterion, ExtendableCrit
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
String propertyName = CriteriaTools.determinePropertyName(
@ -68,17 +69,17 @@ public class AggregatedAuditExpression implements AuditCriterion, ExtendableCrit
// Adding all specified conditions both to the main query, as well as to the
// aggregated one.
for ( AuditCriterion versionsCriteria : criterions ) {
versionsCriteria.addToQuery( enversService, versionsReader, entityName, qb, subParams );
versionsCriteria.addToQuery( enversService, versionsReader, entityName, subQb, subQb.getRootParameters() );
versionsCriteria.addToQuery( enversService, versionsReader, entityName, qb.getAlias(), qb, subParams );
versionsCriteria.addToQuery( enversService, versionsReader, entityName, subQb.getAlias(), subQb, subQb.getRootParameters() );
}
// Setting the desired projection of the aggregated query
switch ( mode ) {
case MIN:
subQb.addProjection( "min", propertyName, false );
subQb.addProjection( "min", subQb.getAlias(), propertyName, false );
break;
case MAX:
subQb.addProjection( "max", propertyName, false );
subQb.addProjection( "max", subQb.getAlias(), propertyName, false );
}
// Correlating subquery with the outer query by entity id. See JIRA HHH-7827.

View File

@ -35,6 +35,7 @@ public class AuditConjunction implements AuditCriterion, ExtendableCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
Parameters andParameters = parameters.addSubParameters( Parameters.AND );
@ -44,7 +45,7 @@ public class AuditConjunction implements AuditCriterion, ExtendableCriterion {
}
else {
for ( AuditCriterion criterion : criterions ) {
criterion.addToQuery( enversService, versionsReader, entityName, qb, andParameters );
criterion.addToQuery( enversService, versionsReader, entityName, alias, qb, andParameters );
}
}
}

View File

@ -19,6 +19,7 @@ public interface AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters);
}

View File

@ -35,6 +35,7 @@ public class AuditDisjunction implements AuditCriterion, ExtendableCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
Parameters orParameters = parameters.addSubParameters( Parameters.OR );
@ -44,7 +45,7 @@ public class AuditDisjunction implements AuditCriterion, ExtendableCriterion {
}
else {
for ( AuditCriterion criterion : criterions ) {
criterion.addToQuery( enversService, versionsReader, entityName, qb, orParameters );
criterion.addToQuery( enversService, versionsReader, entityName, alias, qb, orParameters );
}
}
}

View File

@ -10,6 +10,7 @@ import java.util.Collection;
import org.hibernate.criterion.MatchMode;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.envers.query.criteria.internal.BetweenAuditExpression;
import org.hibernate.envers.query.criteria.internal.IlikeAuditExpression;
@ -275,4 +276,10 @@ public class AuditProperty<T> implements AuditProjection {
public AuditOrder desc() {
return new PropertyAuditOrder( propertyNameGetter, false );
}
@Override
public Object convertQueryResult(EnversService enversService, EntityInstantiator entityInstantiator, String entityName, Number revision, Object value) {
return value;
}
}

View File

@ -31,6 +31,7 @@ public class BetweenAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
String propertyName = CriteriaTools.determinePropertyName(

View File

@ -31,6 +31,7 @@ public class IdentifierEqAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
enversService.getEntitiesConfigurations().get( entityName )

View File

@ -27,7 +27,7 @@ public class IlikeAuditExpression implements AuditCriterion {
public void addToQuery(
EnversService enversService,
AuditReaderImplementor versionsReader, String entityName,
QueryBuilder qb, Parameters parameters) {
String alias, QueryBuilder qb, Parameters parameters) {
String propertyName = CriteriaTools.determinePropertyName(
enversService,

View File

@ -29,6 +29,7 @@ public class InAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
String propertyName = CriteriaTools.determinePropertyName(

View File

@ -30,11 +30,12 @@ public class LogicalAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
Parameters opParameters = parameters.addSubParameters( op );
lhs.addToQuery( enversService, versionsReader, entityName, qb, opParameters.addSubParameters( "and" ) );
rhs.addToQuery( enversService, versionsReader, entityName, qb, opParameters.addSubParameters( "and" ) );
lhs.addToQuery( enversService, versionsReader, entityName, alias, qb, opParameters.addSubParameters( "and" ) );
rhs.addToQuery( enversService, versionsReader, entityName, alias, qb, opParameters.addSubParameters( "and" ) );
}
}

View File

@ -26,8 +26,9 @@ public class NotAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
criterion.addToQuery( enversService, versionsReader, entityName, qb, parameters.addNegatedParameters() );
criterion.addToQuery( enversService, versionsReader, entityName, alias, qb, parameters.addNegatedParameters() );
}
}

View File

@ -28,6 +28,7 @@ public class NotNullAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
String propertyName = CriteriaTools.determinePropertyName(

View File

@ -28,6 +28,7 @@ public class NullAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
String propertyName = CriteriaTools.determinePropertyName(

View File

@ -28,8 +28,10 @@ public class PropertyAuditExpression implements AuditCriterion {
}
public void addToQuery(
EnversService enversService, AuditReaderImplementor versionsReader,
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
String propertyName = CriteriaTools.determinePropertyName(

View File

@ -34,6 +34,7 @@ public class RelatedAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
String propertyName = CriteriaTools.determinePropertyName(

View File

@ -29,6 +29,7 @@ public class RevisionTypeAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
parameters.addWhereWithParam( enversService.getAuditEntitiesConfiguration().getRevisionTypePropName(), op, value );

View File

@ -33,6 +33,7 @@ public class SimpleAuditExpression implements AuditCriterion {
EnversService enversService,
AuditReaderImplementor versionsReader,
String entityName,
String alias,
QueryBuilder qb,
Parameters parameters) {
String propertyName = CriteriaTools.determinePropertyName(

View File

@ -7,9 +7,13 @@
package org.hibernate.envers.query.internal.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.criteria.JoinType;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
@ -22,6 +26,7 @@ import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.AuditAssociationQuery;
import org.hibernate.envers.query.AuditQuery;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.criteria.internal.CriteriaTools;
@ -35,7 +40,7 @@ import static org.hibernate.envers.internal.entities.mapper.relation.query.Query
* @author Adam Warski (adam at warski dot org)
* @author HernпїЅn Chanfreau
*/
public abstract class AbstractAuditQuery implements AuditQuery {
public abstract class AbstractAuditQuery implements AuditQueryImplementor {
protected EntityInstantiator entityInstantiator;
protected List<AuditCriterion> criterions;
@ -44,12 +49,15 @@ public abstract class AbstractAuditQuery implements AuditQuery {
protected String versionsEntityName;
protected QueryBuilder qb;
protected boolean hasProjection;
protected boolean hasOrder;
protected final EnversService enversService;
protected final AuditReaderImplementor versionsReader;
protected final List<AuditAssociationQueryImplementor<?>> associationQueries = new ArrayList<AuditAssociationQueryImplementor<?>>();
protected final Map<String, AuditAssociationQueryImplementor<AuditQueryImplementor>> associationQueryMap = new HashMap<String, AuditAssociationQueryImplementor<AuditQueryImplementor>>();
protected final List<Pair<String, AuditProjection>> projections = new ArrayList<Pair<String,AuditProjection>>();
protected AbstractAuditQuery(
EnversService enversService,
AuditReaderImplementor versionsReader,
@ -116,17 +124,26 @@ public abstract class AbstractAuditQuery implements AuditQuery {
public AuditQuery addProjection(AuditProjection projection) {
Triple<String, String, Boolean> projectionData = projection.getData( enversService );
hasProjection = true;
registerProjection( entityName, projection );
String propertyName = CriteriaTools.determinePropertyName(
enversService,
versionsReader,
entityName,
projectionData.getSecond()
);
qb.addProjection( projectionData.getFirst(), propertyName, projectionData.getThird() );
qb.addProjection( projectionData.getFirst(), REFERENCED_ENTITY_ALIAS, propertyName, projectionData.getThird() );
return this;
}
@Override
public void registerProjection(String entityName, AuditProjection projection) {
projections.add( Pair.make( entityName, projection ) );
}
protected boolean hasProjection() {
return !projections.isEmpty();
}
public AuditQuery addOrder(AuditOrder order) {
hasOrder = true;
Pair<String, Boolean> orderData = order.getData( enversService );
@ -136,10 +153,21 @@ public abstract class AbstractAuditQuery implements AuditQuery {
entityName,
orderData.getFirst()
);
qb.addOrder( propertyName, orderData.getSecond() );
qb.addOrder( REFERENCED_ENTITY_ALIAS, propertyName, orderData.getSecond() );
return this;
}
@Override
public AuditAssociationQuery<? extends AuditQuery> traverseRelation(String associationName, JoinType joinType) {
AuditAssociationQueryImplementor<AuditQueryImplementor> result = associationQueryMap.get( associationName );
if (result == null) {
result = new AuditAssociationQueryImplementor<AuditQueryImplementor>( enversService, versionsReader, this, qb, entityName, associationName, joinType, REFERENCED_ENTITY_ALIAS );
associationQueries.add( result );
associationQueryMap.put( associationName, result );
}
return result;
}
// Query properties
private Integer maxResults;
@ -248,4 +276,29 @@ public abstract class AbstractAuditQuery implements AuditQuery {
query.setLockMode( REFERENCED_ENTITY_ALIAS, lockOptions.getLockMode() );
}
}
protected List applyProjections(final List queryResult, final Number revision) {
final List result = new ArrayList( queryResult.size() );
if ( hasProjection() ) {
for (final Object qr : queryResult) {
if ( projections.size() == 1 ) {
// qr is the value of the projection itself
final Pair<String, AuditProjection> projection = projections.get( 0 );
result.add( projection.getSecond().convertQueryResult( enversService, entityInstantiator, projection.getFirst(), revision, qr ) );
} else {
// qr is an array where each of its components holds the value of corresponding projection
Object[] qresults = (Object[]) qr;
Object[] tresults = new Object[qresults.length];
for ( int i = 0; i < qresults.length; i++ ) {
final Pair<String, AuditProjection> projection = projections.get( i );
tresults[i] = projection.getSecond().convertQueryResult( enversService, entityInstantiator, projection.getFirst(), revision, qresults[i] );
}
result.add( tresults );
}
}
} else {
entityInstantiator.addInstancesFromVersionsEntities( entityName, result, queryResult, revision );
}
return result;
}
}

View File

@ -0,0 +1,263 @@
/**
*
*/
package org.hibernate.envers.query.internal.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.criteria.JoinType;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.LockMode;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.AuditAssociationQuery;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.criteria.internal.CriteriaTools;
import org.hibernate.envers.query.order.AuditOrder;
import org.hibernate.envers.query.projection.AuditProjection;
import org.hibernate.envers.tools.Pair;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
public class AuditAssociationQueryImplementor<Q extends AuditQueryImplementor> implements AuditAssociationQuery<Q>, AuditQueryImplementor {
private final EnversService enversService;
private final AuditReaderImplementor auditReader;
private final Q parent;
private final QueryBuilder queryBuilder;
private final JoinType joinType;
private final String entityName;
private final IdMapper ownerAssociationIdMapper;
private final String ownerAlias;
private final String alias;
private final List<AuditCriterion> criterions = new ArrayList<AuditCriterion>();
private final Parameters parameters;
private final List<AuditAssociationQueryImplementor<?>> associationQueries = new ArrayList<AuditAssociationQueryImplementor<?>>();
private final Map<String, AuditAssociationQueryImplementor<AuditAssociationQueryImplementor<Q>>> associationQueryMap = new HashMap<String, AuditAssociationQueryImplementor<AuditAssociationQueryImplementor<Q>>>();
private boolean hasProjections;
private boolean hasOrders;
public AuditAssociationQueryImplementor(final EnversService enversService, final AuditReaderImplementor auditReader, final Q parent,
final QueryBuilder queryBuilder, final String ownerEntityName, final String propertyName, final JoinType joinType, final String ownerAlias) {
this.enversService = enversService;
this.auditReader = auditReader;
this.parent = parent;
this.queryBuilder = queryBuilder;
this.joinType = joinType;
final RelationDescription relationDescription = CriteriaTools.getRelatedEntity( enversService, ownerEntityName, propertyName );
if ( relationDescription == null ) {
throw new IllegalArgumentException( "Property " + propertyName + " of entity " + ownerEntityName + " is not a valid association for queries" );
}
this.entityName = relationDescription.getToEntityName();
this.ownerAssociationIdMapper = relationDescription.getIdMapper();
this.ownerAlias = ownerAlias;
alias = queryBuilder.generateAlias();
parameters = queryBuilder.addParameters( alias );
}
@Override
public List getResultList() throws AuditException {
return parent.getResultList();
}
@Override
public Object getSingleResult() throws AuditException, NonUniqueResultException, NoResultException {
return parent.getSingleResult();
}
@Override
public AuditAssociationQueryImplementor<AuditAssociationQueryImplementor<Q>> traverseRelation(String associationName, JoinType joinType) {
AuditAssociationQueryImplementor<AuditAssociationQueryImplementor<Q>> result = associationQueryMap.get( associationName );
if (result == null) {
result = new AuditAssociationQueryImplementor<AuditAssociationQueryImplementor<Q>>(
enversService, auditReader, this, queryBuilder, entityName, associationName, joinType, alias );
associationQueries.add( result );
associationQueryMap.put( associationName, result );
}
return result;
}
@Override
public AuditAssociationQueryImplementor<Q> add(AuditCriterion criterion) {
criterions.add( criterion );
return this;
}
@Override
public AuditAssociationQueryImplementor<Q> addProjection(AuditProjection projection) {
hasProjections = true;
Triple<String, String, Boolean> projectionData = projection.getData( enversService );
String propertyName = CriteriaTools.determinePropertyName( enversService, auditReader, entityName, projectionData.getSecond() );
queryBuilder.addProjection( projectionData.getFirst(), alias, propertyName, projectionData.getThird() );
registerProjection( entityName, projection );
return this;
}
@Override
public AuditAssociationQueryImplementor<Q> addOrder(AuditOrder order) {
hasOrders = true;
Pair<String, Boolean> orderData = order.getData( enversService );
String propertyName = CriteriaTools.determinePropertyName( enversService, auditReader, entityName, orderData.getFirst() );
queryBuilder.addOrder( alias, propertyName, orderData.getSecond() );
return this;
}
@Override
public AuditAssociationQueryImplementor<Q> setMaxResults(int maxResults) {
parent.setMaxResults( maxResults );
return this;
}
@Override
public AuditAssociationQueryImplementor<Q> setFirstResult(int firstResult) {
parent.setFirstResult( firstResult );
return this;
}
@Override
public AuditAssociationQueryImplementor<Q> setCacheable(boolean cacheable) {
parent.setCacheable( cacheable );
return this;
}
@Override
public AuditAssociationQueryImplementor<Q> setCacheRegion(String cacheRegion) {
parent.setCacheRegion( cacheRegion );
return this;
}
@Override
public AuditAssociationQueryImplementor<Q> setComment(String comment) {
parent.setComment( comment );
return this;
}
@Override
public AuditAssociationQueryImplementor<Q> setFlushMode(FlushMode flushMode) {
parent.setFlushMode( flushMode );
return this;
}
@Override
public AuditAssociationQueryImplementor<Q> setCacheMode(CacheMode cacheMode) {
parent.setCacheMode( cacheMode );
return this;
}
@Override
public AuditAssociationQueryImplementor<Q> setTimeout(int timeout) {
parent.setTimeout( timeout );
return this;
}
@Override
public AuditAssociationQueryImplementor<Q> setLockMode(LockMode lockMode) {
parent.setLockMode( lockMode );
return this;
}
public Q up() {
return parent;
}
protected boolean hasCriterions() {
boolean result = !criterions.isEmpty();
if ( !result ) {
for ( final AuditAssociationQueryImplementor<?> sub : associationQueries ) {
if ( sub.hasCriterions() ) {
result = true;
break;
}
}
}
return result;
}
protected boolean hasOrders() {
boolean result = hasOrders;
if ( !result ) {
for ( final AuditAssociationQueryImplementor<?> sub : associationQueries ) {
if ( sub.hasOrders() ) {
result = true;
break;
}
}
}
return result;
}
protected boolean hasProjections() {
boolean result = hasProjections;
if ( !result ) {
for ( final AuditAssociationQueryImplementor<?> sub : associationQueries ) {
if ( sub.hasProjections() ) {
result = true;
break;
}
}
}
return result;
}
protected void addCriterionsToQuery(AuditReaderImplementor versionsReader) {
if ( hasCriterions() || hasOrders() || hasProjections() ) {
if ( enversService.getEntitiesConfigurations().isVersioned( entityName ) ) {
String auditEntityName = enversService.getAuditEntitiesConfiguration().getAuditEntityName( entityName );
queryBuilder.addFrom( auditEntityName, alias, false );
// owner.reference_id = target.originalId.id
AuditEntitiesConfiguration verEntCfg = enversService.getAuditEntitiesConfiguration();
String originalIdPropertyName = verEntCfg.getOriginalIdPropName();
IdMapper idMapperTarget = enversService.getEntitiesConfigurations().get( entityName ).getIdMapper();
final String prefix = alias.concat( "." ).concat( originalIdPropertyName );
ownerAssociationIdMapper.addIdsEqualToQuery( queryBuilder.getRootParameters(), ownerAlias, idMapperTarget, prefix );
// filter reference of target entity
String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
MiddleIdData referencedIdData = new MiddleIdData( verEntCfg, enversService.getEntitiesConfigurations().get( entityName ).getIdMappingData(), null, entityName,
enversService.getEntitiesConfigurations().isVersioned( entityName ) );
enversService.getAuditStrategy().addEntityAtRevisionRestriction( enversService.getGlobalConfiguration(), queryBuilder, parameters, revisionPropertyPath,
verEntCfg.getRevisionEndFieldName(), true, referencedIdData, revisionPropertyPath, originalIdPropertyName, alias,
queryBuilder.generateAlias(), true );
}
else {
queryBuilder.addFrom( entityName, alias, false );
// owner.reference_id = target.id
IdMapper idMapperTarget = enversService.getEntitiesConfigurations().getNotVersionEntityConfiguration( entityName ).getIdMapper();
ownerAssociationIdMapper.addIdsEqualToQuery( queryBuilder.getRootParameters(), ownerAlias, idMapperTarget, alias );
}
for ( AuditCriterion criterion : criterions ) {
criterion.addToQuery( enversService, versionsReader, entityName, alias, queryBuilder, parameters );
}
for ( final AuditAssociationQueryImplementor<?> sub : associationQueries ) {
sub.addCriterionsToQuery( versionsReader );
}
}
}
@Override
public void registerProjection(final String entityName, AuditProjection projection) {
parent.registerProjection( entityName, projection );
}
}

View File

@ -0,0 +1,16 @@
/**
*
*/
package org.hibernate.envers.query.internal.impl;
import org.hibernate.envers.query.AuditQuery;
import org.hibernate.envers.query.projection.AuditProjection;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
interface AuditQueryImplementor extends AuditQuery {
void registerProjection(final String entityName, final AuditProjection projection);
}

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.envers.query.internal.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -15,6 +14,7 @@ import org.hibernate.envers.RevisionType;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.query.criteria.AuditCriterion;
@ -50,7 +50,6 @@ public class EntitiesAtRevisionQuery extends AbstractAuditQuery {
this.includeDeletions = includeDeletions;
}
@SuppressWarnings({"unchecked"})
public List list() {
/*
* The query that we need to create:
@ -105,25 +104,27 @@ public class EntitiesAtRevisionQuery extends AbstractAuditQuery {
// all specified conditions
for ( AuditCriterion criterion : criterions ) {
criterion.addToQuery( enversService, versionsReader, entityName, qb, qb.getRootParameters() );
criterion.addToQuery(
enversService,
versionsReader,
entityName,
QueryConstants.REFERENCED_ENTITY_ALIAS,
qb,
qb.getRootParameters()
);
}
for (final AuditAssociationQueryImplementor<?> associationQuery : associationQueries) {
associationQuery.addCriterionsToQuery( versionsReader );
}
Query query = buildQuery();
// add named parameter (only used for ValidAuditTimeStrategy)
// add named parameter (used for ValidityAuditStrategy and association queries)
List<String> params = Arrays.asList( query.getNamedParameters() );
if ( params.contains( REVISION_PARAMETER ) ) {
query.setParameter( REVISION_PARAMETER, revision );
}
List queryResult = query.list();
if ( hasProjection ) {
return queryResult;
}
else {
List result = new ArrayList();
entityInstantiator.addInstancesFromVersionsEntities( entityName, result, queryResult, revision );
return result;
}
return applyProjections( queryResult, revision );
}
}

View File

@ -6,12 +6,12 @@
*/
package org.hibernate.envers.query.internal.impl;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.Query;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.query.criteria.AuditCriterion;
@ -60,20 +60,22 @@ public class EntitiesModifiedAtRevisionQuery extends AbstractAuditQuery {
// all specified conditions
for ( AuditCriterion criterion : criterions ) {
criterion.addToQuery( enversService, versionsReader, entityName, qb, qb.getRootParameters() );
criterion.addToQuery(
enversService,
versionsReader,
entityName,
QueryConstants.REFERENCED_ENTITY_ALIAS,
qb,
qb.getRootParameters()
);
}
for (final AuditAssociationQueryImplementor<?> associationQuery : associationQueries) {
associationQuery.addCriterionsToQuery(versionsReader);
}
Query query = buildQuery();
List queryResult = query.list();
if ( hasProjection ) {
return queryResult;
}
else {
List result = new ArrayList();
entityInstantiator.addInstancesFromVersionsEntities( entityName, result, queryResult, revision );
return result;
}
return applyProjections(queryResult, revision);
}
}

View File

@ -10,11 +10,16 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.persistence.criteria.JoinType;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.query.AuditAssociationQuery;
import org.hibernate.envers.query.AuditQuery;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.proxy.HibernateProxy;
@ -86,16 +91,23 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
// all specified conditions, transformed
for ( AuditCriterion criterion : criterions ) {
criterion.addToQuery( enversService, versionsReader, entityName, qb, qb.getRootParameters() );
criterion.addToQuery(
enversService,
versionsReader,
entityName,
QueryConstants.REFERENCED_ENTITY_ALIAS,
qb,
qb.getRootParameters()
);
}
if ( !hasProjection && !hasOrder ) {
if ( !hasProjection() && !hasOrder ) {
String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
qb.addOrder( revisionPropertyPath, true );
qb.addOrder( QueryConstants.REFERENCED_ENTITY_ALIAS, revisionPropertyPath, true );
}
if ( !selectEntitiesOnly ) {
qb.addFrom( enversService.getAuditEntitiesConfiguration().getRevisionInfoEntityName(), "r" );
qb.addFrom( enversService.getAuditEntitiesConfiguration().getRevisionInfoEntityName(), "r", true );
qb.getRootParameters().addWhere(
enversService.getAuditEntitiesConfiguration().getRevisionNumberPath(),
true,
@ -106,7 +118,7 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
}
List<Object> queryResult = buildAndExecuteQuery();
if ( hasProjection ) {
if ( hasProjection() ) {
return queryResult;
}
else {
@ -146,4 +158,10 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
return entities;
}
}
@Override
public AuditAssociationQuery<? extends AuditQuery> traverseRelation(String associationName, JoinType joinType) {
throw new UnsupportedOperationException( "Not yet implemented for revisions of entity queries" );
}
}

View File

@ -7,6 +7,7 @@
package org.hibernate.envers.query.projection;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.tools.Triple;
/**
@ -19,4 +20,20 @@ public interface AuditProjection {
* @return A triple: (function name - possibly null, property name, add distinct?).
*/
Triple<String, String, Boolean> getData(EnversService enversService);
/**
* @param enversService the Envers service
* @param entityInstantiator the entity instantiator
* @param entityName the name of the entity for which the projection has been added
* @param revision the revision
* @param value the value to convert
* @return the converted value
*/
Object convertQueryResult(
final EnversService enversService,
final EntityInstantiator entityInstantiator,
final String entityName,
final Number revision,
final Object value
);
}

View File

@ -0,0 +1,42 @@
/**
*
*/
package org.hibernate.envers.query.projection.internal;
import java.util.Map;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.envers.query.projection.AuditProjection;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
public class EntityAuditProjection implements AuditProjection {
private final boolean distinct;
public EntityAuditProjection(final boolean distinct) {
this.distinct = distinct;
}
@Override
public Triple<String, String, Boolean> getData(final EnversService enversService) {
// no property is selected, instead the whole entity (alias) is selected
return Triple.make( null, null, distinct );
}
@Override
public Object convertQueryResult(final EnversService enversService, final EntityInstantiator entityInstantiator, final String entityName,
final Number revision, final Object value) {
final Object result;
if ( enversService.getEntitiesConfigurations().isVersioned( entityName ) ) {
result = entityInstantiator.createInstanceFromVersionsEntity( entityName, (Map) value, revision );
} else {
result = value;
}
return result;
}
}

View File

@ -7,6 +7,7 @@
package org.hibernate.envers.query.projection.internal;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
import org.hibernate.envers.query.projection.AuditProjection;
@ -31,4 +32,9 @@ public class PropertyAuditProjection implements AuditProjection {
return Triple.make( function, propertyName, distinct );
}
@Override
public Object convertQueryResult(EnversService enversService, EntityInstantiator entityInstantiator, String entityName, Number revision, Object value) {
return value;
}
}

View File

@ -75,7 +75,7 @@ public class DefaultAuditStrategy implements AuditStrategy {
// create a subquery builder
// SELECT max(e.revision) FROM versionsReferencedEntity e2
QueryBuilder maxERevQb = rootQueryBuilder.newSubQueryBuilder( idData.getAuditEntityName(), alias2 );
maxERevQb.addProjection( "max", revisionPropertyPath, false );
maxERevQb.addProjection( "max", alias2, revisionPropertyPath, false );
// WHERE
Parameters maxERevQbParameters = maxERevQb.getRootParameters();
// e2.revision <= :revision
@ -110,7 +110,7 @@ public class DefaultAuditStrategy implements AuditStrategy {
versionsMiddleEntityName,
MIDDLE_ENTITY_ALIAS_DEF_AUD_STR
);
maxEeRevQb.addProjection( "max", revisionPropertyPath, false );
maxEeRevQb.addProjection( "max", MIDDLE_ENTITY_ALIAS_DEF_AUD_STR, revisionPropertyPath, false );
// WHERE
Parameters maxEeRevQbParameters = maxEeRevQb.getRootParameters();
// ee2.revision <= :revision

View File

@ -0,0 +1,162 @@
/**
*
*/
package org.hibernate.envers.test.integration.query;
import static org.junit.Assert.assertEquals;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.criteria.JoinType;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.integration.query.entities.Address;
import org.hibernate.envers.test.integration.query.entities.Car;
import org.hibernate.envers.test.integration.query.entities.Person;
import org.junit.Test;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
public class AssociationToOneQueryTest extends BaseEnversJPAFunctionalTestCase {
private Car vw;
private Car ford;
private Car toyota;
private Address address1;
private Address address2;
private Person vwOwner;
private Person fordOwner;
private Person toyotaOwner;
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { Car.class, Person.class, Address.class };
}
@Test
@Priority(10)
public void initData() {
EntityManager em = getEntityManager();
// revision 1
em.getTransaction().begin();
address1 = new Address( "Freiburgerstrasse", 5 );
em.persist( address1 );
address2 = new Address( "Hindenburgstrasse", 20 );
em.persist( address2 );
vwOwner = new Person( "VW owner", 20, address1 );
em.persist( vwOwner );
fordOwner = new Person( "Ford owner", 30, address1 );
em.persist( fordOwner );
toyotaOwner = new Person( "Toyota owner", 30, address2 );
em.persist( toyotaOwner );
final Person nonOwner = new Person( "NonOwner", 30, address1 );
em.persist( nonOwner );
vw = new Car( "VW" );
vw.setOwner( vwOwner );
em.persist( vw );
ford = new Car( "Ford" );
ford.setOwner( fordOwner );
em.persist( ford );
toyota = new Car( "Toyota" );
toyota.setOwner( toyotaOwner );
em.persist( toyota );
em.getTransaction().commit();
// revision 2
em.getTransaction().begin();
toyotaOwner.setAge( 40 );
em.getTransaction().commit();
}
@Test
public void testAssociationQuery() {
final AuditReader auditReader = getAuditReader();
final Car result1 = (Car) auditReader.createQuery().forEntitiesAtRevision( Car.class, 1 ).traverseRelation( "owner", JoinType.INNER )
.add( AuditEntity.property( "name" ).like( "Ford%" ) ).getSingleResult();
assertEquals( "Unexpected single car at revision 1", ford.getId(), result1.getId() );
Car result2 = (Car) auditReader.createQuery().forEntitiesAtRevision( Car.class, 1 ).traverseRelation( "owner", JoinType.INNER ).traverseRelation( "address", JoinType.INNER )
.add( AuditEntity.property( "number" ).eq( 20 ) ).getSingleResult();
assertEquals( "Unexpected single car at revision 1", toyota.getId(), result2.getId() );
List<Car> resultList1 = auditReader.createQuery().forEntitiesAtRevision( Car.class, 1 ).traverseRelation( "owner", JoinType.INNER )
.add( AuditEntity.property( "age" ).ge( 30 ) ).add( AuditEntity.property( "age" ).lt( 40 ) ).up()
.addOrder( AuditEntity.property( "make" ).asc() ).getResultList();
assertEquals( "Unexpected number of cars for query in revision 1", 2, resultList1.size() );
assertEquals( "Unexpected car at index 0 in revision 1", ford.getId(), resultList1.get( 0 ).getId() );
assertEquals( "Unexpected car at index 1 in revision 2", toyota.getId(), resultList1.get( 1 ).getId() );
Car result3 = (Car) auditReader.createQuery().forEntitiesAtRevision( Car.class, 2 ).traverseRelation( "owner", JoinType.INNER )
.add( AuditEntity.property( "age" ).ge( 30 ) ).add( AuditEntity.property( "age" ).lt( 40 ) ).up()
.addOrder( AuditEntity.property( "make" ).asc() ).getSingleResult();
assertEquals( "Unexpected car at revision 2", ford.getId(), result3.getId() );
}
@Test
public void testAssociationQueryWithOrdering() {
AuditReader auditReader = getAuditReader();
List<Car> cars1 = auditReader.createQuery().forEntitiesAtRevision( Car.class, 1 ).traverseRelation( "owner", JoinType.INNER ).traverseRelation( "address", JoinType.INNER )
.addOrder( AuditEntity.property( "number" ).asc() ).up().addOrder( AuditEntity.property( "age" ).desc() ).getResultList();
assertEquals( "Unexpected number of results", 3, cars1.size() );
assertEquals( "Unexpected car at index 0", ford.getId(), cars1.get( 0 ).getId() );
assertEquals( "Unexpected car at index 1", vw.getId(), cars1.get( 1 ).getId() );
assertEquals( "Unexpected car at index 2", toyota.getId(), cars1.get( 2 ).getId() );
List<Car> cars2 = auditReader.createQuery().forEntitiesAtRevision( Car.class, 1 ).traverseRelation( "owner", JoinType.INNER ).traverseRelation( "address", JoinType.INNER )
.addOrder( AuditEntity.property( "number" ).asc() ).up().addOrder( AuditEntity.property( "age" ).asc() ).getResultList();
assertEquals( "Unexpected number of results", 3, cars2.size() );
assertEquals( "Unexpected car at index 0", vw.getId(), cars2.get( 0 ).getId() );
assertEquals( "Unexpected car at index 1", ford.getId(), cars2.get( 1 ).getId() );
assertEquals( "Unexpected car at index 2", toyota.getId(), cars2.get( 2 ).getId() );
}
@Test
public void testAssociationQueryWithProjection() {
AuditReader auditReader = getAuditReader();
List<Integer> list1 = auditReader.createQuery().forEntitiesAtRevision( Car.class, 2 ).traverseRelation( "owner", JoinType.INNER )
.addProjection( AuditEntity.property( "age" ) ).addOrder( AuditEntity.property( "age" ).asc() ).getResultList();
assertEquals( "Unexpected number of results", 3, list1.size() );
assertEquals( "Unexpected age at index 0", Integer.valueOf( 20 ), list1.get( 0 ) );
assertEquals( "Unexpected age at index 0", Integer.valueOf( 30 ), list1.get( 1 ) );
assertEquals( "Unexpected age at index 0", Integer.valueOf( 40 ), list1.get( 2 ) );
List<Address> list2 = auditReader.createQuery().forEntitiesAtRevision( Car.class, 2 ).traverseRelation( "owner", JoinType.INNER )
.addOrder( AuditEntity.property( "age" ).asc() ).traverseRelation( "address", JoinType.INNER ).addProjection( AuditEntity.selectEntity( false ) ).getResultList();
assertEquals( "Unexpected number of results", 3, list2.size() );
assertEquals( "Unexpected address at index 0", address1.getId(), list2.get( 0 ).getId() );
assertEquals( "Unexpected address at index 1", address1.getId(), list2.get( 1 ).getId() );
assertEquals( "Unexpected address at index 2", address2.getId(), list2.get( 2 ).getId() );
List<Address> list3 = auditReader.createQuery().forEntitiesAtRevision( Car.class, 2 ).traverseRelation( "owner", JoinType.INNER ).traverseRelation( "address", JoinType.INNER )
.addProjection( AuditEntity.selectEntity( true ) ).addOrder( AuditEntity.property( "number" ).asc() ).getResultList();
assertEquals( "Unexpected number of results", 2, list3.size() );
assertEquals( "Unexpected address at index 0", address1.getId(), list3.get( 0 ).getId() );
assertEquals( "Unexpected address at index 1", address2.getId(), list3.get( 1 ).getId() );
List<Object[]> list4 = auditReader.createQuery().forEntitiesAtRevision( Car.class, 2 ).traverseRelation( "owner", JoinType.INNER )
.addOrder( AuditEntity.property( "age" ).asc() ).addProjection( AuditEntity.selectEntity( false ) ).traverseRelation( "address", JoinType.INNER )
.addProjection( AuditEntity.property( "number" ) ).getResultList();
assertEquals( "Unexpected number of results", 3, list4.size() );
final Object[] index0 = list4.get( 0 );
assertEquals( "Unexpected owner at index 0", vwOwner.getId(), ( (Person) index0[0] ).getId() );
assertEquals( "Unexpected number at index 0", Integer.valueOf( 5 ), index0[1] );
final Object[] index1 = list4.get( 1 );
assertEquals( "Unexpected owner at index 1", fordOwner.getId(), ( (Person) index1[0] ).getId() );
assertEquals( "Unexpected number at index 1", Integer.valueOf( 5 ), index1[1] );
final Object[] index2 = list4.get( 2 );
assertEquals( "Unexpected owner at index 2", toyotaOwner.getId(), ( (Person) index2[0] ).getId() );
assertEquals( "Unexpected number at index 2", Integer.valueOf( 20 ), index2[1] );
}
}

View File

@ -0,0 +1,56 @@
/**
*
*/
package org.hibernate.envers.test.integration.query.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
@Entity
public class Address {
@Id
@GeneratedValue
private Long id;
private String street;
private int number;
public Address() {
}
public Address(String street, int number) {
this.street = street;
this.number = number;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}

View File

@ -0,0 +1,74 @@
/**
*
*/
package org.hibernate.envers.test.integration.query.entities;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import org.hibernate.envers.Audited;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
@Entity
@Audited
public class Car {
@Id
@GeneratedValue
private Long id;
private String make;
@ManyToOne
private Person owner;
@ManyToMany
private Set<Person> drivers = new HashSet<Person>();
public Car() {
}
public Car(final String make) {
this.make = make;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMake() {
return make;
}
public void setMake(String make) {
this.make = make;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
public Set<Person> getDrivers() {
return drivers;
}
public void setDrivers(Set<Person> drivers) {
this.drivers = drivers;
}
}

View File

@ -0,0 +1,73 @@
/**
*
*/
package org.hibernate.envers.test.integration.query.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.envers.Audited;
import org.hibernate.envers.RelationTargetAuditMode;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
@Entity
@Audited
public class Person {
@Id
@GeneratedValue
private Long id;
private String name;
private int age;
@ManyToOne
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
private Address address;
public Person() {
}
public Person(final String name, final int age, final Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}