HHH-13817 Support to-one relation traversals in RevisionsOfEntity queries
This commit is contained in:
parent
96e7d4cbb1
commit
9ace8a9dd3
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.envers.query.internal.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -38,32 +39,37 @@ import org.hibernate.envers.query.order.AuditOrder;
|
|||
import org.hibernate.envers.query.projection.AuditProjection;
|
||||
|
||||
/**
|
||||
* An abstract base class for all {@link AuditAssociationQuery} implementations.
|
||||
*
|
||||
* @author Felix Feisst (feisst dot felix at gmail dot com)
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@Incubating
|
||||
public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
|
||||
public abstract class AbstractAuditAssociationQuery<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 RelationDescription relationDescription;
|
||||
private final ComponentDescription componentDescription;
|
||||
private final String ownerAlias;
|
||||
private final String ownerEntityName;
|
||||
private final String alias;
|
||||
private final Map<String, String> aliasToEntityNameMap;
|
||||
private final Map<String, String> aliasToComponentPropertyNameMap;
|
||||
private final List<AuditCriterion> criterions = new ArrayList<>();
|
||||
private final AuditCriterion onClauseCriterion;
|
||||
private final Parameters parameters;
|
||||
private final List<AuditAssociationQueryImpl<?>> associationQueries = new ArrayList<>();
|
||||
protected final EnversService enversService;
|
||||
protected final AuditReaderImplementor auditReader;
|
||||
protected final Q parent;
|
||||
protected final QueryBuilder queryBuilder;
|
||||
protected final JoinType joinType;
|
||||
protected final String entityName;
|
||||
protected final RelationDescription relationDescription;
|
||||
protected final ComponentDescription componentDescription;
|
||||
protected final String ownerAlias;
|
||||
protected final String ownerEntityName;
|
||||
protected final String alias;
|
||||
protected final Map<String, String> aliasToEntityNameMap;
|
||||
protected final Map<String, String> aliasToComponentPropertyNameMap;
|
||||
protected final List<AuditCriterion> criterions = new ArrayList<>();
|
||||
protected final AuditCriterion onClauseCriterion;
|
||||
protected final Parameters parameters;
|
||||
|
||||
public AuditAssociationQueryImpl(
|
||||
// todo: can these association query collections be merged?
|
||||
protected final List<AbstractAuditAssociationQuery<Q>> associationQueries = new ArrayList<>();
|
||||
protected final Map<String, AbstractAuditAssociationQuery<AbstractAuditAssociationQuery<Q>>> associationQueryMap = new HashMap<>();
|
||||
|
||||
public AbstractAuditAssociationQuery(
|
||||
final EnversService enversService,
|
||||
final AuditReaderImplementor auditReader,
|
||||
final Q parent,
|
||||
|
@ -142,7 +148,7 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
|
|||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<AuditAssociationQueryImpl<Q>> traverseRelation(
|
||||
public AbstractAuditAssociationQuery<AbstractAuditAssociationQuery<Q>> traverseRelation(
|
||||
String associationName,
|
||||
JoinType joinType) {
|
||||
return traverseRelation(
|
||||
|
@ -153,7 +159,7 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
|
|||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<AuditAssociationQueryImpl<Q>> traverseRelation(
|
||||
public AbstractAuditAssociationQuery<AbstractAuditAssociationQuery<Q>> traverseRelation(
|
||||
String associationName,
|
||||
JoinType joinType,
|
||||
String alias) {
|
||||
|
@ -165,37 +171,34 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<AuditAssociationQueryImpl<Q>> traverseRelation(
|
||||
public AbstractAuditAssociationQuery<AbstractAuditAssociationQuery<Q>> traverseRelation(
|
||||
String associationName,
|
||||
JoinType joinType,
|
||||
String alias,
|
||||
AuditCriterion onClause) {
|
||||
AuditAssociationQueryImpl<AuditAssociationQueryImpl<Q>> result = new AuditAssociationQueryImpl<>(
|
||||
enversService,
|
||||
auditReader,
|
||||
this,
|
||||
queryBuilder,
|
||||
associationName,
|
||||
joinType,
|
||||
aliasToEntityNameMap,
|
||||
aliasToComponentPropertyNameMap,
|
||||
this.alias,
|
||||
alias,
|
||||
onClause
|
||||
);
|
||||
associationQueries.add( result );
|
||||
return result;
|
||||
AbstractAuditAssociationQuery<AbstractAuditAssociationQuery<Q>> query = associationQueryMap.get( associationName );
|
||||
if ( query == null ) {
|
||||
query = createAssociationQuery( associationName, joinType, alias, onClause );
|
||||
associationQueries.add( (AbstractAuditAssociationQuery<Q>) query );
|
||||
associationQueryMap.put( associationName, query );
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
protected abstract AbstractAuditAssociationQuery<AbstractAuditAssociationQuery<Q>> createAssociationQuery(
|
||||
String associationName,
|
||||
JoinType joinType,
|
||||
String alias,
|
||||
AuditCriterion onClause);
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<Q> add(AuditCriterion criterion) {
|
||||
public AbstractAuditAssociationQuery<Q> add(AuditCriterion criterion) {
|
||||
criterions.add( criterion );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<Q> addProjection(AuditProjection projection) {
|
||||
public AbstractAuditAssociationQuery<Q> addProjection(AuditProjection projection) {
|
||||
String projectionEntityAlias = projection.getAlias( alias );
|
||||
String projectionEntityName = aliasToEntityNameMap.get( projectionEntityAlias );
|
||||
registerProjection( projectionEntityName, projection );
|
||||
|
@ -211,7 +214,7 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
|
|||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<Q> addOrder(AuditOrder order) {
|
||||
public AbstractAuditAssociationQuery<Q> addOrder(AuditOrder order) {
|
||||
AuditOrder.OrderData orderData = order.getData( enversService.getConfig() );
|
||||
String orderEntityAlias = orderData.getAlias( alias );
|
||||
String orderEntityName = aliasToEntityNameMap.get( orderEntityAlias );
|
||||
|
@ -237,55 +240,55 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
|
|||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<Q> setMaxResults(int maxResults) {
|
||||
public AbstractAuditAssociationQuery<Q> setMaxResults(int maxResults) {
|
||||
parent.setMaxResults( maxResults );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<Q> setFirstResult(int firstResult) {
|
||||
public AbstractAuditAssociationQuery<Q> setFirstResult(int firstResult) {
|
||||
parent.setFirstResult( firstResult );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<Q> setCacheable(boolean cacheable) {
|
||||
public AbstractAuditAssociationQuery<Q> setCacheable(boolean cacheable) {
|
||||
parent.setCacheable( cacheable );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<Q> setCacheRegion(String cacheRegion) {
|
||||
public AbstractAuditAssociationQuery<Q> setCacheRegion(String cacheRegion) {
|
||||
parent.setCacheRegion( cacheRegion );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<Q> setComment(String comment) {
|
||||
public AbstractAuditAssociationQuery<Q> setComment(String comment) {
|
||||
parent.setComment( comment );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<Q> setFlushMode(FlushMode flushMode) {
|
||||
public AbstractAuditAssociationQuery<Q> setFlushMode(FlushMode flushMode) {
|
||||
parent.setFlushMode( flushMode );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<Q> setCacheMode(CacheMode cacheMode) {
|
||||
public AbstractAuditAssociationQuery<Q> setCacheMode(CacheMode cacheMode) {
|
||||
parent.setCacheMode( cacheMode );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<Q> setTimeout(int timeout) {
|
||||
public AbstractAuditAssociationQuery<Q> setTimeout(int timeout) {
|
||||
parent.setTimeout( timeout );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQueryImpl<Q> setLockMode(LockMode lockMode) {
|
||||
public AbstractAuditAssociationQuery<Q> setLockMode(LockMode lockMode) {
|
||||
parent.setLockMode( lockMode );
|
||||
return this;
|
||||
}
|
||||
|
@ -294,7 +297,7 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
|
|||
return parent;
|
||||
}
|
||||
|
||||
protected void addCriterionsToQuery(AuditReaderImplementor versionsReader) {
|
||||
protected void addCriterionToQuery(AuditReaderImplementor versionsReader) {
|
||||
Parameters onClauseParameters;
|
||||
if ( relationDescription != null ) {
|
||||
onClauseParameters = createEntityJoin( enversService.getConfig() );
|
||||
|
@ -327,12 +330,12 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
|
|||
);
|
||||
}
|
||||
|
||||
for ( AuditAssociationQueryImpl<?> query : associationQueries ) {
|
||||
query.addCriterionsToQuery( versionsReader );
|
||||
for ( AbstractAuditAssociationQuery<Q> subQuery : associationQueries ) {
|
||||
subQuery.addCriterionToQuery( versionsReader );
|
||||
}
|
||||
}
|
||||
|
||||
private Parameters createEntityJoin(Configuration configuration) {
|
||||
protected Parameters createEntityJoin(Configuration configuration) {
|
||||
boolean targetIsAudited = enversService.getEntitiesConfigurations().isVersioned( entityName );
|
||||
String targetEntityName = entityName;
|
||||
if ( targetIsAudited ) {
|
||||
|
@ -480,40 +483,10 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
|
|||
);
|
||||
}
|
||||
|
||||
if ( targetIsAudited ) {
|
||||
// filter revision of target entity
|
||||
Parameters parametersToUse = parameters;
|
||||
if ( joinType == JoinType.LEFT ) {
|
||||
parametersToUse = parameters.addSubParameters( Parameters.OR );
|
||||
parametersToUse.addNullRestriction( revisionPropertyPath, true );
|
||||
parametersToUse = parametersToUse.addSubParameters( Parameters.AND );
|
||||
}
|
||||
MiddleIdData referencedIdData = new MiddleIdData(
|
||||
configuration,
|
||||
enversService.getEntitiesConfigurations().get( entityName ).getIdMappingData(),
|
||||
null,
|
||||
entityName,
|
||||
true
|
||||
);
|
||||
enversService.getAuditStrategy().addEntityAtRevisionRestriction(
|
||||
configuration,
|
||||
queryBuilder,
|
||||
parametersToUse,
|
||||
revisionPropertyPath,
|
||||
configuration.getRevisionEndFieldName(),
|
||||
true,
|
||||
referencedIdData,
|
||||
revisionPropertyPath,
|
||||
originalIdPropertyName,
|
||||
alias,
|
||||
queryBuilder.generateAlias(),
|
||||
true
|
||||
);
|
||||
}
|
||||
return onClauseParameters;
|
||||
}
|
||||
|
||||
private Parameters createComponentJoin(Configuration configuration) {
|
||||
protected Parameters createComponentJoin(Configuration configuration) {
|
||||
String originalIdPropertyName = configuration.getOriginalIdPropertyName();
|
||||
String revisionPropertyPath = configuration.getRevisionNumberPath();
|
||||
Parameters onClauseParameters;
|
|
@ -58,7 +58,9 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
|
|||
protected final EnversService enversService;
|
||||
protected final AuditReaderImplementor versionsReader;
|
||||
|
||||
protected final List<AuditAssociationQueryImpl<?>> associationQueries = new ArrayList<>();
|
||||
// todo: can these association query collections be merged?
|
||||
protected final List<AbstractAuditAssociationQuery<?>> associationQueries = new ArrayList<>();
|
||||
protected final Map<String, AbstractAuditAssociationQuery<AuditQueryImplementor>> associationQueryMap = new HashMap<>();
|
||||
protected final List<Pair<String, AuditProjection>> projections = new ArrayList<>();
|
||||
|
||||
protected AbstractAuditQuery(
|
||||
|
@ -202,29 +204,6 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
|
|||
null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQuery<? extends AuditQuery> traverseRelation(
|
||||
String associationName,
|
||||
JoinType joinType,
|
||||
String alias,
|
||||
AuditCriterion onClause) {
|
||||
AuditAssociationQueryImpl<AbstractAuditQuery> result = new AuditAssociationQueryImpl<>(
|
||||
enversService,
|
||||
versionsReader,
|
||||
this,
|
||||
qb,
|
||||
associationName,
|
||||
joinType,
|
||||
aliasToEntityNameMap,
|
||||
aliasToComponentPropertyNameMap,
|
||||
REFERENCED_ENTITY_ALIAS,
|
||||
alias,
|
||||
onClause
|
||||
);
|
||||
associationQueries.add( result );
|
||||
return result;
|
||||
}
|
||||
|
||||
// Query properties
|
||||
|
||||
private Integer maxResults;
|
||||
|
@ -383,4 +362,9 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
|
|||
// todo: can this be replaced by a call to getEntittyConfiguration#getEntityClassName()?
|
||||
return entityName;
|
||||
}
|
||||
|
||||
protected void addAssociationQuery(String associationName, AbstractAuditAssociationQuery<AuditQueryImplementor> query) {
|
||||
associationQueries.add( query );
|
||||
associationQueryMap.put( associationName, query );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.envers.query.internal.impl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.envers.boot.internal.EnversService;
|
||||
import org.hibernate.envers.configuration.Configuration;
|
||||
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
|
||||
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
|
||||
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 jakarta.persistence.criteria.JoinType;
|
||||
|
||||
/**
|
||||
* An {@link AuditAssociationQuery} implementation for
|
||||
* {@link EntitiesAtRevisionQuery} and {@link EntitiesModifiedAtRevisionQuery} query types.
|
||||
*
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@Incubating
|
||||
public class EntitiesAtRevisionAssociationQuery<Q extends AuditQueryImplementor> extends AbstractAuditAssociationQuery<Q> {
|
||||
|
||||
public EntitiesAtRevisionAssociationQuery(
|
||||
EnversService enversService,
|
||||
AuditReaderImplementor auditReader,
|
||||
Q parent,
|
||||
QueryBuilder queryBuilder,
|
||||
String propertyName,
|
||||
JoinType joinType,
|
||||
Map<String, String> aliasToEntityNameMap,
|
||||
Map<String, String> aliasToComponentPropertyNameMap,
|
||||
String ownerAlias,
|
||||
String userSuppliedAlias,
|
||||
AuditCriterion onClauseCriterion) {
|
||||
super(
|
||||
enversService,
|
||||
auditReader,
|
||||
parent,
|
||||
queryBuilder,
|
||||
propertyName,
|
||||
joinType,
|
||||
aliasToEntityNameMap,
|
||||
aliasToComponentPropertyNameMap,
|
||||
ownerAlias,
|
||||
userSuppliedAlias,
|
||||
onClauseCriterion
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractAuditAssociationQuery<AbstractAuditAssociationQuery<Q>> createAssociationQuery(
|
||||
String associationName,
|
||||
JoinType joinType,
|
||||
String alias,
|
||||
AuditCriterion onClause) {
|
||||
return new EntitiesAtRevisionAssociationQuery<>(
|
||||
enversService,
|
||||
auditReader,
|
||||
this,
|
||||
queryBuilder,
|
||||
associationName,
|
||||
joinType,
|
||||
aliasToEntityNameMap,
|
||||
aliasToComponentPropertyNameMap,
|
||||
this.alias,
|
||||
alias,
|
||||
onClauseCriterion
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parameters createEntityJoin(Configuration configuration) {
|
||||
Parameters onClauseParameters = super.createEntityJoin( configuration );
|
||||
|
||||
if ( enversService.getEntitiesConfigurations().isVersioned( entityName ) ) {
|
||||
final String originalIdPropertyName = configuration.getOriginalIdPropertyName();
|
||||
final String revisionPropertyPath = configuration.getRevisionNumberPath();
|
||||
|
||||
// filter revision of target entity
|
||||
Parameters parametersToUse = parameters;
|
||||
if ( joinType == JoinType.LEFT ) {
|
||||
parametersToUse = parameters.addSubParameters( Parameters.OR );
|
||||
parametersToUse.addNullRestriction( revisionPropertyPath, true );
|
||||
parametersToUse = parametersToUse.addSubParameters( Parameters.AND );
|
||||
}
|
||||
MiddleIdData referencedIdData = new MiddleIdData(
|
||||
configuration,
|
||||
enversService.getEntitiesConfigurations().get( entityName ).getIdMappingData(),
|
||||
null,
|
||||
entityName,
|
||||
true
|
||||
);
|
||||
enversService.getAuditStrategy().addEntityAtRevisionRestriction(
|
||||
configuration,
|
||||
queryBuilder,
|
||||
parametersToUse,
|
||||
revisionPropertyPath,
|
||||
configuration.getRevisionEndFieldName(),
|
||||
true,
|
||||
referencedIdData,
|
||||
revisionPropertyPath,
|
||||
originalIdPropertyName,
|
||||
alias,
|
||||
queryBuilder.generateAlias(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
return onClauseParameters;
|
||||
}
|
||||
}
|
|
@ -9,12 +9,16 @@ package org.hibernate.envers.query.internal.impl;
|
|||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.criteria.JoinType;
|
||||
|
||||
import org.hibernate.envers.RevisionType;
|
||||
import org.hibernate.envers.boot.internal.EnversService;
|
||||
import org.hibernate.envers.configuration.Configuration;
|
||||
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.AuditAssociationQuery;
|
||||
import org.hibernate.envers.query.AuditQuery;
|
||||
import org.hibernate.envers.query.criteria.AuditCriterion;
|
||||
import org.hibernate.query.Query;
|
||||
|
||||
|
@ -121,8 +125,8 @@ public class EntitiesAtRevisionQuery extends AbstractAuditQuery {
|
|||
);
|
||||
}
|
||||
|
||||
for (final AuditAssociationQueryImpl<?> associationQuery : associationQueries) {
|
||||
associationQuery.addCriterionsToQuery( versionsReader );
|
||||
for ( AbstractAuditAssociationQuery<?> associationQuery : associationQueries ) {
|
||||
associationQuery.addCriterionToQuery( versionsReader );
|
||||
}
|
||||
|
||||
Query query = buildQuery();
|
||||
|
@ -134,4 +138,30 @@ public class EntitiesAtRevisionQuery extends AbstractAuditQuery {
|
|||
List queryResult = query.list();
|
||||
return applyProjections( queryResult, revision );
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQuery<? extends AuditQuery> traverseRelation(
|
||||
String associationName,
|
||||
JoinType joinType,
|
||||
String alias,
|
||||
AuditCriterion onClauseCriterion) {
|
||||
AbstractAuditAssociationQuery<AuditQueryImplementor> query = associationQueryMap.get( associationName );
|
||||
if ( query == null ) {
|
||||
query = new EntitiesAtRevisionAssociationQuery<>(
|
||||
enversService,
|
||||
versionsReader,
|
||||
this,
|
||||
qb,
|
||||
associationName,
|
||||
joinType,
|
||||
aliasToEntityNameMap,
|
||||
aliasToComponentPropertyNameMap,
|
||||
REFERENCED_ENTITY_ALIAS,
|
||||
alias,
|
||||
onClauseCriterion
|
||||
);
|
||||
addAssociationQuery( associationName, query );
|
||||
}
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,17 @@ package org.hibernate.envers.query.internal.impl;
|
|||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.criteria.JoinType;
|
||||
|
||||
import org.hibernate.envers.boot.internal.EnversService;
|
||||
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.query.Query;
|
||||
|
||||
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.REFERENCED_ENTITY_ALIAS;
|
||||
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.REVISION_PARAMETER;
|
||||
|
||||
/**
|
||||
|
@ -73,8 +78,8 @@ public class EntitiesModifiedAtRevisionQuery extends AbstractAuditQuery {
|
|||
);
|
||||
}
|
||||
|
||||
for (final AuditAssociationQueryImpl<?> associationQuery : associationQueries) {
|
||||
associationQuery.addCriterionsToQuery( versionsReader );
|
||||
for ( AbstractAuditAssociationQuery<?> associationQuery : associationQueries ) {
|
||||
associationQuery.addCriterionToQuery( versionsReader );
|
||||
}
|
||||
|
||||
Query query = buildQuery();
|
||||
|
@ -86,4 +91,30 @@ public class EntitiesModifiedAtRevisionQuery extends AbstractAuditQuery {
|
|||
List queryResult = query.list();
|
||||
return applyProjections( queryResult, revision );
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQuery<? extends AuditQuery> traverseRelation(
|
||||
String associationName,
|
||||
JoinType joinType,
|
||||
String alias,
|
||||
AuditCriterion onClauseCriterion) {
|
||||
AbstractAuditAssociationQuery<AuditQueryImplementor> query = associationQueryMap.get( associationName );
|
||||
if ( query == null ) {
|
||||
query = new EntitiesAtRevisionAssociationQuery<>(
|
||||
enversService,
|
||||
versionsReader,
|
||||
this,
|
||||
qb,
|
||||
associationName,
|
||||
joinType,
|
||||
aliasToEntityNameMap,
|
||||
aliasToComponentPropertyNameMap,
|
||||
REFERENCED_ENTITY_ALIAS,
|
||||
alias,
|
||||
null
|
||||
);
|
||||
addAssociationQuery( associationName, query );
|
||||
}
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.envers.query.internal.impl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.envers.boot.internal.EnversService;
|
||||
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
|
||||
import org.hibernate.envers.internal.tools.query.QueryBuilder;
|
||||
import org.hibernate.envers.query.AuditAssociationQuery;
|
||||
import org.hibernate.envers.query.criteria.AuditCriterion;
|
||||
|
||||
import jakarta.persistence.criteria.JoinType;
|
||||
|
||||
/**
|
||||
* An {@link AuditAssociationQuery} implementation for {@link RevisionsOfEntityQuery}.
|
||||
*
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@Incubating
|
||||
public class RevisionsOfEntityAssociationQuery<Q extends AuditQueryImplementor> extends AbstractAuditAssociationQuery<Q> {
|
||||
|
||||
public RevisionsOfEntityAssociationQuery(
|
||||
EnversService enversService,
|
||||
AuditReaderImplementor auditReader,
|
||||
Q parent,
|
||||
QueryBuilder queryBuilder,
|
||||
String propertyName,
|
||||
JoinType joinType,
|
||||
Map<String, String> aliasToEntityNameMap,
|
||||
Map<String, String> aliastoComponentPropertyNameMap,
|
||||
String ownerAlias,
|
||||
String userSuppliedAlias,
|
||||
AuditCriterion onClauseCriterion) {
|
||||
super(
|
||||
enversService,
|
||||
auditReader,
|
||||
parent,
|
||||
queryBuilder,
|
||||
propertyName,
|
||||
joinType,
|
||||
aliasToEntityNameMap,
|
||||
aliastoComponentPropertyNameMap,
|
||||
ownerAlias,
|
||||
userSuppliedAlias,
|
||||
onClauseCriterion
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractAuditAssociationQuery<AbstractAuditAssociationQuery<Q>> createAssociationQuery(
|
||||
String associationName,
|
||||
JoinType joinType,
|
||||
String alias,
|
||||
AuditCriterion onClauseCriterion) {
|
||||
return new RevisionsOfEntityAssociationQuery<>(
|
||||
enversService,
|
||||
auditReader,
|
||||
this,
|
||||
queryBuilder,
|
||||
associationName,
|
||||
joinType,
|
||||
aliasToEntityNameMap,
|
||||
aliasToComponentPropertyNameMap,
|
||||
this.alias,
|
||||
alias,
|
||||
onClauseCriterion
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.envers.query.internal.impl;
|
||||
|
||||
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.REFERENCED_ENTITY_ALIAS;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -72,6 +74,40 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
|
|||
this.includePropertyChanges = includePropertyChanges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQuery<? extends AuditQuery> traverseRelation(
|
||||
String associationName,
|
||||
JoinType joinType,
|
||||
String alias,
|
||||
AuditCriterion onClauseCriterion) {
|
||||
if ( !selectEntitiesOnly ) {
|
||||
throw new IllegalStateException(
|
||||
"Audit association queries are only permitted when the query is created with selectEntitiesOnly=true"
|
||||
);
|
||||
}
|
||||
|
||||
AbstractAuditAssociationQuery<AuditQueryImplementor> query = associationQueryMap.get( associationName );
|
||||
if ( query == null ) {
|
||||
query = new RevisionsOfEntityAssociationQuery<>(
|
||||
enversService,
|
||||
versionsReader,
|
||||
this,
|
||||
qb,
|
||||
associationName,
|
||||
joinType,
|
||||
aliasToEntityNameMap,
|
||||
aliasToComponentPropertyNameMap,
|
||||
REFERENCED_ENTITY_ALIAS,
|
||||
alias,
|
||||
null
|
||||
);
|
||||
|
||||
addAssociationQuery( associationName, query );
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private Number getRevisionNumber(Map versionsEntity) {
|
||||
Configuration configuration = enversService.getConfig();
|
||||
|
||||
|
@ -89,7 +125,8 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@Override
|
||||
public List list() throws AuditException {
|
||||
Configuration configuration = enversService.getConfig();
|
||||
|
||||
|
@ -119,6 +156,10 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
|
|||
);
|
||||
}
|
||||
|
||||
for ( AbstractAuditAssociationQuery<?> associationQuery : associationQueries ) {
|
||||
associationQuery.addCriterionToQuery( versionsReader );
|
||||
}
|
||||
|
||||
if ( !hasProjection() && !hasOrder ) {
|
||||
String revisionPropertyPath = configuration.getRevisionNumberPath();
|
||||
qb.addOrder( QueryConstants.REFERENCED_ENTITY_ALIAS, revisionPropertyPath, true, null );
|
||||
|
@ -138,11 +179,6 @@ public class RevisionsOfEntityQuery extends AbstractAuditQuery {
|
|||
return getQueryResults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditAssociationQuery<? extends AuditQuery> traverseRelation(String associationName, JoinType joinType) {
|
||||
throw new UnsupportedOperationException( "Not yet implemented for revisions of entity queries" );
|
||||
}
|
||||
|
||||
private boolean isEntityUsingModifiedFlags() {
|
||||
// todo: merge HHH-8973 ModifiedFlagMapperSupport into 6.0 to get this behavior by default
|
||||
final ExtendedPropertyMapper propertyMapper = getEntityConfiguration().getPropertyMapper();
|
||||
|
|
|
@ -13,10 +13,15 @@ import java.util.Map;
|
|||
import org.hibernate.Session;
|
||||
import org.hibernate.envers.AuditReader;
|
||||
import org.hibernate.envers.AuditReaderFactory;
|
||||
import org.hibernate.envers.boot.internal.EnversService;
|
||||
import org.hibernate.envers.configuration.Configuration;
|
||||
import org.hibernate.envers.configuration.EnversSettings;
|
||||
import org.hibernate.internal.SessionImpl;
|
||||
import org.hibernate.resource.transaction.spi.TransactionStatus;
|
||||
|
||||
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||
|
||||
import org.hibernate.service.ServiceRegistry;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
|
@ -61,6 +66,11 @@ public abstract class BaseEnversFunctionalTestCase extends BaseNonConfigCoreFunc
|
|||
return AuditReaderFactory.get( getSession() );
|
||||
}
|
||||
|
||||
protected Configuration getConfiguration() {
|
||||
ServiceRegistry registry = getSession().unwrap(SessionImpl.class ).getSessionFactory().getServiceRegistry();
|
||||
return registry.getService( EnversService.class ).getConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addSettings(Map settings) {
|
||||
super.addSettings( settings );
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.orm.test.envers.integration.query;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.envers.configuration.EnversSettings;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
|
||||
/**
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@TestForIssue( jiraKey = "HHH-13817" )
|
||||
public class AssociationRevisionsOfEntitiesQueryStoreAtDeletionTest extends AssociationRevisionsOfEntitiesQueryTest {
|
||||
@Override
|
||||
protected void addSettings(Map settings) {
|
||||
super.addSettings( settings );
|
||||
settings.put( EnversSettings.STORE_DATA_AT_DELETE, true );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.orm.test.envers.integration.query;
|
||||
|
||||
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.criteria.JoinType;
|
||||
|
||||
import org.hibernate.envers.Audited;
|
||||
import org.hibernate.envers.query.AuditEntity;
|
||||
import org.hibernate.orm.test.envers.BaseEnversFunctionalTestCase;
|
||||
import org.hibernate.orm.test.envers.Priority;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
|
||||
/**
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@TestForIssue( jiraKey = "HHH-13817" )
|
||||
public class AssociationRevisionsOfEntitiesQueryTest extends BaseEnversFunctionalTestCase {
|
||||
@Override
|
||||
protected Class[] getAnnotatedClasses() {
|
||||
return new Class<?>[] { Template.class, TemplateType.class };
|
||||
}
|
||||
|
||||
@Test
|
||||
@Priority(10)
|
||||
public void initData() {
|
||||
doInHibernate( this::sessionFactory, session -> {
|
||||
final TemplateType type1 = new TemplateType( 1, "Type1" );
|
||||
final TemplateType type2 = new TemplateType( 2, "Type2" );
|
||||
session.save( type1 );
|
||||
session.save( type2 );
|
||||
|
||||
final Template template = new Template( 1, "Template1", type1 );
|
||||
session.save( template );
|
||||
} );
|
||||
|
||||
doInHibernate( this::sessionFactory, session -> {
|
||||
final TemplateType type = session.find( TemplateType.class, 2 );
|
||||
final Template template = session.find( Template.class, 1 );
|
||||
template.setName( "Template1-Updated" );
|
||||
template.setTemplateType( type );
|
||||
session.update( template );
|
||||
} );
|
||||
|
||||
doInHibernate( this::sessionFactory, session -> {
|
||||
final Template template = session.find( Template.class, 1 );
|
||||
session.remove( template );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRevisionsOfEntityWithAssociationQueries() {
|
||||
doInHibernate( this::sessionFactory, session -> {
|
||||
List<?> results = getAuditReader().createQuery()
|
||||
.forRevisionsOfEntity( Template.class, true, true )
|
||||
.add( AuditEntity.id().eq( 1 ) )
|
||||
.traverseRelation( "templateType", JoinType.INNER )
|
||||
.add( AuditEntity.property( "name" ).eq( "Type1" ) )
|
||||
.up()
|
||||
.getResultList();
|
||||
assertEquals( 1, results.size() );
|
||||
assertEquals( "Template1", ( (Template) results.get( 0 ) ).getName() );
|
||||
} );
|
||||
|
||||
doInHibernate( this::sessionFactory, session -> {
|
||||
List<?> results = getAuditReader().createQuery()
|
||||
.forRevisionsOfEntity( Template.class, true, true )
|
||||
.add( AuditEntity.id().eq( 1 ) )
|
||||
.traverseRelation( "templateType", JoinType.INNER )
|
||||
.add( AuditEntity.property("name" ).eq("Type2" ) )
|
||||
.up()
|
||||
.getResultList();
|
||||
|
||||
assertEquals( getConfiguration().isStoreDataAtDelete() ? 2 : 1, results.size() );
|
||||
for ( Object result : results ) {
|
||||
assertEquals( "Template1-Updated", ( (Template) result ).getName() );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAssociationQueriesNotAllowedWhenNotSelectingJustEntities() {
|
||||
try {
|
||||
doInHibernate( this::sessionFactory, session -> {
|
||||
getAuditReader().createQuery()
|
||||
.forRevisionsOfEntity( Template.class, false, true )
|
||||
.add( AuditEntity.id().eq( 1 ) )
|
||||
.traverseRelation("templateType", JoinType.INNER )
|
||||
.add( AuditEntity.property( "name" ).eq( "Type1" ) )
|
||||
.up()
|
||||
.getResultList();
|
||||
} );
|
||||
|
||||
fail( "Test should have thrown IllegalStateException due to selectEntitiesOnly=false" );
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
assertTyping( IllegalStateException.class, e );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "TemplateType")
|
||||
@Audited
|
||||
public static class TemplateType {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
TemplateType() {
|
||||
this( null, null );
|
||||
}
|
||||
|
||||
TemplateType(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Template")
|
||||
@Audited
|
||||
public static class Template {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
@ManyToOne
|
||||
private TemplateType templateType;
|
||||
|
||||
Template() {
|
||||
this( null, null, null );
|
||||
}
|
||||
|
||||
Template(Integer id, String name, TemplateType type) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.templateType = type;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public TemplateType getTemplateType() {
|
||||
return templateType;
|
||||
}
|
||||
|
||||
public void setTemplateType(TemplateType templateType) {
|
||||
this.templateType = templateType;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue