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 ); addMapper( context, commonCollectionMapperData, elementComponentData, indexComponentData );
// Storing information about this relation. // Storing information about this relation.
storeMiddleEntityRelationInformation( context, mappedBy ); storeMiddleEntityRelationInformation( context, mappedBy, referencingIdData, referencedPrefix, auditMiddleEntityName );
} }
private String getMiddleTableName(CollectionMetadataContext context) { private String getMiddleTableName(CollectionMetadataContext context) {
@ -231,20 +231,42 @@ public class MiddleTableCollectionMetadataGenerator extends AbstractCollectionMe
return getMetadataBuildingContext().getMetadataCollector().getEntityBinding( context.getReferencedEntityName() ); 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). // Only if this is a relation (when there is a referenced entity).
if ( context.getReferencedEntityName() != null ) { 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() ) { if ( context.getCollection().isInverse() ) {
context.getReferencingEntityConfiguration().addToManyMiddleNotOwningRelation( context.getReferencingEntityConfiguration().addToManyMiddleNotOwningRelation(
context.getPropertyName(), context.getPropertyName(),
mappedBy, mappedBy,
context.getReferencedEntityName() context.getReferencedEntityName(),
referencingIdData,
referencedIdData,
auditMiddleEntityName
); );
} }
else { else {
context.getReferencingEntityConfiguration().addToManyMiddleRelation( context.getReferencingEntityConfiguration().addToManyMiddleRelation(
context.getPropertyName(), 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.ExtendedPropertyMapper;
import org.hibernate.envers.internal.entities.mapper.PropertyMapper; import org.hibernate.envers.internal.entities.mapper.PropertyMapper;
import org.hibernate.envers.internal.entities.mapper.id.IdMapper; 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. * Runtime representation of an entity that may or may not be audited.
@ -109,13 +110,21 @@ public class EntityConfiguration {
idMapper, idMapper,
fakeBidirectionalRelationMapper, fakeBidirectionalRelationMapper,
fakeBidirectionalRelationIndexMapper, fakeBidirectionalRelationIndexMapper,
null,
null,
null,
true, true,
indexed indexed
) )
); );
} }
public void addToManyMiddleRelation(String fromPropertyName, String toEntityName) { public void addToManyMiddleRelation(
String fromPropertyName,
String toEntityName,
MiddleIdData referencingIdData,
MiddleIdData referencedIdData,
String auditMiddleEntityName) {
relations.put( relations.put(
fromPropertyName, fromPropertyName,
RelationDescription.toMany( RelationDescription.toMany(
@ -126,13 +135,22 @@ public class EntityConfiguration {
null, null,
null, null,
null, null,
referencingIdData,
referencedIdData,
auditMiddleEntityName,
true, true,
false 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( relations.put(
fromPropertyName, fromPropertyName,
RelationDescription.toMany( RelationDescription.toMany(
@ -143,6 +161,9 @@ public class EntityConfiguration {
null, null,
null, null,
null, null,
referencingIdData,
referencedIdData,
auditMiddleEntityName,
true, true,
false 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.PropertyMapper;
import org.hibernate.envers.internal.entities.mapper.id.IdMapper; 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) * @author Adam Warski (adam at warski dot org)
@ -22,6 +23,9 @@ public class RelationDescription {
private final IdMapper idMapper; private final IdMapper idMapper;
private final PropertyMapper fakeBidirectionalRelationMapper; private final PropertyMapper fakeBidirectionalRelationMapper;
private final PropertyMapper fakeBidirectionalRelationIndexMapper; private final PropertyMapper fakeBidirectionalRelationIndexMapper;
private final MiddleIdData referencingIdData;
private final MiddleIdData referencedIdData;
private final String auditMiddleEntityName;
private final boolean insertable; private final boolean insertable;
private final boolean indexed; private final boolean indexed;
private boolean bidirectional; private boolean bidirectional;
@ -38,7 +42,7 @@ public class RelationDescription {
boolean ignoreNotFound) { boolean ignoreNotFound) {
return new RelationDescription( return new RelationDescription(
fromPropertyName, relationType, toEntityName, mappedByPropertyName, idMapper, 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, IdMapper idMapper,
PropertyMapper fakeBidirectionalRelationMapper, PropertyMapper fakeBidirectionalRelationMapper,
PropertyMapper fakeBidirectionalRelationIndexMapper, PropertyMapper fakeBidirectionalRelationIndexMapper,
MiddleIdData referencingIdData,
MiddleIdData referencedIdData,
String auditMiddleEntityName,
boolean insertable, boolean insertable,
boolean indexed) { boolean indexed) {
// Envers populates collections by executing dedicated queries. Special handling of // Envers populates collections by executing dedicated queries. Special handling of
@ -58,7 +65,7 @@ public class RelationDescription {
// Therefore assigning false to ignoreNotFound. // Therefore assigning false to ignoreNotFound.
return new RelationDescription( return new RelationDescription(
fromPropertyName, relationType, toEntityName, mappedByPropertyName, idMapper, fakeBidirectionalRelationMapper, 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, IdMapper idMapper,
PropertyMapper fakeBidirectionalRelationMapper, PropertyMapper fakeBidirectionalRelationMapper,
PropertyMapper fakeBidirectionalRelationIndexMapper, PropertyMapper fakeBidirectionalRelationIndexMapper,
MiddleIdData referencingIdData,
MiddleIdData referencedIdData,
String auditMiddleEntityName,
boolean insertable, boolean insertable,
boolean ignoreNotFound, boolean ignoreNotFound,
boolean indexed) { boolean indexed) {
@ -81,6 +91,9 @@ public class RelationDescription {
this.idMapper = idMapper; this.idMapper = idMapper;
this.fakeBidirectionalRelationMapper = fakeBidirectionalRelationMapper; this.fakeBidirectionalRelationMapper = fakeBidirectionalRelationMapper;
this.fakeBidirectionalRelationIndexMapper = fakeBidirectionalRelationIndexMapper; this.fakeBidirectionalRelationIndexMapper = fakeBidirectionalRelationIndexMapper;
this.referencingIdData = referencingIdData;
this.referencedIdData = referencedIdData;
this.auditMiddleEntityName = auditMiddleEntityName;
this.insertable = insertable; this.insertable = insertable;
this.indexed = indexed; this.indexed = indexed;
this.bidirectional = false; this.bidirectional = false;
@ -118,6 +131,18 @@ public class RelationDescription {
return fakeBidirectionalRelationIndexMapper; return fakeBidirectionalRelationIndexMapper;
} }
public MiddleIdData getReferencingIdData() {
return referencingIdData;
}
public MiddleIdData getReferencedIdData() {
return referencedIdData;
}
public String getAuditMiddleEntityName() {
return auditMiddleEntityName;
}
public boolean isInsertable() { public boolean isInsertable() {
return insertable; return insertable;
} }

View File

@ -9,6 +9,7 @@ package org.hibernate.envers.query.criteria.internal;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.boot.internal.EnversService;
@ -48,13 +49,20 @@ public abstract class CriteriaTools {
return null; 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; return relationDesc;
} }
throw new AuditException( throw new AuditException(
"This type of relation (" + entityName + "." + propertyName + String.format(
") isn't supported and can't be used in queries." 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; package org.hibernate.envers.query.criteria.internal;
import java.util.Locale;
import org.hibernate.envers.boot.internal.EnversService; 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.RelationDescription;
import org.hibernate.envers.internal.entities.RelationType;
import org.hibernate.envers.internal.reader.AuditReaderImplementor; import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters; import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder; import org.hibernate.envers.internal.tools.query.QueryBuilder;
@ -43,8 +47,18 @@ public class NotNullAuditExpression extends AbstractAtomicExpression {
if ( relatedEntity == null ) { if ( relatedEntity == null ) {
parameters.addNotNullRestriction( alias, propertyName ); parameters.addNotNullRestriction( alias, propertyName );
} }
else { else if ( relatedEntity.getRelationType() == RelationType.TO_ONE ) {
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, null, alias, null, false ); 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; package org.hibernate.envers.query.criteria.internal;
import java.util.Locale;
import org.hibernate.envers.boot.internal.EnversService; 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.RelationDescription;
import org.hibernate.envers.internal.entities.RelationType;
import org.hibernate.envers.internal.reader.AuditReaderImplementor; import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters; import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder; import org.hibernate.envers.internal.tools.query.QueryBuilder;
@ -43,8 +47,18 @@ public class NullAuditExpression extends AbstractAtomicExpression {
if ( relatedEntity == null ) { if ( relatedEntity == null ) {
parameters.addNullRestriction( alias, propertyName ); parameters.addNullRestriction( alias, propertyName );
} }
else { else if ( relatedEntity.getRelationType() == RelationType.TO_ONE ) {
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, null, alias, null, true ); 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; package org.hibernate.envers.query.criteria.internal;
import java.util.Locale;
import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.RelationDescription; 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.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters; import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder; import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.internal.property.PropertyNameGetter; import org.hibernate.envers.query.internal.property.PropertyNameGetter;
/** /**
@ -49,7 +51,16 @@ public class RelatedAuditEqualityExpression extends AbstractAtomicExpression {
RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName ); RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName );
if ( relatedEntity == null ) { if ( relatedEntity == null ) {
throw new AuditException( 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 ); relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, id, alias, null, equals );

View File

@ -7,15 +7,16 @@
package org.hibernate.envers.query.criteria.internal; package org.hibernate.envers.query.criteria.internal;
import java.util.List; import java.util.List;
import java.util.Locale;
import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.RelationDescription; 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.entities.mapper.id.QueryParameterData;
import org.hibernate.envers.internal.reader.AuditReaderImplementor; import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters; import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder; import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.internal.property.PropertyNameGetter; import org.hibernate.envers.query.internal.property.PropertyNameGetter;
/** /**
@ -51,7 +52,16 @@ public class RelatedAuditInExpression extends AbstractAtomicExpression {
RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName ); RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName );
if ( relatedEntity == null ) { if ( relatedEntity == null ) {
throw new AuditException( 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; package org.hibernate.envers.query.criteria.internal;
import java.util.Locale;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.RelationDescription; 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.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters; import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder; import org.hibernate.envers.internal.tools.query.QueryBuilder;
@ -59,7 +62,13 @@ public class SimpleAuditExpression extends AbstractAtomicExpression {
final Type type = getPropertyType( session, entityName, propertyName ); final Type type = getPropertyType( session, entityName, propertyName );
if ( type != null && type.isComponentType() ) { if ( type != null && type.isComponentType() ) {
if ( !"=".equals( op ) && !"<>".equals( op ) ) { 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; final ComponentType componentType = (ComponentType) type;
for ( int i = 0; i < componentType.getPropertyNames().length; i++ ) { for ( int i = 0; i < componentType.getPropertyNames().length; i++ ) {
@ -76,16 +85,30 @@ public class SimpleAuditExpression extends AbstractAtomicExpression {
parameters.addWhereWithParam( alias, propertyName, op, value ); parameters.addWhereWithParam( alias, propertyName, op, value );
} }
} }
else { else if ( relatedEntity.getRelationType() == RelationType.TO_ONE ) {
if ( !"=".equals( op ) && !"<>".equals( op ) ) { if ( !"=".equals( op ) && !"<>".equals( op ) ) {
throw new AuditException( throw new AuditException(
"This type of operation: " + op + " (" + entityName + "." + propertyName + String.format(
") isn't supported and can't be used in queries." 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 ); Object id = relatedEntity.getIdMapper().mapToIdFromEntity( value );
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, id, alias, null, "=".equals( op ) ); 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.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import jakarta.persistence.NoResultException; import jakarta.persistence.NoResultException;
import jakarta.persistence.NonUniqueResultException; import jakarta.persistence.NonUniqueResultException;
@ -18,10 +19,12 @@ import org.hibernate.CacheMode;
import org.hibernate.FlushMode; import org.hibernate.FlushMode;
import org.hibernate.Incubating; import org.hibernate.Incubating;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.boot.internal.EnversService; import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.configuration.Configuration; import org.hibernate.envers.configuration.Configuration;
import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.RelationDescription; 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.id.IdMapper;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData; import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.reader.AuditReaderImplementor; import org.hibernate.envers.internal.reader.AuditReaderImplementor;
@ -47,8 +50,9 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
private final QueryBuilder queryBuilder; private final QueryBuilder queryBuilder;
private final JoinType joinType; private final JoinType joinType;
private final String entityName; private final String entityName;
private final IdMapper ownerAssociationIdMapper; private final RelationDescription relationDescription;
private final String ownerAlias; private final String ownerAlias;
private final String ownerEntityName;
private final String alias; private final String alias;
private final Map<String, String> aliasToEntityNameMap; private final Map<String, String> aliasToEntityNameMap;
private final List<AuditCriterion> criterions = new ArrayList<>(); private final List<AuditCriterion> criterions = new ArrayList<>();
@ -72,17 +76,24 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
this.queryBuilder = queryBuilder; this.queryBuilder = queryBuilder;
this.joinType = joinType; this.joinType = joinType;
String ownerEntityName = aliasToEntityNameMap.get( ownerAlias ); ownerEntityName = aliasToEntityNameMap.get( ownerAlias );
final RelationDescription relationDescription = CriteriaTools.getRelatedEntity( final RelationDescription relationDescription = CriteriaTools.getRelatedEntity(
enversService, enversService,
ownerEntityName, ownerEntityName,
propertyName propertyName
); );
if ( relationDescription == null ) { 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.entityName = relationDescription.getToEntityName();
this.ownerAssociationIdMapper = relationDescription.getIdMapper(); this.relationDescription = relationDescription;
this.ownerAlias = ownerAlias; this.ownerAlias = ownerAlias;
this.alias = userSuppliedAlias == null ? queryBuilder.generateAlias() : userSuppliedAlias; this.alias = userSuppliedAlias == null ? queryBuilder.generateAlias() : userSuppliedAlias;
aliasToEntityNameMap.put( this.alias, entityName ); aliasToEntityNameMap.put( this.alias, entityName );
@ -241,26 +252,154 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
} }
protected void addCriterionsToQuery(AuditReaderImplementor versionsReader) { protected void addCriterionsToQuery(AuditReaderImplementor versionsReader) {
if ( enversService.getEntitiesConfigurations().isVersioned( entityName ) ) { final Configuration configuration = enversService.getConfig();
Configuration configuration = enversService.getConfig(); boolean targetIsAudited = enversService.getEntitiesConfigurations().isVersioned( entityName );
String auditEntityName = configuration.getAuditEntityName( entityName ); String targetEntityName = entityName;
Parameters joinConditionParameters = queryBuilder.addJoin( joinType, auditEntityName, alias, false ); 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 // owner.reference_id = target.originalId.id
String originalIdPropertyName = configuration.getOriginalIdPropertyName(); IdMapper idMapperTarget;
IdMapper idMapperTarget = enversService.getEntitiesConfigurations().get( entityName ).getIdMapper(); String prefix;
final String prefix = alias.concat( "." ).concat( originalIdPropertyName ); if ( targetIsAudited ) {
ownerAssociationIdMapper.addIdsEqualToQuery( idMapperTarget = enversService.getEntitiesConfigurations().get( entityName ).getIdMapper();
prefix = alias.concat( "." ).concat( originalIdPropertyName );
}
else {
idMapperTarget = enversService.getEntitiesConfigurations()
.getNotVersionEntityConfiguration( entityName )
.getIdMapper();
prefix = alias;
}
relationDescription.getIdMapper().addIdsEqualToQuery(
joinConditionParameters, joinConditionParameters,
ownerAlias, ownerAlias,
idMapperTarget, idMapperTarget,
prefix 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 // filter revision of target entity
Parameters parametersToUse = parameters; Parameters parametersToUse = parameters;
String revisionPropertyPath = configuration.getRevisionNumberPath(); if ( joinType == JoinType.LEFT ) {
if (joinType == JoinType.LEFT) {
parametersToUse = parameters.addSubParameters( Parameters.OR ); parametersToUse = parameters.addSubParameters( Parameters.OR );
parametersToUse.addNullRestriction( revisionPropertyPath, true ); parametersToUse.addNullRestriction( revisionPropertyPath, true );
parametersToUse = parametersToUse.addSubParameters( Parameters.AND ); parametersToUse = parametersToUse.addSubParameters( Parameters.AND );
@ -270,7 +409,7 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
enversService.getEntitiesConfigurations().get( entityName ).getIdMappingData(), enversService.getEntitiesConfigurations().get( entityName ).getIdMappingData(),
null, null,
entityName, entityName,
enversService.getEntitiesConfigurations().isVersioned( entityName ) true
); );
enversService.getAuditStrategy().addEntityAtRevisionRestriction( enversService.getAuditStrategy().addEntityAtRevisionRestriction(
configuration, configuration,
@ -287,19 +426,6 @@ public class AuditAssociationQueryImpl<Q extends AuditQueryImplementor>
true 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 ) { for ( AuditCriterion criterion : criterions ) {
criterion.addToQuery( criterion.addToQuery(

View File

@ -16,7 +16,6 @@ import java.util.Set;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.query.AuditEntity; import org.hibernate.envers.query.AuditEntity;
import org.hibernate.orm.test.envers.BaseEnversFunctionalTestCase; import org.hibernate.orm.test.envers.BaseEnversFunctionalTestCase;
import org.hibernate.orm.test.envers.Priority; import org.hibernate.orm.test.envers.Priority;
@ -371,28 +370,6 @@ public class AuditedDynamicComponentsAdvancedCasesTest extends BaseEnversFunctio
assertTyping( IllegalArgumentException.class, e ); 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 @Test

View File

@ -148,7 +148,8 @@ public class SanityCheckTest extends BaseEnversFunctionalTestCase {
catch ( Exception e ) { catch ( Exception e ) {
assertTyping( AuditException.class, e ); assertTyping( AuditException.class, e );
assertEquals( 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() 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;
}
}