HHH-11735 Support traversal of to-many-associations in audit queries.

This commit is contained in:
Felix Feisst 2017-05-09 07:57:44 -04:00 committed by Chris Cranford
parent b384b37f39
commit bb09222102
14 changed files with 1329 additions and 73 deletions

View File

@ -178,7 +178,7 @@ public class MiddleTableCollectionMetadataGenerator extends AbstractCollectionMe
addMapper( context, commonCollectionMapperData, elementComponentData, indexComponentData );
// Storing information about this relation.
storeMiddleEntityRelationInformation( context, mappedBy );
storeMiddleEntityRelationInformation( context, mappedBy, referencingIdData, referencedPrefix, auditMiddleEntityName );
}
private String getMiddleTableName(CollectionMetadataContext context) {
@ -231,20 +231,42 @@ public class MiddleTableCollectionMetadataGenerator extends AbstractCollectionMe
return getMetadataBuildingContext().getMetadataCollector().getEntityBinding( context.getReferencedEntityName() );
}
private void storeMiddleEntityRelationInformation(CollectionMetadataContext context, String mappedBy) {
private void storeMiddleEntityRelationInformation(
CollectionMetadataContext context,
String mappedBy,
MiddleIdData referencingIdData,
String referencedPrefix,
String auditMiddleEntityName) {
// Only if this is a relation (when there is a referenced entity).
if ( context.getReferencedEntityName() != null ) {
final IdMappingData referencedIdMapping = getReferencedIdMappingData(
context.getReferencingEntityName(),
context.getReferencedEntityName(),
context.getPropertyAuditingData(),
true
);
final MiddleIdData referencedIdData = createMiddleIdData(
referencedIdMapping,
referencedPrefix + "_",
context.getReferencedEntityName()
);
if ( context.getCollection().isInverse() ) {
context.getReferencingEntityConfiguration().addToManyMiddleNotOwningRelation(
context.getPropertyName(),
mappedBy,
context.getReferencedEntityName()
context.getReferencedEntityName(),
referencingIdData,
referencedIdData,
auditMiddleEntityName
);
}
else {
context.getReferencingEntityConfiguration().addToManyMiddleRelation(
context.getPropertyName(),
context.getReferencedEntityName()
context.getReferencedEntityName(),
referencingIdData,
referencedIdData,
auditMiddleEntityName
);
}
}

View File

@ -12,6 +12,7 @@ import java.util.Map;
import org.hibernate.envers.internal.entities.mapper.ExtendedPropertyMapper;
import org.hibernate.envers.internal.entities.mapper.PropertyMapper;
import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
/**
* Runtime representation of an entity that may or may not be audited.
@ -109,13 +110,21 @@ public class EntityConfiguration {
idMapper,
fakeBidirectionalRelationMapper,
fakeBidirectionalRelationIndexMapper,
null,
null,
null,
true,
indexed
)
);
}
public void addToManyMiddleRelation(String fromPropertyName, String toEntityName) {
public void addToManyMiddleRelation(
String fromPropertyName,
String toEntityName,
MiddleIdData referencingIdData,
MiddleIdData referencedIdData,
String auditMiddleEntityName) {
relations.put(
fromPropertyName,
RelationDescription.toMany(
@ -126,13 +135,22 @@ public class EntityConfiguration {
null,
null,
null,
referencingIdData,
referencedIdData,
auditMiddleEntityName,
true,
false
)
);
}
public void addToManyMiddleNotOwningRelation(String fromPropertyName, String mappedByPropertyName, String toEntityName) {
public void addToManyMiddleNotOwningRelation(
String fromPropertyName,
String mappedByPropertyName,
String toEntityName,
MiddleIdData referencingIdData,
MiddleIdData referencedIdData,
String auditMiddleEntityName) {
relations.put(
fromPropertyName,
RelationDescription.toMany(
@ -143,6 +161,9 @@ public class EntityConfiguration {
null,
null,
null,
referencingIdData,
referencedIdData,
auditMiddleEntityName,
true,
false
)

View File

@ -8,6 +8,7 @@ package org.hibernate.envers.internal.entities;
import org.hibernate.envers.internal.entities.mapper.PropertyMapper;
import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
/**
* @author Adam Warski (adam at warski dot org)
@ -22,6 +23,9 @@ public class RelationDescription {
private final IdMapper idMapper;
private final PropertyMapper fakeBidirectionalRelationMapper;
private final PropertyMapper fakeBidirectionalRelationIndexMapper;
private final MiddleIdData referencingIdData;
private final MiddleIdData referencedIdData;
private final String auditMiddleEntityName;
private final boolean insertable;
private final boolean indexed;
private boolean bidirectional;
@ -38,7 +42,7 @@ public class RelationDescription {
boolean ignoreNotFound) {
return new RelationDescription(
fromPropertyName, relationType, toEntityName, mappedByPropertyName, idMapper,
fakeBidirectionalRelationMapper, fakeBidirectionalRelationIndexMapper, insertable, ignoreNotFound, false
fakeBidirectionalRelationMapper, fakeBidirectionalRelationIndexMapper, null, null, null, insertable, ignoreNotFound, false
);
}
@ -50,6 +54,9 @@ public class RelationDescription {
IdMapper idMapper,
PropertyMapper fakeBidirectionalRelationMapper,
PropertyMapper fakeBidirectionalRelationIndexMapper,
MiddleIdData referencingIdData,
MiddleIdData referencedIdData,
String auditMiddleEntityName,
boolean insertable,
boolean indexed) {
// Envers populates collections by executing dedicated queries. Special handling of
@ -58,7 +65,7 @@ public class RelationDescription {
// Therefore assigning false to ignoreNotFound.
return new RelationDescription(
fromPropertyName, relationType, toEntityName, mappedByPropertyName, idMapper, fakeBidirectionalRelationMapper,
fakeBidirectionalRelationIndexMapper, insertable, false, indexed
fakeBidirectionalRelationIndexMapper, referencingIdData, referencedIdData, auditMiddleEntityName, insertable, false, indexed
);
}
@ -70,6 +77,9 @@ public class RelationDescription {
IdMapper idMapper,
PropertyMapper fakeBidirectionalRelationMapper,
PropertyMapper fakeBidirectionalRelationIndexMapper,
MiddleIdData referencingIdData,
MiddleIdData referencedIdData,
String auditMiddleEntityName,
boolean insertable,
boolean ignoreNotFound,
boolean indexed) {
@ -81,6 +91,9 @@ public class RelationDescription {
this.idMapper = idMapper;
this.fakeBidirectionalRelationMapper = fakeBidirectionalRelationMapper;
this.fakeBidirectionalRelationIndexMapper = fakeBidirectionalRelationIndexMapper;
this.referencingIdData = referencingIdData;
this.referencedIdData = referencedIdData;
this.auditMiddleEntityName = auditMiddleEntityName;
this.insertable = insertable;
this.indexed = indexed;
this.bidirectional = false;
@ -118,6 +131,18 @@ public class RelationDescription {
return fakeBidirectionalRelationIndexMapper;
}
public MiddleIdData getReferencingIdData() {
return referencingIdData;
}
public MiddleIdData getReferencedIdData() {
return referencedIdData;
}
public String getAuditMiddleEntityName() {
return auditMiddleEntityName;
}
public boolean isInsertable() {
return insertable;
}

View File

@ -9,6 +9,7 @@ package org.hibernate.envers.query.criteria.internal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.envers.boot.internal.EnversService;
@ -48,13 +49,20 @@ public abstract class CriteriaTools {
return null;
}
if ( relationDesc.getRelationType() == RelationType.TO_ONE ) {
if ( relationDesc.getRelationType() == RelationType.TO_ONE
|| relationDesc.getRelationType() == RelationType.TO_MANY_MIDDLE
|| relationDesc.getRelationType() == RelationType.TO_MANY_NOT_OWNING
|| relationDesc.getRelationType() == RelationType.TO_MANY_MIDDLE_NOT_OWNING ) {
return relationDesc;
}
throw new AuditException(
"This type of relation (" + entityName + "." + propertyName +
") isn't supported and can't be used in queries."
String.format(
Locale.ENGLISH,
"This type of relation (%s.%s) isn't supported and can't be used in queries.",
entityName,
propertyName
)
);
}

View File

@ -6,8 +6,12 @@
*/
package org.hibernate.envers.query.criteria.internal;
import java.util.Locale;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.entities.RelationType;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
@ -43,8 +47,18 @@ public class NotNullAuditExpression extends AbstractAtomicExpression {
if ( relatedEntity == null ) {
parameters.addNotNullRestriction( alias, propertyName );
}
else {
else if ( relatedEntity.getRelationType() == RelationType.TO_ONE ) {
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, null, alias, null, false );
}
else {
throw new AuditException(
String.format(
Locale.ENGLISH,
"This type of relation (%s.%s) can't be used with not null restrictions.",
entityName,
propertyName
)
);
}
}
}

View File

@ -6,8 +6,12 @@
*/
package org.hibernate.envers.query.criteria.internal;
import java.util.Locale;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.entities.RelationType;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
@ -43,8 +47,18 @@ public class NullAuditExpression extends AbstractAtomicExpression {
if ( relatedEntity == null ) {
parameters.addNullRestriction( alias, propertyName );
}
else {
else if ( relatedEntity.getRelationType() == RelationType.TO_ONE ) {
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, null, alias, null, true );
}
else {
throw new AuditException(
String.format(
Locale.ENGLISH,
"This type of relation (%s.%s) can't be used with null restrictions",
entityName,
propertyName
)
);
}
}
}

View File

@ -6,13 +6,15 @@
*/
package org.hibernate.envers.query.criteria.internal;
import java.util.Locale;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.entities.RelationType;
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.criteria.AuditCriterion;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
/**
@ -49,7 +51,16 @@ public class RelatedAuditEqualityExpression extends AbstractAtomicExpression {
RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName );
if ( relatedEntity == null ) {
throw new AuditException(
"This criterion can only be used on a property that is a relation to another property."
"This criterion can only be used on a property that is a relation to another property." );
}
else if ( relatedEntity.getRelationType() != RelationType.TO_ONE ) {
throw new AuditException(
String.format(
Locale.ENGLISH,
"This type of relation (%s.%s) can't be used with related equality restrictions",
entityName,
propertyName
)
);
}
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, id, alias, null, equals );

View File

@ -7,15 +7,16 @@
package org.hibernate.envers.query.criteria.internal;
import java.util.List;
import java.util.Locale;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.entities.RelationType;
import org.hibernate.envers.internal.entities.mapper.id.QueryParameterData;
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.criteria.AuditCriterion;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
/**
@ -51,7 +52,16 @@ public class RelatedAuditInExpression extends AbstractAtomicExpression {
RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName );
if ( relatedEntity == null ) {
throw new AuditException(
"The criterion can only be used on a property that is a relation to another property."
"The criterion can only be used on a property that is a relation to another property." );
}
else if ( relatedEntity.getRelationType() != RelationType.TO_ONE ) {
throw new AuditException(
String.format(
Locale.ENGLISH,
"This type of relation (%s.%s) can't be used with related in restrictions",
entityName,
propertyName
)
);
}

View File

@ -6,10 +6,13 @@
*/
package org.hibernate.envers.query.criteria.internal;
import java.util.Locale;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.entities.RelationType;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
@ -59,7 +62,13 @@ public class SimpleAuditExpression extends AbstractAtomicExpression {
final Type type = getPropertyType( session, entityName, propertyName );
if ( type != null && type.isComponentType() ) {
if ( !"=".equals( op ) && !"<>".equals( op ) ) {
throw new AuditException( "Component-based criterion is not supported for op: " + op );
throw new AuditException(
String.format(
Locale.ENGLISH,
"Component-based criterion is not supported for op: %s",
op
)
);
}
final ComponentType componentType = (ComponentType) type;
for ( int i = 0; i < componentType.getPropertyNames().length; i++ ) {
@ -76,16 +85,30 @@ public class SimpleAuditExpression extends AbstractAtomicExpression {
parameters.addWhereWithParam( alias, propertyName, op, value );
}
}
else {
else if ( relatedEntity.getRelationType() == RelationType.TO_ONE ) {
if ( !"=".equals( op ) && !"<>".equals( op ) ) {
throw new AuditException(
"This type of operation: " + op + " (" + entityName + "." + propertyName +
") isn't supported and can't be used in queries."
String.format(
Locale.ENGLISH,
"This type of operation: %s (%s.%s) isn't supported and can't be used in queries.",
op,
entityName,
propertyName
)
);
}
Object id = relatedEntity.getIdMapper().mapToIdFromEntity( value );
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, id, alias, null, "=".equals( op ) );
}
else {
throw new AuditException(
String.format(
"This type of relation (%s.%s) can't be used in audit query restrictions.",
entityName,
propertyName
)
);
}
}
/**

View File

@ -9,6 +9,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;
import jakarta.persistence.NoResultException;
import jakarta.persistence.NonUniqueResultException;
@ -18,10 +19,12 @@ import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.Incubating;
import org.hibernate.LockMode;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.configuration.Configuration;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.RelationDescription;
import org.hibernate.envers.internal.entities.RelationType;
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;
@ -47,8 +50,9 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
private final QueryBuilder queryBuilder;
private final JoinType joinType;
private final String entityName;
private final IdMapper ownerAssociationIdMapper;
private final RelationDescription relationDescription;
private final String ownerAlias;
private final String ownerEntityName;
private final String alias;
private final Map<String, String> aliasToEntityNameMap;
private final List<AuditCriterion> criterions = new ArrayList<>();
@ -72,17 +76,24 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
this.queryBuilder = queryBuilder;
this.joinType = joinType;
String ownerEntityName = aliasToEntityNameMap.get( ownerAlias );
ownerEntityName = aliasToEntityNameMap.get( ownerAlias );
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" );
throw new IllegalArgumentException(
String.format(
Locale.ENGLISH,
"Property %s of entity %s is not a valid association for queries",
propertyName,
ownerEntityName
)
);
}
this.entityName = relationDescription.getToEntityName();
this.ownerAssociationIdMapper = relationDescription.getIdMapper();
this.relationDescription = relationDescription;
this.ownerAlias = ownerAlias;
this.alias = userSuppliedAlias == null ? queryBuilder.generateAlias() : userSuppliedAlias;
aliasToEntityNameMap.put( this.alias, entityName );
@ -241,25 +252,153 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
}
protected void addCriterionsToQuery(AuditReaderImplementor versionsReader) {
if ( enversService.getEntitiesConfigurations().isVersioned( entityName ) ) {
Configuration configuration = enversService.getConfig();
String auditEntityName = configuration.getAuditEntityName( entityName );
Parameters joinConditionParameters = queryBuilder.addJoin( joinType, auditEntityName, alias, false );
final Configuration configuration = enversService.getConfig();
boolean targetIsAudited = enversService.getEntitiesConfigurations().isVersioned( entityName );
String targetEntityName = entityName;
if ( targetIsAudited ) {
targetEntityName = configuration.getAuditEntityName( entityName );
}
String originalIdPropertyName = configuration.getOriginalIdPropertyName();
String revisionPropertyPath = configuration.getRevisionNumberPath();
if ( relationDescription.getRelationType() == RelationType.TO_ONE ) {
Parameters joinConditionParameters = queryBuilder.addJoin( joinType, targetEntityName, alias, false );
// owner.reference_id = target.originalId.id
String originalIdPropertyName = configuration.getOriginalIdPropertyName();
IdMapper idMapperTarget = enversService.getEntitiesConfigurations().get( entityName ).getIdMapper();
final String prefix = alias.concat( "." ).concat( originalIdPropertyName );
ownerAssociationIdMapper.addIdsEqualToQuery(
IdMapper idMapperTarget;
String prefix;
if ( targetIsAudited ) {
idMapperTarget = enversService.getEntitiesConfigurations().get( entityName ).getIdMapper();
prefix = alias.concat( "." ).concat( originalIdPropertyName );
}
else {
idMapperTarget = enversService.getEntitiesConfigurations()
.getNotVersionEntityConfiguration( entityName )
.getIdMapper();
prefix = alias;
}
relationDescription.getIdMapper().addIdsEqualToQuery(
joinConditionParameters,
ownerAlias,
idMapperTarget,
prefix
);
}
else if ( relationDescription.getRelationType() == RelationType.TO_MANY_NOT_OWNING ) {
if ( !targetIsAudited ) {
throw new AuditException(
String.format(
Locale.ENGLISH,
"Cannot build queries for relation type %s to non audited target entities",
relationDescription.getRelationType()
)
);
}
Parameters joinConditionParameters = queryBuilder.addJoin( joinType, targetEntityName, alias, false );
// owner.originalId.id = target.reference_id
IdMapper idMapperOwner = enversService.getEntitiesConfigurations().get( ownerEntityName ).getIdMapper();
String prefix = ownerAlias.concat( "." ).concat( originalIdPropertyName );
relationDescription.getIdMapper().addIdsEqualToQuery(
joinConditionParameters,
alias,
idMapperOwner,
prefix );
}
else if ( relationDescription.getRelationType() == RelationType.TO_MANY_MIDDLE
|| relationDescription.getRelationType() == RelationType.TO_MANY_MIDDLE_NOT_OWNING ) {
if ( !targetIsAudited && relationDescription.getRelationType() == RelationType.TO_MANY_MIDDLE_NOT_OWNING ) {
throw new AuditException(
String.format(
Locale.ENGLISH,
"Cannot build queries for relation type %s to non audited target entities",
relationDescription.getRelationType()
)
);
}
String middleEntityAlias = queryBuilder.generateAlias();
// join middle_entity
Parameters joinConditionParametersMiddle = queryBuilder.addJoin(
joinType,
relationDescription.getAuditMiddleEntityName(),
middleEntityAlias,
false
);
// join target_entity
Parameters joinConditionParametersTarget = queryBuilder.addJoin( joinType, targetEntityName, alias, false );
Parameters middleParameters = queryBuilder.addParameters( middleEntityAlias );
String middleOriginalIdPropertyPath = middleEntityAlias + "." + originalIdPropertyName;
// join condition: owner.reference_id = middle.id_ref_ing
String ownerPrefix = ownerAlias + "." + originalIdPropertyName;
MiddleIdData referencingIdData = relationDescription.getReferencingIdData();
referencingIdData.getPrefixedMapper().addIdsEqualToQuery(
joinConditionParametersMiddle,
middleOriginalIdPropertyPath,
referencingIdData.getOriginalMapper(),
ownerPrefix
);
// join condition: middle.id_ref_ed = target.id
String targetPrefix = alias;
if ( targetIsAudited ) {
targetPrefix = alias + "." + originalIdPropertyName;
}
MiddleIdData referencedIdData = relationDescription.getReferencedIdData();
referencedIdData.getPrefixedMapper().addIdsEqualToQuery(
joinConditionParametersTarget,
middleOriginalIdPropertyPath,
referencedIdData.getOriginalMapper(),
targetPrefix
);
// filter revisions of middle entity
Parameters middleParametersToUse = middleParameters;
if ( joinType == JoinType.LEFT ) {
middleParametersToUse = middleParameters.addSubParameters( Parameters.OR );
middleParametersToUse.addNullRestriction( revisionPropertyPath, true );
middleParametersToUse = middleParametersToUse.addSubParameters( Parameters.AND );
}
enversService.getAuditStrategy().addAssociationAtRevisionRestriction(
queryBuilder,
middleParametersToUse,
revisionPropertyPath,
configuration.getRevisionEndFieldName(),
true,
referencingIdData,
relationDescription.getAuditMiddleEntityName(),
middleOriginalIdPropertyPath,
revisionPropertyPath,
originalIdPropertyName,
middleEntityAlias,
true
);
// filter deleted middle entities
if ( joinType == JoinType.LEFT ) {
middleParametersToUse = middleParameters.addSubParameters( Parameters.OR );
middleParametersToUse.addNullRestriction( configuration.getRevisionTypePropertyName(), true );
}
middleParametersToUse.addWhereWithParam( configuration.getRevisionTypePropertyName(), true, "!=", RevisionType.DEL );
}
else {
throw new AuditException(
String.format(
Locale.ENGLISH,
"Cannot build queries for relation type %s",
relationDescription.getRelationType()
)
);
}
if ( targetIsAudited ) {
// filter revision of target entity
Parameters parametersToUse = parameters;
String revisionPropertyPath = configuration.getRevisionNumberPath();
if ( joinType == JoinType.LEFT ) {
parametersToUse = parameters.addSubParameters( Parameters.OR );
parametersToUse.addNullRestriction( revisionPropertyPath, true );
@ -270,7 +409,7 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
enversService.getEntitiesConfigurations().get( entityName ).getIdMappingData(),
null,
entityName,
enversService.getEntitiesConfigurations().isVersioned( entityName )
true
);
enversService.getAuditStrategy().addEntityAtRevisionRestriction(
configuration,
@ -287,19 +426,6 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
true
);
}
else {
Parameters joinConditionParameters = queryBuilder.addJoin( joinType, entityName, alias, false );
// owner.reference_id = target.id
final IdMapper idMapperTarget = enversService.getEntitiesConfigurations()
.getNotVersionEntityConfiguration( entityName )
.getIdMapper();
ownerAssociationIdMapper.addIdsEqualToQuery(
joinConditionParameters,
ownerAlias,
idMapperTarget,
alias
);
}
for ( AuditCriterion criterion : criterions ) {
criterion.addToQuery(

View File

@ -16,7 +16,6 @@ import java.util.Set;
import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.orm.test.envers.BaseEnversFunctionalTestCase;
import org.hibernate.orm.test.envers.Priority;
@ -371,28 +370,6 @@ public class AuditedDynamicComponentsAdvancedCasesTest extends BaseEnversFunctio
assertTyping( IllegalArgumentException.class, e );
}
try {
getAuditReader().createQuery()
.forEntitiesAtRevision( AdvancedEntity.class, 1 )
.add(
AuditEntity.property( "dynamicConfiguration_" + INTERNAL_MAP_WITH_MANY_TO_MANY )
.eq( entity.getDynamicConfiguration().get( INTERNAL_MAP_WITH_MANY_TO_MANY ) )
)
.getResultList();
Assert.fail();
}
catch ( Exception e ) {
if ( getSession().getTransaction().isActive() ) {
getSession().getTransaction().rollback();
}
assertTyping( AuditException.class, e );
Assert.assertEquals(
"This type of relation (org.hibernate.orm.test.envers.integration.components.dynamic.AdvancedEntity.dynamicConfiguration_internalMapWithEntities) isn't supported and can't be used in queries.",
e.getMessage()
);
}
}
@Test

View File

@ -148,7 +148,8 @@ public class SanityCheckTest extends BaseEnversFunctionalTestCase {
catch ( Exception e ) {
assertTyping( AuditException.class, e );
assertEquals(
"This type of relation (org.hibernate.orm.test.envers.integration.components.dynamic.PlainEntity.component_manyToManyList) isn't supported and can't be used in queries.",
"This type of relation (org.hibernate.orm.test.envers.integration.components.dynamic.PlainEntity." +
"component_manyToManyList) can't be used in audit query restrictions.",
e.getMessage()
);
}

View File

@ -0,0 +1,496 @@
/*
* 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.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Id;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.criteria.JoinType;
import org.hibernate.envers.AuditJoinTable;
import org.hibernate.envers.Audited;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
import org.hibernate.orm.test.envers.Priority;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
@TestForIssue(jiraKey = "HHH-11735")
public class AssociationToManyJoinQueryTest extends BaseEnversJPAFunctionalTestCase {
private EntityA aEmpty;
private EntityA aOneToMany;
private EntityA aManyToMany;
private EntityA aBidiOneToManyInverse;
private EntityA aBidiManyToManyOwning;
private EntityA aBidiManyToManyInverse;
private EntityB b1;
private EntityB b2;
private EntityB b3;
private EntityC c1;
private EntityC c2;
private EntityC c3;
@Entity(name = "EntityA")
@Audited
public static class EntityA {
@Id
private Long id;
private String name;
@OneToMany
@AuditJoinTable(name = "entitya_onetomany_entityb_aud")
private Set<EntityB> bOneToMany = new HashSet<>();
@ManyToMany
@JoinTable(name = "entitya_manytomany_entityb")
private Set<EntityB> bManyToMany = new HashSet<>();
@OneToMany(mappedBy = "bidiAManyToOneOwning")
private Set<EntityC> bidiCOneToManyInverse = new HashSet<>();
@ManyToMany
@AuditJoinTable(name = "entitya_entityc_bidi_aud")
private Set<EntityC> bidiCManyToManyOwning = new HashSet<>();
@ManyToMany(mappedBy = "bidiAManyToManyOwning")
private Set<EntityC> bidiCManyToManyInverse = new HashSet<>();
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 Set<EntityB> getbOneToMany() {
return bOneToMany;
}
public void setbOneToMany(Set<EntityB> bOneToMany) {
this.bOneToMany = bOneToMany;
}
public Set<EntityB> getbManyToMany() {
return bManyToMany;
}
public void setbManyToMany(Set<EntityB> bManyToMany) {
this.bManyToMany = bManyToMany;
}
public Set<EntityC> getBidiCOneToManyInverse() {
return bidiCOneToManyInverse;
}
public void setBidiCOneToManyInverse(Set<EntityC> bidiCOneToManyInverse) {
this.bidiCOneToManyInverse = bidiCOneToManyInverse;
}
public Set<EntityC> getBidiCManyToManyOwning() {
return bidiCManyToManyOwning;
}
public void setBidiCManyToManyOwning(Set<EntityC> bidiCManyToManyOwning) {
this.bidiCManyToManyOwning = bidiCManyToManyOwning;
}
public Set<EntityC> getBidiCManyToManyInverse() {
return bidiCManyToManyInverse;
}
public void setBidiCManyToManyInverse(Set<EntityC> bidiCManyToManyInverse) {
this.bidiCManyToManyInverse = bidiCManyToManyInverse;
}
}
@Entity(name = "EntityB")
@Audited
public static class EntityB {
@Id
private Long id;
private String name;
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;
}
}
@Entity(name = "EntityC")
@Audited
public static class EntityC {
@Id
private Long id;
private String name;
@ManyToOne
private EntityA bidiAManyToOneOwning;
@ManyToMany
@JoinTable(name = "entityc_entitya_bidi")
private Set<EntityA> bidiAManyToManyOwning = new HashSet<>();
@ManyToMany(mappedBy = "bidiCManyToManyOwning")
private Set<EntityA> bidiAManyToManyInverse = new HashSet<>();
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 EntityA getBidiAManyToOneOwning() {
return bidiAManyToOneOwning;
}
public void setBidiAManyToOneOwning(EntityA bidiAManyToOneOwning) {
this.bidiAManyToOneOwning = bidiAManyToOneOwning;
}
public Set<EntityA> getBidiAManyToManyOwning() {
return bidiAManyToManyOwning;
}
public void setBidiAManyToManyOwning(Set<EntityA> bidiAManyToManyOwning) {
this.bidiAManyToManyOwning = bidiAManyToManyOwning;
}
public Set<EntityA> getBidiAManyToManyInverse() {
return bidiAManyToManyInverse;
}
public void setBidiAManyToManyInverse(Set<EntityA> bidiAManyToManyInverse) {
this.bidiAManyToManyInverse = bidiAManyToManyInverse;
}
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[]{ EntityA.class, EntityB.class, EntityC.class };
}
@Test
@Priority(10)
public void initData() {
// revision 1:
EntityManager em = getEntityManager();
em.getTransaction().begin();
b1 = new EntityB();
b1.setId( 21L );
b1.setName( "B1" );
em.persist( b1 );
b2 = new EntityB();
b2.setId( 22L );
b2.setName( "B2" );
em.persist( b2 );
b3 = new EntityB();
b3.setId( 23L );
b3.setName( "B3" );
em.persist( b3 );
c1 = new EntityC();
c1.setId( 31L );
c1.setName( "C1" );
em.persist( c1 );
c2 = new EntityC();
c2.setId( 32L );
c2.setName( "C2" );
em.persist( c2 );
c3 = new EntityC();
c3.setId( 33L );
c3.setName( "C3" );
em.persist( c3 );
aEmpty = new EntityA();
aEmpty.setId( 1L );
aEmpty.setName( "aEmpty" );
em.persist( aEmpty );
aOneToMany = new EntityA();
aOneToMany.setId( 2L );
aOneToMany.setName( "aOneToMany" );
aOneToMany.getbOneToMany().add( b1 );
aOneToMany.getbOneToMany().add( b3 );
em.persist( aOneToMany );
aManyToMany = new EntityA();
aManyToMany.setId( 3L );
aManyToMany.setName( "aManyToMany" );
aManyToMany.getbManyToMany().add( b1 );
aManyToMany.getbManyToMany().add( b3 );
em.persist( aManyToMany );
aBidiOneToManyInverse = new EntityA();
aBidiOneToManyInverse.setId( 4L );
aBidiOneToManyInverse.setName( "aBidiOneToManyInverse" );
aBidiOneToManyInverse.getBidiCOneToManyInverse().add( c1 );
c1.setBidiAManyToOneOwning( aBidiOneToManyInverse );
aBidiOneToManyInverse.getBidiCOneToManyInverse().add( c3 );
c3.setBidiAManyToOneOwning( aBidiOneToManyInverse );
em.persist( aBidiOneToManyInverse );
aBidiManyToManyOwning = new EntityA();
aBidiManyToManyOwning.setId( 5L );
aBidiManyToManyOwning.setName( "aBidiManyToManyOwning" );
aBidiManyToManyOwning.getBidiCManyToManyOwning().add( c1 );
c1.getBidiAManyToManyInverse().add( aBidiManyToManyOwning );
aBidiManyToManyOwning.getBidiCManyToManyOwning().add( c3 );
c3.getBidiAManyToManyInverse().add( aBidiManyToManyOwning );
em.persist( aBidiManyToManyOwning );
aBidiManyToManyInverse = new EntityA();
aBidiManyToManyInverse.setId( 6L );
aBidiManyToManyInverse.setName( "aBidiManyToManyInverse" );
aBidiManyToManyInverse.getBidiCManyToManyInverse().add( c1 );
c1.getBidiAManyToManyOwning().add( aBidiManyToManyInverse );
aBidiManyToManyInverse.getBidiCManyToManyInverse().add( c3 );
c3.getBidiAManyToManyOwning().add( aBidiManyToManyInverse );
em.persist( aBidiManyToManyInverse );
em.getTransaction().commit();
// revision 2:
em.getTransaction().begin();
aOneToMany.getbOneToMany().remove( b1 );
aOneToMany.getbOneToMany().add( b2 );
aManyToMany.getbManyToMany().remove( b1 );
aManyToMany.getbManyToMany().add( b2 );
aBidiOneToManyInverse.getBidiCOneToManyInverse().remove( c1 );
c1.setBidiAManyToOneOwning( null );
aBidiOneToManyInverse.getBidiCOneToManyInverse().add( c2 );
c2.setBidiAManyToOneOwning( aBidiOneToManyInverse );
aBidiManyToManyOwning.getBidiCManyToManyOwning().remove( c1 );
c1.getBidiAManyToManyInverse().remove( aBidiManyToManyOwning );
aBidiManyToManyOwning.getBidiCManyToManyOwning().add( c2 );
c2.getBidiAManyToManyInverse().add( aBidiManyToManyOwning );
aBidiManyToManyInverse.getBidiCManyToManyInverse().remove( c1 );
c1.getBidiAManyToManyOwning().remove( aBidiManyToManyInverse );
aBidiManyToManyInverse.getBidiCManyToManyInverse().add( c2 );
c2.getBidiAManyToManyOwning().add( aBidiManyToManyInverse );
em.getTransaction().commit();
}
@Test
public void testOneToManyInnerJoin() {
List<?> list1 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bOneToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B1" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list1.size() );
EntityA entityA1 = (EntityA) list1.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aOneToMany.getId(), entityA1.getId() );
List<?> list2 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bOneToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B2" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since B2 has been added in revision 2", list2.isEmpty() );
List<?> list3 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bOneToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B2" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list3.size() );
EntityA entityA3 = (EntityA) list3.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aOneToMany.getId(), entityA3.getId() );
List<?> list4 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bOneToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B1" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since B1 has been removed in revision 2", list4.isEmpty() );
}
@Test
public void testOneToManyLeftJoin() {
List<?> list = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bOneToMany", JoinType.LEFT, "b" ).up()
.add( AuditEntity.or( AuditEntity.property( "name" ).eq( "aEmpty" ), AuditEntity.property( "b", "name" ).eq( "B1" ) ) )
.getResultList();
assertTrue( "Expected the correct entities to be resolved", listContainsIds( list, aEmpty.getId(), aOneToMany.getId() ) );
}
@Test
public void testManyToManyInnerJoin() {
List<?> list1 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bManyToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B1" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list1.size() );
EntityA entityA1 = (EntityA) list1.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aManyToMany.getId(), entityA1.getId() );
List<?> list2 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bManyToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B2" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since B2 has been added in revision 2", list2.isEmpty() );
List<?> list3 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bManyToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B2" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list3.size() );
EntityA entityA3 = (EntityA) list3.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aManyToMany.getId(), entityA3.getId() );
List<?> list4 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bManyToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B1" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since B1 has been removed in revision 2", list4.isEmpty() );
}
@Test
public void testManyToManyLeftJoin() {
List<?> list = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bManyToMany", JoinType.LEFT, "b" ).up()
.add( AuditEntity.or( AuditEntity.property( "name" ).eq( "aEmpty" ), AuditEntity.property( "b", "name" ).eq( "B1" ) ) )
.getResultList();
assertTrue( "Expected the correct entities to be resolved", listContainsIds( list, aEmpty.getId(), aManyToMany.getId() ) );
}
@Test
public void testBidiOneToManyInnerJoin() {
List<?> list1 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bidiCOneToManyInverse", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C1" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list1.size() );
EntityA entityA1 = (EntityA) list1.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aBidiOneToManyInverse.getId(), entityA1.getId() );
List<?> list2 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bidiCOneToManyInverse", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C2" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since C2 has been added in revision 2", list2.isEmpty() );
List<?> list3 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bidiCOneToManyInverse", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C2" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list3.size() );
EntityA entityA3 = (EntityA) list3.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aBidiOneToManyInverse.getId(), entityA3.getId() );
List<?> list4 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bidiCOneToManyInverse", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C1" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since C1 has been removed in revision 2", list4.isEmpty() );
}
@Test
public void testBidiOneToManyLeftJoin() {
List<?> list = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bidiCOneToManyInverse", JoinType.LEFT, "c" )
.up()
.add( AuditEntity.or( AuditEntity.property( "name" ).eq( "aEmpty" ), AuditEntity.property( "c", "name" ).eq( "C1" ) ) )
.getResultList();
assertTrue( "Expected the correct entities to be resolved", listContainsIds( list, aEmpty.getId(), aBidiOneToManyInverse.getId() ) );
}
@Test
public void testBidiManyToManyOwningInnerJoin() {
List<?> list1 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bidiCManyToManyOwning", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C1" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list1.size() );
EntityA entityA1 = (EntityA) list1.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aBidiManyToManyOwning.getId(), entityA1.getId() );
List<?> list2 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bidiCManyToManyOwning", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C2" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since C2 has been added in revision 2", list2.isEmpty() );
List<?> list3 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bidiCManyToManyOwning", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C2" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list3.size() );
EntityA entityA3 = (EntityA) list3.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aBidiManyToManyOwning.getId(), entityA3.getId() );
List<?> list4 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bidiCManyToManyOwning", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C1" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since C1 has been removed in revision 2", list4.isEmpty() );
}
@Test
public void testBidiManyToManyOwningLeftJoin() {
List<?> list = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bidiCManyToManyOwning", JoinType.LEFT, "c" )
.up()
.add( AuditEntity.or( AuditEntity.property( "name" ).eq( "aEmpty" ), AuditEntity.property( "c", "name" ).eq( "C1" ) ) )
.getResultList();
assertTrue( "Expected the correct entities to be resolved", listContainsIds( list, aEmpty.getId(), aBidiManyToManyOwning.getId() ) );
}
@Test
public void testBidiManyToManyInverseInnerJoin() {
List<?> list1 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bidiCManyToManyInverse", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C1" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list1.size() );
EntityA entityA1 = (EntityA) list1.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aBidiManyToManyInverse.getId(), entityA1.getId() );
List<?> list2 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bidiCManyToManyInverse", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C2" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since C2 has been added in revision 2", list2.isEmpty() );
List<?> list3 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bidiCManyToManyInverse", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C2" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list3.size() );
EntityA entityA3 = (EntityA) list3.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aBidiManyToManyInverse.getId(), entityA3.getId() );
List<?> list4 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bidiCManyToManyInverse", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C1" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since C1 has been removed in revision 2", list4.isEmpty() );
}
@Test
public void testBidiManyToManyInverseLeftJoin() {
List<?> list = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bidiCManyToManyInverse", JoinType.LEFT, "c" )
.up()
.add( AuditEntity.or( AuditEntity.property( "name" ).eq( "aEmpty" ), AuditEntity.property( "c", "name" ).eq( "C1" ) ) )
.getResultList();
assertTrue( "Expected the correct entities to be resolved", listContainsIds( list, aEmpty.getId(), aBidiManyToManyInverse.getId() ) );
}
private boolean listContainsIds(final Collection<?> entities, final long... ids) {
final Set<Long> idSet = new HashSet<>();
for ( final Object entity : entities ) {
idSet.add( ( (EntityA) entity ).getId() );
}
boolean result = true;
for ( final long id : ids ) {
if ( !idSet.contains( id ) ) {
result = false;
break;
}
}
return result;
}
}

View File

@ -0,0 +1,508 @@
/*
* 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.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Id;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.criteria.JoinType;
import org.hibernate.envers.AuditJoinTable;
import org.hibernate.envers.Audited;
import org.hibernate.envers.RelationTargetAuditMode;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
import org.hibernate.orm.test.envers.Priority;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
/**
* @author Felix Feisst (feisst dot felix at gmail dot com)
*/
@TestForIssue(jiraKey = "HHH-11735")
public class AssociationToNotAuditedManyJoinQueryTest extends BaseEnversJPAFunctionalTestCase {
private EntityA aEmpty;
private EntityA aOneToMany;
private EntityA aManyToMany;
private EntityA aBidiOneToManyInverse;
private EntityA aBidiManyToManyOwning;
private EntityA aBidiManyToManyInverse;
private EntityB b1;
private EntityB b2;
private EntityB b3;
private EntityC c1;
private EntityC c2;
private EntityC c3;
@Entity(name = "EntityA")
@Audited
public static class EntityA {
@Id
private Long id;
private String name;
@OneToMany
@AuditJoinTable(name = "entitya_onetomany_entityb_aud")
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
private Set<EntityB> bOneToMany = new HashSet<>();
@ManyToMany
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
@JoinTable(name = "entitya_manytomany_entityb")
private Set<EntityB> bManyToMany = new HashSet<>();
// @OneToMany(mappedBy="bidiAManyToOneOwning")
// @Audited(targetAuditMode=RelationTargetAuditMode.NOT_AUDITED)
// private Set<EntityC> bidiCOneToManyInverse = new HashSet<>();
@ManyToMany
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
@AuditJoinTable(name = "entitya_entityc_bidi_aud")
private Set<EntityC> bidiCManyToManyOwning = new HashSet<>();
// @ManyToMany(mappedBy="bidiAManyToManyOwning")
// @Audited(targetAuditMode=RelationTargetAuditMode.NOT_AUDITED)
// private Set<EntityC> bidiCManyToManyInverse = new HashSet<>();
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 Set<EntityB> getbOneToMany() {
return bOneToMany;
}
public void setbOneToMany(Set<EntityB> bOneToMany) {
this.bOneToMany = bOneToMany;
}
public Set<EntityB> getbManyToMany() {
return bManyToMany;
}
public void setbManyToMany(Set<EntityB> bManyToMany) {
this.bManyToMany = bManyToMany;
}
// public Set<EntityC> getBidiCOneToManyInverse() {
// return bidiCOneToManyInverse;
// }
//
//
//
// public void setBidiCOneToManyInverse(Set<EntityC> bidiCOneToManyInverse) {
// this.bidiCOneToManyInverse = bidiCOneToManyInverse;
// }
public Set<EntityC> getBidiCManyToManyOwning() {
return bidiCManyToManyOwning;
}
public void setBidiCManyToManyOwning(Set<EntityC> bidiCManyToManyOwning) {
this.bidiCManyToManyOwning = bidiCManyToManyOwning;
}
// public Set<EntityC> getBidiCManyToManyInverse() {
// return bidiCManyToManyInverse;
// }
//
//
// public void setBidiCManyToManyInverse(Set<EntityC> bidiCManyToManyInverse) {
// this.bidiCManyToManyInverse = bidiCManyToManyInverse;
// }
}
@Entity(name = "EntityB")
public static class EntityB {
@Id
private Long id;
private String name;
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;
}
}
@Entity(name = "EntityC")
public static class EntityC {
@Id
private Long id;
private String name;
// @ManyToOne
// private EntityA bidiAManyToOneOwning;
// @ManyToMany
// @JoinTable(name="entityc_entitya_bidi")
// private Set<EntityA> bidiAManyToManyOwning = new HashSet<>();
@ManyToMany(mappedBy = "bidiCManyToManyOwning")
private Set<EntityA> bidiAManyToManyInverse = new HashSet<>();
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 EntityA getBidiAManyToOneOwning() {
// return bidiAManyToOneOwning;
// }
//
//
// public void setBidiAManyToOneOwning(EntityA bidiAManyToOneOwning) {
// this.bidiAManyToOneOwning = bidiAManyToOneOwning;
// }
// public Set<EntityA> getBidiAManyToManyOwning() {
// return bidiAManyToManyOwning;
// }
//
//
// public void setBidiAManyToManyOwning(Set<EntityA> bidiAManyToManyOwning) {
// this.bidiAManyToManyOwning = bidiAManyToManyOwning;
// }
public Set<EntityA> getBidiAManyToManyInverse() {
return bidiAManyToManyInverse;
}
public void setBidiAManyToManyInverse(Set<EntityA> bidiAManyToManyInverse) {
this.bidiAManyToManyInverse = bidiAManyToManyInverse;
}
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[]{ EntityA.class, EntityB.class, EntityC.class };
}
@Test
@Priority(10)
public void initData() {
// revision 1:
EntityManager em = getEntityManager();
em.getTransaction().begin();
b1 = new EntityB();
b1.setId( 21L );
b1.setName( "B1" );
em.persist( b1 );
b2 = new EntityB();
b2.setId( 22L );
b2.setName( "B2" );
em.persist( b2 );
b3 = new EntityB();
b3.setId( 23L );
b3.setName( "B3" );
em.persist( b3 );
c1 = new EntityC();
c1.setId( 31L );
c1.setName( "C1" );
em.persist( c1 );
c2 = new EntityC();
c2.setId( 32L );
c2.setName( "C2" );
em.persist( c2 );
c3 = new EntityC();
c3.setId( 33L );
c3.setName( "C3" );
em.persist( c3 );
aEmpty = new EntityA();
aEmpty.setId( 1L );
aEmpty.setName( "aEmpty" );
em.persist( aEmpty );
aOneToMany = new EntityA();
aOneToMany.setId( 2L );
aOneToMany.setName( "aOneToMany" );
aOneToMany.getbOneToMany().add( b1 );
aOneToMany.getbOneToMany().add( b3 );
em.persist( aOneToMany );
aManyToMany = new EntityA();
aManyToMany.setId( 3L );
aManyToMany.setName( "aManyToMany" );
aManyToMany.getbManyToMany().add( b1 );
aManyToMany.getbManyToMany().add( b3 );
em.persist( aManyToMany );
aBidiOneToManyInverse = new EntityA();
aBidiOneToManyInverse.setId( 4L );
aBidiOneToManyInverse.setName( "aBidiOneToManyInverse" );
// aBidiOneToManyInverse.getBidiCOneToManyInverse().add( c1 );
// c1.setBidiAManyToOneOwning( aBidiOneToManyInverse );
// aBidiOneToManyInverse.getBidiCOneToManyInverse().add( c3 );
// c3.setBidiAManyToOneOwning( aBidiOneToManyInverse );
em.persist( aBidiOneToManyInverse );
aBidiManyToManyOwning = new EntityA();
aBidiManyToManyOwning.setId( 5L );
aBidiManyToManyOwning.setName( "aBidiManyToManyOwning" );
aBidiManyToManyOwning.getBidiCManyToManyOwning().add( c1 );
c1.getBidiAManyToManyInverse().add( aBidiManyToManyOwning );
aBidiManyToManyOwning.getBidiCManyToManyOwning().add( c3 );
c3.getBidiAManyToManyInverse().add( aBidiManyToManyOwning );
em.persist( aBidiManyToManyOwning );
aBidiManyToManyInverse = new EntityA();
aBidiManyToManyInverse.setId( 6L );
aBidiManyToManyInverse.setName( "aBidiManyToManyInverse" );
// aBidiManyToManyInverse.getBidiCManyToManyInverse().add( c1 );
// c1.getBidiAManyToManyOwning().add( aBidiManyToManyInverse );
// aBidiManyToManyInverse.getBidiCManyToManyInverse().add( c3 );
// c3.getBidiAManyToManyOwning().add( aBidiManyToManyInverse );
em.persist( aBidiManyToManyInverse );
em.getTransaction().commit();
// revision 2:
em.getTransaction().begin();
aOneToMany.getbOneToMany().remove( b1 );
aOneToMany.getbOneToMany().add( b2 );
aManyToMany.getbManyToMany().remove( b1 );
aManyToMany.getbManyToMany().add( b2 );
// aBidiOneToManyInverse.getBidiCOneToManyInverse().remove( c1 );
// c1.setBidiAManyToOneOwning( null );
// aBidiOneToManyInverse.getBidiCOneToManyInverse().add( c2 );
// c2.setBidiAManyToOneOwning( aBidiOneToManyInverse );
aBidiManyToManyOwning.getBidiCManyToManyOwning().remove( c1 );
c1.getBidiAManyToManyInverse().remove( aBidiManyToManyOwning );
aBidiManyToManyOwning.getBidiCManyToManyOwning().add( c2 );
c2.getBidiAManyToManyInverse().add( aBidiManyToManyOwning );
// aBidiManyToManyInverse.getBidiCManyToManyInverse().remove( c1 );
// c1.getBidiAManyToManyOwning().remove( aBidiManyToManyInverse );
// aBidiManyToManyInverse.getBidiCManyToManyInverse().add( c2 );
// c2.getBidiAManyToManyOwning().add( aBidiManyToManyInverse );
em.getTransaction().commit();
}
@Test
public void testOneToManyInnerJoin() {
List<?> list1 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bOneToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B1" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list1.size() );
EntityA entityA1 = (EntityA) list1.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aOneToMany.getId(), entityA1.getId() );
List<?> list2 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bOneToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B2" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since B2 has been added in revision 2", list2.isEmpty() );
List<?> list3 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bOneToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B2" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list3.size() );
EntityA entityA3 = (EntityA) list3.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aOneToMany.getId(), entityA3.getId() );
List<?> list4 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bOneToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B1" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since B1 has been removed in revision 2", list4.isEmpty() );
}
@Test
public void testOneToManyLeftJoin() {
List<?> list = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bOneToMany", JoinType.LEFT, "b" ).up()
.add( AuditEntity.or( AuditEntity.property( "name" ).eq( "aEmpty" ), AuditEntity.property( "b", "name" ).eq( "B1" ) ) )
.getResultList();
assertTrue( "Expected the correct entities to be resolved", listContainsIds( list, aEmpty.getId(), aOneToMany.getId() ) );
}
@Test
public void testManyToManyInnerJoin() {
List<?> list1 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bManyToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B1" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list1.size() );
EntityA entityA1 = (EntityA) list1.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aManyToMany.getId(), entityA1.getId() );
List<?> list2 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bManyToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B2" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since B2 has been added in revision 2", list2.isEmpty() );
List<?> list3 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bManyToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B2" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list3.size() );
EntityA entityA3 = (EntityA) list3.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aManyToMany.getId(), entityA3.getId() );
List<?> list4 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bManyToMany", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "B1" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since B1 has been removed in revision 2", list4.isEmpty() );
}
@Test
public void testManyToManyLeftJoin() {
List<?> list = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bManyToMany", JoinType.LEFT, "b" ).up()
.add( AuditEntity.or( AuditEntity.property( "name" ).eq( "aEmpty" ), AuditEntity.property( "b", "name" ).eq( "B1" ) ) )
.getResultList();
assertTrue( "Expected the correct entities to be resolved", listContainsIds( list, aEmpty.getId(), aManyToMany.getId() ) );
}
// @Test
// public void testBidiOneToManyInnerJoin() {
// List<?> list1 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation(
// "bidiCOneToManyInverse", JoinType.INNER ).add( AuditEntity.property( "name" ).eq( "C1" ) ).getResultList();
// assertEquals("Expected exactly one entity", 1, list1.size());
// EntityA entityA1 = (EntityA) list1.get( 0 );
// assertEquals("Expected the correct entity to be resolved", aBidiOneToManyInverse.getId(), entityA1.getId());
//
// List<?> list2 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation(
// "bidiCOneToManyInverse", JoinType.INNER ).add( AuditEntity.property( "name" ).eq( "C2" ) ).getResultList();
// assertTrue("Expected no entities to be returned, since C2 has been added in revision 2", list2.isEmpty());
//
// List<?> list3 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation(
// "bidiCOneToManyInverse", JoinType.INNER ).add( AuditEntity.property( "name" ).eq( "C2" ) ).getResultList();
// assertEquals("Expected exactly one entity", 1, list3.size());
// EntityA entityA3 = (EntityA) list3.get( 0 );
// assertEquals("Expected the correct entity to be resolved", aBidiOneToManyInverse.getId(), entityA3.getId());
//
// List<?> list4 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation(
// "bidiCOneToManyInverse", JoinType.INNER ).add( AuditEntity.property( "name" ).eq( "C1" ) ).getResultList();
// assertTrue("Expected no entities to be returned, since C1 has been removed in revision 2", list4.isEmpty());
// }
//
// @Test
// public void testBidiOneToManyLeftJoin() {
// List<?> list = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation(
// "bidiCOneToManyInverse", JoinType.LEFT, "c" ).up()
// .add( AuditEntity.or( AuditEntity.property( "name" ).eq( "aEmpty" ), AuditEntity.property( "c", "name" ).eq( "C1"
// ) ) )
// .getResultList();
// assertTrue("Expected the correct entities to be resolved", listContainsIds(list, aEmpty.getId(),
// aBidiOneToManyInverse.getId()));
// }
@Test
public void testBidiManyToManyOwningInnerJoin() {
List<?> list1 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bidiCManyToManyOwning", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C1" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list1.size() );
EntityA entityA1 = (EntityA) list1.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aBidiManyToManyOwning.getId(), entityA1.getId() );
List<?> list2 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bidiCManyToManyOwning", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C2" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since C2 has been added in revision 2", list2.isEmpty() );
List<?> list3 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bidiCManyToManyOwning", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C2" ) ).getResultList();
assertEquals( "Expected exactly one entity", 1, list3.size() );
EntityA entityA3 = (EntityA) list3.get( 0 );
assertEquals( "Expected the correct entity to be resolved", aBidiManyToManyOwning.getId(), entityA3.getId() );
List<?> list4 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation( "bidiCManyToManyOwning", JoinType.INNER )
.add( AuditEntity.property( "name" ).eq( "C1" ) ).getResultList();
assertTrue( "Expected no entities to be returned, since C1 has been removed in revision 2", list4.isEmpty() );
}
@Test
public void testBidiManyToManyOwningLeftJoin() {
List<?> list = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation( "bidiCManyToManyOwning", JoinType.LEFT, "c" )
.up()
.add( AuditEntity.or( AuditEntity.property( "name" ).eq( "aEmpty" ), AuditEntity.property( "c", "name" ).eq( "C1" ) ) )
.getResultList();
assertTrue( "Expected the correct entities to be resolved", listContainsIds( list, aEmpty.getId(), aBidiManyToManyOwning.getId() ) );
}
// @Test
// public void testBidiManyToManyInverseInnerJoin() {
// List<?> list1 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation(
// "bidiCManyToManyInverse", JoinType.INNER ).add( AuditEntity.property( "name" ).eq( "C1" ) ).getResultList();
// assertEquals("Expected exactly one entity", 1, list1.size());
// EntityA entityA1 = (EntityA) list1.get( 0 );
// assertEquals("Expected the correct entity to be resolved", aBidiManyToManyInverse.getId(), entityA1.getId());
//
// List<?> list2 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation(
// "bidiCManyToManyInverse", JoinType.INNER ).add( AuditEntity.property( "name" ).eq( "C2" ) ).getResultList();
// assertTrue("Expected no entities to be returned, since C2 has been added in revision 2", list2.isEmpty());
//
// List<?> list3 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation(
// "bidiCManyToManyInverse", JoinType.INNER ).add( AuditEntity.property( "name" ).eq( "C2" ) ).getResultList();
// assertEquals("Expected exactly one entity", 1, list3.size());
// EntityA entityA3 = (EntityA) list3.get( 0 );
// assertEquals("Expected the correct entity to be resolved", aBidiManyToManyInverse.getId(), entityA3.getId());
//
// List<?> list4 = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 2 ).traverseRelation(
// "bidiCManyToManyInverse", JoinType.INNER ).add( AuditEntity.property( "name" ).eq( "C1" ) ).getResultList();
// assertTrue("Expected no entities to be returned, since C1 has been removed in revision 2", list4.isEmpty());
// }
//
// @Test
// public void testBidiManyToManyInverseLeftJoin() {
// List<?> list = getAuditReader().createQuery().forEntitiesAtRevision( EntityA.class, 1 ).traverseRelation(
// "bidiCManyToManyInverse", JoinType.LEFT, "c" ).up()
// .add( AuditEntity.or( AuditEntity.property( "name" ).eq( "aEmpty" ), AuditEntity.property( "c", "name" ).eq( "C1"
// ) ) )
// .getResultList();
// assertTrue("Expected the correct entities to be resolved", listContainsIds(list, aEmpty.getId(),
// aBidiManyToManyInverse.getId()));
// }
private boolean listContainsIds(final Collection<?> entities, final long... ids) {
final Set<Long> idSet = new HashSet<>();
for ( final Object entity : entities ) {
idSet.add( ( (EntityA) entity ).getId() );
}
boolean result = true;
for ( final long id : ids ) {
if ( !idSet.contains( id ) ) {
result = false;
break;
}
}
return result;
}
}