HHH-13817 Support to-one relation traversals in RevisionsOfEntity queries

This commit is contained in:
Chris Cranford 2021-12-17 14:38:15 -05:00 committed by Chris Cranford
parent 96e7d4cbb1
commit 9ace8a9dd3
10 changed files with 596 additions and 119 deletions

View File

@ -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;

View File

@ -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 );
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
);
}
}

View File

@ -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();

View File

@ -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 );

View File

@ -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 );
}
}

View File

@ -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;
}
}
}