HHH-12992 - Fix proper support for order-by annotation on audited entity associations.

This commit is contained in:
Chris Cranford 2018-10-04 11:13:18 -04:00
parent 334e064272
commit eff27db90a
13 changed files with 498 additions and 329 deletions

View File

@ -64,6 +64,7 @@ import org.hibernate.envers.internal.tools.MappingTools;
import org.hibernate.envers.internal.tools.ReflectionTools;
import org.hibernate.envers.internal.tools.StringTools;
import org.hibernate.envers.internal.tools.Tools;
import org.hibernate.mapping.Bag;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.IndexedCollection;
@ -237,7 +238,8 @@ public final class CollectionMetadataGenerator {
referencedIdData,
isEmbeddableElementType(),
mappedBy,
isMappedByKey( propertyValue, mappedBy )
isMappedByKey( propertyValue, mappedBy ),
propertyValue.getOrderBy()
);
// Creating common mapper data.
@ -290,7 +292,15 @@ public final class CollectionMetadataGenerator {
// Checking if there's an index defined. If so, adding a mapper for it.
if ( positionMappedBy != null ) {
final Type indexType = ( (IndexedCollection) propertyValue ).getIndex().getType();
final Type indexType;
if ( IndexedCollection.class.isInstance( propertyValue ) ) {
indexType = ( (IndexedCollection) propertyValue ).getIndex().getType();
}
else {
// todo - do we need to reverse lookup the type anyway?
indexType = null;
}
fakeBidirectionalRelationIndexMapper = new SinglePropertyMapper(
PropertyData.forProperty( positionMappedBy, indexType )
);
@ -451,7 +461,10 @@ public final class CollectionMetadataGenerator {
mainGenerator.getAuditStrategy(),
referencingIdData,
auditMiddleEntityName,
isRevisionTypeInId()
isRevisionTypeInId(),
propertyValue.getOrderBy() == null
? propertyValue.getManyToManyOrdering()
: propertyValue.getOrderBy()
);
// Adding the XML mapping for the referencing entity, if the relation isn't inverse.

View File

@ -36,6 +36,7 @@ public final class QueryGeneratorBuilder {
private final String auditMiddleEntityName;
private final List<MiddleIdData> idDatas;
private final boolean revisionTypeInId;
private final String orderBy;
QueryGeneratorBuilder(
GlobalConfiguration globalCfg,
@ -43,12 +44,14 @@ public final class QueryGeneratorBuilder {
AuditStrategy auditStrategy,
MiddleIdData referencingIdData,
String auditMiddleEntityName,
boolean revisionTypeInId) {
boolean revisionTypeInId,
String orderBy) {
this.globalCfg = globalCfg;
this.verEntCfg = verEntCfg;
this.auditStrategy = auditStrategy;
this.referencingIdData = referencingIdData;
this.auditMiddleEntityName = auditMiddleEntityName;
this.orderBy = orderBy;
this.revisionTypeInId = revisionTypeInId;
idDatas = new ArrayList<>();
@ -61,7 +64,7 @@ public final class QueryGeneratorBuilder {
RelationQueryGenerator build(MiddleComponentData... componentDatas) {
if ( idDatas.size() == 0 ) {
return new OneEntityQueryGenerator(
verEntCfg, auditStrategy, auditMiddleEntityName, referencingIdData,
globalCfg, verEntCfg, auditStrategy, auditMiddleEntityName, referencingIdData,
revisionTypeInId, componentDatas
);
}
@ -69,13 +72,13 @@ public final class QueryGeneratorBuilder {
if ( idDatas.get( 0 ).isAudited() ) {
return new TwoEntityQueryGenerator(
globalCfg, verEntCfg, auditStrategy, auditMiddleEntityName, referencingIdData,
idDatas.get( 0 ), revisionTypeInId, componentDatas
idDatas.get( 0 ), revisionTypeInId, orderBy, componentDatas
);
}
else {
return new TwoEntityOneAuditedQueryGenerator(
verEntCfg, auditStrategy, auditMiddleEntityName, referencingIdData,
idDatas.get( 0 ), revisionTypeInId, componentDatas
globalCfg, verEntCfg, auditStrategy, auditMiddleEntityName, referencingIdData,
idDatas.get( 0 ), revisionTypeInId, orderBy, componentDatas
);
}
}
@ -86,10 +89,9 @@ public final class QueryGeneratorBuilder {
"Ternary relations using @Audited(targetAuditMode = NOT_AUDITED) are not supported."
);
}
return new ThreeEntityQueryGenerator(
globalCfg, verEntCfg, auditStrategy, auditMiddleEntityName, referencingIdData,
idDatas.get( 0 ), idDatas.get( 1 ), revisionTypeInId, componentDatas
idDatas.get( 0 ), idDatas.get( 1 ), revisionTypeInId, orderBy, componentDatas
);
}
else {

View File

@ -8,6 +8,7 @@ package org.hibernate.envers.internal.entities.mapper.relation.lazy.initializor;
import java.util.List;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.internal.entities.EntityInstantiator;
import org.hibernate.envers.internal.entities.mapper.relation.query.RelationQueryGenerator;
@ -46,7 +47,8 @@ public abstract class AbstractCollectionInitializor<T> implements Initializor<T>
@Override
public T initialize() {
final List<?> collectionContent = queryGenerator.getQuery( versionsReader, primaryKey, revision, removed ).list();
final SharedSessionContractImplementor session = versionsReader.getSessionImplementor();
final List<?> collectionContent = queryGenerator.getQuery( session, primaryKey, revision, removed ).list();
final T collection = initializeCollection( collectionContent.size() );

View File

@ -9,12 +9,17 @@ package org.hibernate.envers.internal.entities.mapper.relation.query;
import java.util.Collections;
import java.util.Map;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
import org.hibernate.envers.internal.entities.mapper.id.QueryParameterData;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.strategy.AuditStrategy;
import org.hibernate.query.Query;
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.DEL_REVISION_TYPE_PARAMETER;
@ -27,57 +32,127 @@ import static org.hibernate.envers.internal.entities.mapper.relation.query.Query
* @author Chris Cranford
*/
public abstract class AbstractRelationQueryGenerator implements RelationQueryGenerator {
protected final GlobalConfiguration globalCfg;
protected final AuditEntitiesConfiguration verEntCfg;
protected final AuditStrategy auditStrategy;
protected final MiddleIdData referencingIdData;
protected final boolean revisionTypeInId;
protected final String entityName;
protected final String orderBy;
private String queryString;
private String queryRemovedString;
protected AbstractRelationQueryGenerator(
GlobalConfiguration globalCfg,
AuditEntitiesConfiguration verEntCfg,
AuditStrategy auditStrategy,
String entityName,
MiddleIdData referencingIdData,
boolean revisionTypeInId) {
boolean revisionTypeInId,
String orderBy) {
this.globalCfg = globalCfg;
this.verEntCfg = verEntCfg;
this.entityName = entityName;
this.auditStrategy = auditStrategy;
this.referencingIdData = referencingIdData;
this.revisionTypeInId = revisionTypeInId;
this.orderBy = orderBy;
}
/**
* @return Query used to retrieve state of audited entity valid at a given revision.
*/
protected abstract String getQueryString();
/**
* @return Query executed to retrieve state of audited entity valid at previous revision
* or removed during exactly specified revision number. Used only when traversing deleted
* entities graph.
*/
protected abstract String getQueryRemovedString();
@Override
public Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision, boolean removed) {
final Query query = versionsReader.getSession().createQuery( removed ? getQueryRemovedString() : getQueryString() );
public Query getQuery(SharedSessionContractImplementor session, Object primaryKey, Number revision, boolean removed) {
final String queryString = getQueryString( session.getFactory(), removed );
final Query query = session.createQuery( queryString );
query.setParameter( DEL_REVISION_TYPE_PARAMETER, RevisionType.DEL );
query.setParameter( REVISION_PARAMETER, revision );
for ( QueryParameterData paramData : referencingIdData.getPrefixedMapper().mapToQueryParametersFromId(
primaryKey
) ) {
final IdMapper prefixIdMapper = referencingIdData.getPrefixedMapper();
for ( QueryParameterData paramData : prefixIdMapper.mapToQueryParametersFromId( primaryKey ) ) {
paramData.setParameterValue( query );
}
return query;
}
protected String queryToString(QueryBuilder query) {
/**
* Build the common aspects of a {@link QueryBuilder} used by both query and query-remove strings.
*
* @param sessionFactory The session factory.
* @return The constructed query builder instance.
*/
protected abstract QueryBuilder buildQueryBuilderCommon(SessionFactoryImplementor sessionFactory);
/**
* Apply predicates used to fetch actual data.
*
* @param qb The query builder instance to apply predicates against.
* @param parameters The root query parameters
* @param inclusive Whether its inclusive or not.
*/
protected abstract void applyValidPredicates(QueryBuilder qb, Parameters parameters, boolean inclusive);
/**
* Apply predicates to fetch data and deletions that took place during the same revision.
*
* @param qb The query builder instance to apply predicates against.
*/
protected abstract void applyValidAndRemovePredicates(QueryBuilder qb);
protected String getRevisionTypePath() {
if ( revisionTypeInId ) {
return verEntCfg.getOriginalIdPropName() + "." + verEntCfg.getRevisionTypePropName();
}
return verEntCfg.getRevisionTypePropName();
}
/**
* Get the query to be used.
*
* If {@code removed} is specified as {@code true}, the removal query will be returned.
* If {@code removed} is specified as {@code false}, the non-removal query will be returned.
*
* This method internally will cache the built queries so that subsequent calls will not
* require the rebuilding of the queries.
*
* @param sessionFactory The session factory.
* @param removed Whether to return the removal query or non-removal query.
* @return The query string to be used.
*/
private String getQueryString(SessionFactoryImplementor sessionFactory, boolean removed) {
if ( removed ) {
if ( queryRemovedString == null ) {
queryRemovedString = buildQueryRemoveString( sessionFactory );
}
return queryRemovedString;
}
if ( queryString == null ) {
queryString = buildQueryString( sessionFactory );
}
return queryString;
}
private String buildQueryString(SessionFactoryImplementor sessionFactory) {
final QueryBuilder builder = buildQueryBuilderCommon( sessionFactory );
applyValidPredicates( builder, builder.getRootParameters(), true );
return queryToString( builder );
}
private String buildQueryRemoveString(SessionFactoryImplementor sessionFactory) {
final QueryBuilder builder = buildQueryBuilderCommon( sessionFactory );
applyValidAndRemovePredicates( builder );
return queryToString( builder );
}
private String queryToString(QueryBuilder query) {
return queryToString( query, Collections.emptyMap() );
}
protected String queryToString(QueryBuilder query, Map<String, Object> queryParamValues) {
private String queryToString(QueryBuilder query, Map<String, Object> queryParamValues) {
final StringBuilder sb = new StringBuilder();
query.build( sb, queryParamValues );
return sb.toString();
}
protected String getRevisionTypePath() {
return revisionTypeInId
? verEntCfg.getOriginalIdPropName() + "." + verEntCfg.getRevisionTypePropName()
: verEntCfg.getRevisionTypePropName();
}
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.envers.internal.entities.mapper.relation.query;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
import org.hibernate.envers.internal.entities.mapper.id.AbstractCompositeIdMapper;
@ -14,6 +15,7 @@ import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.strategy.AuditStrategy;
import org.hibernate.internal.util.StringHelper;
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.DEL_REVISION_TYPE_PARAMETER;
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.REFERENCED_ENTITY_ALIAS;
@ -28,10 +30,9 @@ import static org.hibernate.envers.internal.entities.mapper.relation.query.Query
* @author Chris Cranford
*/
public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGenerator {
private final String queryString;
private final String queryRemovedString;
private final String mappedBy;
private final boolean multipleIdMapperKey;
private final MiddleIdData referencedIdData;
public OneAuditEntityQueryGenerator(
GlobalConfiguration globalCfg,
@ -42,10 +43,20 @@ public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGen
MiddleIdData referencedIdData,
boolean revisionTypeInId,
String mappedBy,
boolean mappedByKey) {
super( verEntCfg, referencingIdData, revisionTypeInId );
boolean mappedByKey,
String orderBy) {
super(
globalCfg,
verEntCfg,
auditStrategy,
verEntCfg.getAuditEntityName( referencedEntityName ),
referencingIdData,
revisionTypeInId,
orderBy
);
this.mappedBy = mappedBy;
this.referencedIdData = referencedIdData;
// HHH-11770 We use AbstractCompositeIdMapper here to handle EmbeddedIdMapper and MultipleIdMappper support
// so that OneAuditEntityQueryGenerator supports mappings to both @IdClass and @EmbeddedId components.
@ -74,24 +85,12 @@ public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGen
* (only non-deleted entities)
* e.revision_type != DEL
*/
final QueryBuilder commonPart = commonQueryPart( verEntCfg.getAuditEntityName( referencedEntityName ) );
final QueryBuilder validQuery = commonPart.deepCopy();
final QueryBuilder removedQuery = commonPart.deepCopy();
createValidDataRestrictions(
globalCfg, auditStrategy, referencedIdData, validQuery, validQuery.getRootParameters()
);
createValidAndRemovedDataRestrictions( globalCfg, auditStrategy, referencedIdData, removedQuery );
queryString = queryToString( validQuery );
queryRemovedString = queryToString( removedQuery );
}
/**
* Compute common part for both queries.
*/
private QueryBuilder commonQueryPart(String versionsReferencedEntityName) {
@Override
protected QueryBuilder buildQueryBuilderCommon(SessionFactoryImplementor sessionFactory) {
// SELECT e FROM versionsEntity e
final QueryBuilder qb = new QueryBuilder( versionsReferencedEntityName, REFERENCED_ENTITY_ALIAS );
final QueryBuilder qb = new QueryBuilder( entityName, REFERENCED_ENTITY_ALIAS, sessionFactory );
qb.addProjection( null, REFERENCED_ENTITY_ALIAS, null, false );
// WHERE
if ( multipleIdMapperKey ) {
@ -105,57 +104,53 @@ public final class OneAuditEntityQueryGenerator extends AbstractRelationQueryGen
// e.id_ref_ed = :id_ref_ed
referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( qb.getRootParameters(), null, true );
}
// ORDER BY
if ( !StringHelper.isEmpty( orderBy ) ) {
qb.addOrderFragment( REFERENCED_ENTITY_ALIAS, orderBy );
}
return qb;
}
/**
* Creates query restrictions used to retrieve only actual data.
*/
private void createValidDataRestrictions(
GlobalConfiguration globalCfg, AuditStrategy auditStrategy,
MiddleIdData referencedIdData, QueryBuilder qb, Parameters rootParameters) {
@Override
protected void applyValidPredicates(QueryBuilder qb, Parameters rootParameters, boolean inclusive) {
final String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
// (selecting e entities at revision :revision)
// --> based on auditStrategy (see above)
auditStrategy.addEntityAtRevisionRestriction(
globalCfg, qb, rootParameters, revisionPropertyPath,
verEntCfg.getRevisionEndFieldName(), true, referencedIdData, revisionPropertyPath,
verEntCfg.getOriginalIdPropName(), REFERENCED_ENTITY_ALIAS, REFERENCED_ENTITY_ALIAS_DEF_AUD_STR,
globalCfg,
qb,
rootParameters,
revisionPropertyPath,
verEntCfg.getRevisionEndFieldName(),
true,
referencedIdData,
revisionPropertyPath,
verEntCfg.getOriginalIdPropName(),
REFERENCED_ENTITY_ALIAS,
REFERENCED_ENTITY_ALIAS_DEF_AUD_STR,
true
);
// e.revision_type != DEL
rootParameters.addWhereWithNamedParam( getRevisionTypePath(), false, "!=", DEL_REVISION_TYPE_PARAMETER );
}
/**
* Create query restrictions used to retrieve actual data and deletions that took place at exactly given revision.
*/
private void createValidAndRemovedDataRestrictions(
GlobalConfiguration globalCfg, AuditStrategy auditStrategy,
MiddleIdData referencedIdData, QueryBuilder remQb) {
@Override
protected void applyValidAndRemovePredicates(QueryBuilder remQb) {
final Parameters disjoint = remQb.getRootParameters().addSubParameters( "or" );
// Restrictions to match all valid rows.
final Parameters valid = disjoint.addSubParameters( "and" );
// Restrictions to match all rows deleted at exactly given revision.
final Parameters removed = disjoint.addSubParameters( "and" );
// Excluding current revision, because we need to match data valid at the previous one.
createValidDataRestrictions( globalCfg, auditStrategy, referencedIdData, remQb, valid );
applyValidPredicates( remQb, valid, false );
// e.revision = :revision
removed.addWhereWithNamedParam( verEntCfg.getRevisionNumberPath(), false, "=", REVISION_PARAMETER );
// e.revision_type = DEL
removed.addWhereWithNamedParam( getRevisionTypePath(), false, "=", DEL_REVISION_TYPE_PARAMETER );
}
@Override
protected String getQueryString() {
return queryString;
}
@Override
protected String getQueryRemovedString() {
return queryRemovedString;
}
private IdMapper getMultipleIdPrefixedMapper() {
final String prefix = verEntCfg.getOriginalIdPropName() + "." + mappedBy + ".";
return referencingIdData.getOriginalMapper().prefixMappedProperties( prefix );

View File

@ -6,7 +6,9 @@
*/
package org.hibernate.envers.internal.entities.mapper.relation.query;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleComponentData;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.tools.query.Parameters;
@ -22,16 +24,30 @@ import static org.hibernate.envers.internal.entities.mapper.relation.query.Query
*
* @author Adam Warski (adam at warski dot org)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
* @author Chris Cranford
*/
public final class OneEntityQueryGenerator extends AbstractRelationQueryGenerator {
private final String queryString;
private final String queryRemovedString;
private final MiddleComponentData[] componentDatas;
public OneEntityQueryGenerator(
AuditEntitiesConfiguration verEntCfg, AuditStrategy auditStrategy,
String versionsMiddleEntityName, MiddleIdData referencingIdData,
boolean revisionTypeInId, MiddleComponentData... componentData) {
super( verEntCfg, referencingIdData, revisionTypeInId );
GlobalConfiguration globalCfg,
AuditEntitiesConfiguration verEntCfg,
AuditStrategy auditStrategy,
String versionsMiddleEntityName,
MiddleIdData referencingIdData,
boolean revisionTypeInId,
MiddleComponentData... componentData) {
super(
globalCfg,
verEntCfg,
auditStrategy,
versionsMiddleEntityName,
referencingIdData,
revisionTypeInId,
null
);
this.componentDatas = componentData;
/*
* The valid query that we need to create:
@ -52,24 +68,12 @@ public final class OneEntityQueryGenerator extends AbstractRelationQueryGenerato
* (only non-deleted entities and associations)
* ee.revision_type != DEL
*/
final QueryBuilder commonPart = commonQueryPart( versionsMiddleEntityName );
final QueryBuilder validQuery = commonPart.deepCopy();
final QueryBuilder removedQuery = commonPart.deepCopy();
createValidDataRestrictions(
auditStrategy, versionsMiddleEntityName, validQuery, validQuery.getRootParameters(), true, componentData
);
createValidAndRemovedDataRestrictions( auditStrategy, versionsMiddleEntityName, removedQuery, componentData );
queryString = queryToString( validQuery );
queryRemovedString = queryToString( removedQuery );
}
/**
* Compute common part for both queries.
*/
private QueryBuilder commonQueryPart(String versionsMiddleEntityName) {
@Override
protected QueryBuilder buildQueryBuilderCommon(SessionFactoryImplementor sessionFactory) {
// SELECT ee FROM middleEntity ee
final QueryBuilder qb = new QueryBuilder( versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS );
final QueryBuilder qb = new QueryBuilder( entityName, MIDDLE_ENTITY_ALIAS, sessionFactory );
qb.addProjection( null, MIDDLE_ENTITY_ALIAS, null, false );
// WHERE
// ee.originalId.id_ref_ing = :id_ref_ing
@ -78,56 +82,55 @@ public final class OneEntityQueryGenerator extends AbstractRelationQueryGenerato
verEntCfg.getOriginalIdPropName(),
true
);
// NOTE:
// No `orderBy` fragment is specified because this generator is used for
// embeddables and enumerations where either a Set-based container will
// force the SETORDINAL property to give us a unique primary key tuple
// or an @IndexColumn/@OrderColumn must be specified that takes priority
// over an @OrderBy fragment.
return qb;
}
/**
* Creates query restrictions used to retrieve only actual data.
*/
private void createValidDataRestrictions(
AuditStrategy auditStrategy, String versionsMiddleEntityName,
QueryBuilder qb, Parameters rootParameters, boolean inclusive,
MiddleComponentData... componentData) {
@Override
protected void applyValidPredicates(QueryBuilder qb, Parameters rootParameters, boolean inclusive) {
final String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
final String originalIdPropertyName = verEntCfg.getOriginalIdPropName();
final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName;
// (with ee association at revision :revision)
// --> based on auditStrategy (see above)
auditStrategy.addAssociationAtRevisionRestriction(
qb, rootParameters, revisionPropertyPath, verEntCfg.getRevisionEndFieldName(), true,
referencingIdData, versionsMiddleEntityName, eeOriginalIdPropertyPath, revisionPropertyPath,
originalIdPropertyName, MIDDLE_ENTITY_ALIAS, inclusive, componentData
qb,
rootParameters,
revisionPropertyPath,
verEntCfg.getRevisionEndFieldName(),
true,
referencingIdData,
entityName,
eeOriginalIdPropertyPath,
revisionPropertyPath,
originalIdPropertyName,
MIDDLE_ENTITY_ALIAS,
inclusive,
componentDatas
);
// ee.revision_type != DEL
rootParameters.addWhereWithNamedParam( getRevisionTypePath(), "!=", DEL_REVISION_TYPE_PARAMETER );
}
/**
* Create query restrictions used to retrieve actual data and deletions that took place at exactly given revision.
*/
private void createValidAndRemovedDataRestrictions(
AuditStrategy auditStrategy, String versionsMiddleEntityName,
QueryBuilder remQb, MiddleComponentData... componentData) {
@Override
protected void applyValidAndRemovePredicates(QueryBuilder remQb) {
final Parameters disjoint = remQb.getRootParameters().addSubParameters( "or" );
// Restrictions to match all valid rows.
final Parameters valid = disjoint.addSubParameters( "and" );
// Restrictions to match all rows deleted at exactly given revision.
final Parameters removed = disjoint.addSubParameters( "and" );
// Excluding current revision, because we need to match data valid at the previous one.
createValidDataRestrictions( auditStrategy, versionsMiddleEntityName, remQb, valid, false, componentData );
applyValidPredicates( remQb, valid, false );
// ee.revision = :revision
removed.addWhereWithNamedParam( verEntCfg.getRevisionNumberPath(), "=", REVISION_PARAMETER );
// ee.revision_type = DEL
removed.addWhereWithNamedParam( getRevisionTypePath(), "=", DEL_REVISION_TYPE_PARAMETER );
}
@Override
protected String getQueryString() {
return queryString;
}
@Override
protected String getQueryRemovedString() {
return queryRemovedString;
}
}

View File

@ -6,18 +6,26 @@
*/
package org.hibernate.envers.internal.entities.mapper.relation.query;
import org.hibernate.envers.internal.reader.AuditReaderImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.query.Query;
/**
* TODO: cleanup implementations and extract common code
* <p/>
* Implementations of this interface provide a method to generate queries on a relation table (a table used
* for mapping relations). The query can select, apart from selecting the content of the relation table, also data of
* other "related" entities.
* Implementations of this interface provide a method to generate queries on a
* relation table (a table used for mapping relations). The query can select,
* apart from selecting the content of the relation table, also data of other
* "related" entities.
*
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/
public interface RelationQueryGenerator {
Query getQuery(AuditReaderImplementor versionsReader, Object primaryKey, Number revision, boolean removed);
/**
* Return the query to fetch the relation.
*
* @param session The session.
* @param primaryKey The primary key of the owning object.
* @param revision The revision to be fetched.
* @param removed Whether to return a query that includes the removed audit rows.
*/
Query getQuery(SharedSessionContractImplementor session, Object primaryKey, Number revision, boolean removed);
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.envers.internal.entities.mapper.relation.query;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleComponentData;
@ -13,6 +14,7 @@ import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.strategy.AuditStrategy;
import org.hibernate.internal.util.StringHelper;
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.DEL_REVISION_TYPE_PARAMETER;
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.INDEX_ENTITY_ALIAS;
@ -27,18 +29,37 @@ import static org.hibernate.envers.internal.entities.mapper.relation.query.Query
*
* @author Adam Warski (adam at warski dot org)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
* @author Chris Cranford
*/
public final class ThreeEntityQueryGenerator extends AbstractRelationQueryGenerator {
private final String queryString;
private final String queryRemovedString;
private final MiddleIdData referencedIdData;
private final MiddleIdData indexIdData;
private final MiddleComponentData[] componentDatas;
public ThreeEntityQueryGenerator(
GlobalConfiguration globalCfg, AuditEntitiesConfiguration verEntCfg,
AuditStrategy auditStrategy, String versionsMiddleEntityName,
MiddleIdData referencingIdData, MiddleIdData referencedIdData,
MiddleIdData indexIdData, boolean revisionTypeInId,
GlobalConfiguration globalCfg,
AuditEntitiesConfiguration verEntCfg,
AuditStrategy auditStrategy,
String versionsMiddleEntityName,
MiddleIdData referencingIdData,
MiddleIdData referencedIdData,
MiddleIdData indexIdData,
boolean revisionTypeInId,
String orderBy,
MiddleComponentData... componentData) {
super( verEntCfg, referencingIdData, revisionTypeInId );
super(
globalCfg,
verEntCfg,
auditStrategy,
versionsMiddleEntityName,
referencingIdData,
revisionTypeInId,
orderBy
);
this.referencedIdData = referencedIdData;
this.indexIdData = indexIdData;
this.componentDatas = componentData;
/*
* The valid query that we need to create:
@ -86,35 +107,14 @@ public final class ThreeEntityQueryGenerator extends AbstractRelationQueryGenera
* e.revision_type != DEL AND
* f.revision_type != DEL
*/
final QueryBuilder commonPart = commonQueryPart(
referencedIdData,
indexIdData,
versionsMiddleEntityName,
verEntCfg.getOriginalIdPropName()
);
final QueryBuilder validQuery = commonPart.deepCopy();
final QueryBuilder removedQuery = commonPart.deepCopy();
createValidDataRestrictions(
globalCfg, auditStrategy, referencedIdData, versionsMiddleEntityName, validQuery,
validQuery.getRootParameters(), true, indexIdData, componentData
);
createValidAndRemovedDataRestrictions(
globalCfg, auditStrategy, referencedIdData, versionsMiddleEntityName, removedQuery, indexIdData, componentData
);
queryString = queryToString( validQuery );
queryRemovedString = queryToString( removedQuery );
}
/**
* Compute common part for both queries.
*/
private QueryBuilder commonQueryPart(
MiddleIdData referencedIdData, MiddleIdData indexIdData,
String versionsMiddleEntityName, String originalIdPropertyName) {
@Override
protected QueryBuilder buildQueryBuilderCommon(SessionFactoryImplementor sessionFactory) {
final String originalIdPropertyName = verEntCfg.getOriginalIdPropName();
final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName;
// SELECT new list(ee) FROM middleEntity ee
final QueryBuilder qb = new QueryBuilder( versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS );
final QueryBuilder qb = new QueryBuilder( entityName, MIDDLE_ENTITY_ALIAS, sessionFactory );
qb.addFrom( referencedIdData.getAuditEntityName(), REFERENCED_ENTITY_ALIAS, false );
qb.addFrom( indexIdData.getAuditEntityName(), INDEX_ENTITY_ALIAS, false );
qb.addProjection(
@ -135,16 +135,18 @@ public final class ThreeEntityQueryGenerator extends AbstractRelationQueryGenera
);
// ee.originalId.id_ref_ing = :id_ref_ing
referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( rootParameters, originalIdPropertyName, true );
// ORDER BY
// Hibernate applies @OrderBy on map elements, not the key.
// So here we apply it to the referenced entity, not the actual index entity that represents the key.
if ( !StringHelper.isEmpty( orderBy ) ) {
qb.addOrderFragment( REFERENCED_ENTITY_ALIAS, orderBy );
}
return qb;
}
/**
* Creates query restrictions used to retrieve only actual data.
*/
private void createValidDataRestrictions(
GlobalConfiguration globalCfg, AuditStrategy auditStrategy,
MiddleIdData referencedIdData, String versionsMiddleEntityName, QueryBuilder qb,
Parameters rootParameters, boolean inclusive, MiddleIdData indexIdData, MiddleComponentData... componentData) {
@Override
protected void applyValidPredicates(QueryBuilder qb, Parameters rootParameters, boolean inclusive) {
final String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
final String originalIdPropertyName = verEntCfg.getOriginalIdPropName();
final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName;
@ -184,9 +186,19 @@ public final class ThreeEntityQueryGenerator extends AbstractRelationQueryGenera
// (with ee association at revision :revision)
// --> based on auditStrategy (see above)
auditStrategy.addAssociationAtRevisionRestriction(
qb, rootParameters, revisionPropertyPath, verEntCfg.getRevisionEndFieldName(), true,
referencingIdData, versionsMiddleEntityName, eeOriginalIdPropertyPath, revisionPropertyPath,
originalIdPropertyName, MIDDLE_ENTITY_ALIAS, inclusive, componentData
qb,
rootParameters,
revisionPropertyPath,
verEntCfg.getRevisionEndFieldName(),
true,
referencingIdData,
entityName,
eeOriginalIdPropertyPath,
revisionPropertyPath,
originalIdPropertyName,
MIDDLE_ENTITY_ALIAS,
inclusive,
componentDatas
);
// ee.revision_type != DEL
rootParameters.addWhereWithNamedParam( revisionTypePropName, "!=", DEL_REVISION_TYPE_PARAMETER );
@ -206,13 +218,8 @@ public final class ThreeEntityQueryGenerator extends AbstractRelationQueryGenera
);
}
/**
* Create query restrictions used to retrieve actual data and deletions that took place at exactly given revision.
*/
private void createValidAndRemovedDataRestrictions(
GlobalConfiguration globalCfg, AuditStrategy auditStrategy,
MiddleIdData referencedIdData, String versionsMiddleEntityName,
QueryBuilder remQb, MiddleIdData indexIdData, MiddleComponentData... componentData) {
@Override
protected void applyValidAndRemovePredicates(QueryBuilder remQb) {
final Parameters disjoint = remQb.getRootParameters().addSubParameters( "or" );
// Restrictions to match all valid rows.
final Parameters valid = disjoint.addSubParameters( "and" );
@ -221,9 +228,7 @@ public final class ThreeEntityQueryGenerator extends AbstractRelationQueryGenera
final String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
final String revisionTypePropName = getRevisionTypePath();
// Excluding current revision, because we need to match data valid at the previous one.
createValidDataRestrictions(
globalCfg, auditStrategy, referencedIdData, versionsMiddleEntityName, remQb, valid, false, indexIdData, componentData
);
applyValidPredicates( remQb, valid, false );
// ee.revision = :revision
removed.addWhereWithNamedParam( revisionPropertyPath, "=", REVISION_PARAMETER );
// e.revision = :revision
@ -257,14 +262,4 @@ public final class ThreeEntityQueryGenerator extends AbstractRelationQueryGenera
DEL_REVISION_TYPE_PARAMETER
);
}
@Override
protected String getQueryString() {
return queryString;
}
@Override
protected String getQueryRemovedString() {
return queryRemovedString;
}
}

View File

@ -6,12 +6,15 @@
*/
package org.hibernate.envers.internal.entities.mapper.relation.query;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleComponentData;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.strategy.AuditStrategy;
import org.hibernate.internal.util.StringHelper;
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.DEL_REVISION_TYPE_PARAMETER;
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS;
@ -23,17 +26,34 @@ import static org.hibernate.envers.internal.entities.mapper.relation.query.Query
*
* @author Adam Warski (adam at warski dot org)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
* @author Chris Cranford
*/
public final class TwoEntityOneAuditedQueryGenerator extends AbstractRelationQueryGenerator {
private final String queryString;
private final String queryRemovedString;
private final MiddleIdData referencedIdData;
private final MiddleComponentData[] componentDatas;
public TwoEntityOneAuditedQueryGenerator(
AuditEntitiesConfiguration verEntCfg, AuditStrategy auditStrategy,
String versionsMiddleEntityName, MiddleIdData referencingIdData,
MiddleIdData referencedIdData, boolean revisionTypeInId,
GlobalConfiguration globalCfg,
AuditEntitiesConfiguration verEntCfg,
AuditStrategy auditStrategy,
String versionsMiddleEntityName,
MiddleIdData referencingIdData,
MiddleIdData referencedIdData,
boolean revisionTypeInId,
String orderBy,
MiddleComponentData... componentData) {
super( verEntCfg, referencingIdData, revisionTypeInId );
super(
globalCfg,
verEntCfg,
auditStrategy,
versionsMiddleEntityName,
referencingIdData,
revisionTypeInId,
orderBy
);
this.referencedIdData = referencedIdData;
this.componentDatas = componentData;
/*
* The valid query that we need to create:
@ -57,31 +77,14 @@ public final class TwoEntityOneAuditedQueryGenerator extends AbstractRelationQue
* (only non-deleted entities and associations)
* ee.revision_type != DEL
*/
final QueryBuilder commonPart = commonQueryPart(
referencedIdData,
versionsMiddleEntityName,
verEntCfg.getOriginalIdPropName()
);
final QueryBuilder validQuery = commonPart.deepCopy();
final QueryBuilder removedQuery = commonPart.deepCopy();
createValidDataRestrictions(
auditStrategy, versionsMiddleEntityName, validQuery, validQuery.getRootParameters(), componentData
);
createValidAndRemovedDataRestrictions( auditStrategy, versionsMiddleEntityName, removedQuery, componentData );
queryString = queryToString( validQuery );
queryRemovedString = queryToString( removedQuery );
}
/**
* Compute common part for both queries.
*/
private QueryBuilder commonQueryPart(
MiddleIdData referencedIdData, String versionsMiddleEntityName,
String originalIdPropertyName) {
@Override
protected QueryBuilder buildQueryBuilderCommon(SessionFactoryImplementor sessionFactory) {
final String originalIdPropertyName = verEntCfg.getOriginalIdPropName();
final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName;
// SELECT new list(ee) FROM middleEntity ee
final QueryBuilder qb = new QueryBuilder( versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS );
final QueryBuilder qb = new QueryBuilder( entityName, MIDDLE_ENTITY_ALIAS, sessionFactory );
qb.addFrom( referencedIdData.getEntityName(), REFERENCED_ENTITY_ALIAS, false );
qb.addProjection( "new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS, null, false );
// WHERE
@ -92,54 +95,53 @@ public final class TwoEntityOneAuditedQueryGenerator extends AbstractRelationQue
);
// ee.originalId.id_ref_ing = :id_ref_ing
referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( rootParameters, originalIdPropertyName, true );
// ORDER BY
if ( !StringHelper.isEmpty( orderBy ) ) {
qb.addOrderFragment( REFERENCED_ENTITY_ALIAS, orderBy );
}
return qb;
}
/**
* Creates query restrictions used to retrieve only actual data.
*/
private void createValidDataRestrictions(
AuditStrategy auditStrategy, String versionsMiddleEntityName, QueryBuilder qb,
Parameters rootParameters, MiddleComponentData... componentData) {
@Override
protected void applyValidPredicates(QueryBuilder qb, Parameters rootParameters, boolean inclusive) {
final String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
final String originalIdPropertyName = verEntCfg.getOriginalIdPropName();
final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName;
// (with ee association at revision :revision)
// --> based on auditStrategy (see above)
auditStrategy.addAssociationAtRevisionRestriction(
qb, rootParameters, revisionPropertyPath, verEntCfg.getRevisionEndFieldName(), true,
referencingIdData, versionsMiddleEntityName, eeOriginalIdPropertyPath, revisionPropertyPath,
originalIdPropertyName, MIDDLE_ENTITY_ALIAS, true, componentData
qb,
rootParameters,
revisionPropertyPath,
verEntCfg.getRevisionEndFieldName(),
true,
referencingIdData,
entityName,
eeOriginalIdPropertyPath,
revisionPropertyPath,
originalIdPropertyName,
MIDDLE_ENTITY_ALIAS,
true,
componentDatas
);
// ee.revision_type != DEL
rootParameters.addWhereWithNamedParam( getRevisionTypePath(), "!=", DEL_REVISION_TYPE_PARAMETER );
}
/**
* Create query restrictions used to retrieve actual data and deletions that took place at exactly given revision.
*/
private void createValidAndRemovedDataRestrictions(
AuditStrategy auditStrategy, String versionsMiddleEntityName,
QueryBuilder remQb, MiddleComponentData... componentData) {
@Override
protected void applyValidAndRemovePredicates(QueryBuilder remQb) {
final Parameters disjoint = remQb.getRootParameters().addSubParameters( "or" );
// Restrictions to match all valid rows.
final Parameters valid = disjoint.addSubParameters( "and" );
// Restrictions to match all rows deleted at exactly given revision.
final Parameters removed = disjoint.addSubParameters( "and" );
createValidDataRestrictions( auditStrategy, versionsMiddleEntityName, remQb, valid, componentData );
applyValidPredicates( remQb, valid, false );
// ee.revision = :revision
removed.addWhereWithNamedParam( verEntCfg.getRevisionNumberPath(), "=", REVISION_PARAMETER );
// ee.revision_type = DEL
removed.addWhereWithNamedParam( getRevisionTypePath(), "=", DEL_REVISION_TYPE_PARAMETER );
}
@Override
protected String getQueryString() {
return queryString;
}
@Override
protected String getQueryRemovedString() {
return queryRemovedString;
}
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.envers.internal.entities.mapper.relation.query;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
import org.hibernate.envers.internal.entities.mapper.relation.MiddleComponentData;
@ -13,6 +15,7 @@ import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.strategy.AuditStrategy;
import org.hibernate.internal.util.StringHelper;
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.DEL_REVISION_TYPE_PARAMETER;
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS;
@ -25,17 +28,34 @@ import static org.hibernate.envers.internal.entities.mapper.relation.query.Query
*
* @author Adam Warski (adam at warski dot org)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
* @author Chris Cranford
*/
public final class TwoEntityQueryGenerator extends AbstractRelationQueryGenerator {
private final String queryString;
private final String queryRemovedString;
private final MiddleIdData referencedIdData;
private final MiddleComponentData[] componentDatas;
public TwoEntityQueryGenerator(
GlobalConfiguration globalCfg, AuditEntitiesConfiguration verEntCfg,
AuditStrategy auditStrategy, String versionsMiddleEntityName,
MiddleIdData referencingIdData, MiddleIdData referencedIdData,
boolean revisionTypeInId, MiddleComponentData... componentData) {
super( verEntCfg, referencingIdData, revisionTypeInId );
GlobalConfiguration globalCfg,
AuditEntitiesConfiguration verEntCfg,
AuditStrategy auditStrategy,
String versionsMiddleEntityName,
MiddleIdData referencingIdData,
MiddleIdData referencedIdData,
boolean revisionTypeInId,
String orderBy,
MiddleComponentData... componentData) {
super(
globalCfg,
verEntCfg,
auditStrategy,
versionsMiddleEntityName,
referencingIdData,
revisionTypeInId,
orderBy
);
this.referencedIdData = referencedIdData;
this.componentDatas = componentData;
/*
* The valid query that we need to create:
@ -68,34 +88,14 @@ public final class TwoEntityQueryGenerator extends AbstractRelationQueryGenerato
* ee.revision_type != DEL AND
* e.revision_type != DEL
*/
final QueryBuilder commonPart = commonQueryPart(
referencedIdData,
versionsMiddleEntityName,
verEntCfg.getOriginalIdPropName()
);
final QueryBuilder validQuery = commonPart.deepCopy();
final QueryBuilder removedQuery = commonPart.deepCopy();
createValidDataRestrictions(
globalCfg, auditStrategy, referencedIdData, versionsMiddleEntityName, validQuery,
validQuery.getRootParameters(), true, componentData
);
createValidAndRemovedDataRestrictions(
globalCfg, auditStrategy, referencedIdData, versionsMiddleEntityName, removedQuery, componentData
);
queryString = queryToString( validQuery );
queryRemovedString = queryToString( removedQuery );
}
/**
* Compute common part for both queries.
*/
private QueryBuilder commonQueryPart(
MiddleIdData referencedIdData, String versionsMiddleEntityName,
String originalIdPropertyName) {
@Override
protected QueryBuilder buildQueryBuilderCommon(SessionFactoryImplementor sessionFactory) {
final String originalIdPropertyName = verEntCfg.getOriginalIdPropName();
final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName;
// SELECT new list(ee) FROM middleEntity ee
QueryBuilder qb = new QueryBuilder( versionsMiddleEntityName, MIDDLE_ENTITY_ALIAS );
QueryBuilder qb = new QueryBuilder( entityName, MIDDLE_ENTITY_ALIAS, sessionFactory );
qb.addFrom( referencedIdData.getAuditEntityName(), REFERENCED_ENTITY_ALIAS, false );
qb.addProjection( "new list", MIDDLE_ENTITY_ALIAS + ", " + REFERENCED_ENTITY_ALIAS, null, false );
// WHERE
@ -107,16 +107,17 @@ public final class TwoEntityQueryGenerator extends AbstractRelationQueryGenerato
);
// ee.originalId.id_ref_ing = :id_ref_ing
referencingIdData.getPrefixedMapper().addNamedIdEqualsToQuery( rootParameters, originalIdPropertyName, true );
// ORDER BY
if ( !StringHelper.isEmpty( orderBy ) ) {
qb.addOrderFragment( REFERENCED_ENTITY_ALIAS, orderBy );
}
return qb;
}
/**
* Creates query restrictions used to retrieve only actual data.
*/
private void createValidDataRestrictions(
GlobalConfiguration globalCfg, AuditStrategy auditStrategy, MiddleIdData referencedIdData,
String versionsMiddleEntityName, QueryBuilder qb, Parameters rootParameters,
boolean inclusive, MiddleComponentData... componentData) {
@Override
protected void applyValidPredicates(QueryBuilder qb, Parameters rootParameters, boolean inclusive) {
final String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
final String originalIdPropertyName = verEntCfg.getOriginalIdPropName();
final String eeOriginalIdPropertyPath = MIDDLE_ENTITY_ALIAS + "." + originalIdPropertyName;
@ -140,10 +141,19 @@ public final class TwoEntityQueryGenerator extends AbstractRelationQueryGenerato
// (with ee association at revision :revision)
// --> based on auditStrategy (see above)
auditStrategy.addAssociationAtRevisionRestriction(
qb, rootParameters, revisionPropertyPath,
verEntCfg.getRevisionEndFieldName(), true, referencingIdData, versionsMiddleEntityName,
eeOriginalIdPropertyPath, revisionPropertyPath, originalIdPropertyName, MIDDLE_ENTITY_ALIAS,
inclusive, componentData
qb,
rootParameters,
revisionPropertyPath,
verEntCfg.getRevisionEndFieldName(),
true,
referencingIdData,
entityName,
eeOriginalIdPropertyPath,
revisionPropertyPath,
originalIdPropertyName,
MIDDLE_ENTITY_ALIAS,
inclusive,
componentDatas
);
// ee.revision_type != DEL
rootParameters.addWhereWithNamedParam( revisionTypePropName, "!=", DEL_REVISION_TYPE_PARAMETER );
@ -156,13 +166,8 @@ public final class TwoEntityQueryGenerator extends AbstractRelationQueryGenerato
);
}
/**
* Create query restrictions used to retrieve actual data and deletions that took place at exactly given revision.
*/
private void createValidAndRemovedDataRestrictions(
GlobalConfiguration globalCfg, AuditStrategy auditStrategy,
MiddleIdData referencedIdData, String versionsMiddleEntityName,
QueryBuilder remQb, MiddleComponentData... componentData) {
@Override
protected void applyValidAndRemovePredicates(QueryBuilder remQb) {
final Parameters disjoint = remQb.getRootParameters().addSubParameters( "or" );
// Restrictions to match all valid rows.
final Parameters valid = disjoint.addSubParameters( "and" );
@ -171,15 +176,10 @@ public final class TwoEntityQueryGenerator extends AbstractRelationQueryGenerato
final String revisionPropertyPath = verEntCfg.getRevisionNumberPath();
final String revisionTypePropName = getRevisionTypePath();
// Excluding current revision, because we need to match data valid at the previous one.
createValidDataRestrictions(
globalCfg,
auditStrategy,
referencedIdData,
versionsMiddleEntityName,
applyValidPredicates(
remQb,
valid,
false,
componentData
false
);
// ee.revision = :revision
removed.addWhereWithNamedParam( revisionPropertyPath, "=", REVISION_PARAMETER );
@ -200,14 +200,4 @@ public final class TwoEntityQueryGenerator extends AbstractRelationQueryGenerato
DEL_REVISION_TYPE_PARAMETER
);
}
@Override
protected String getQueryString() {
return queryString;
}
@Override
protected String getQueryRemovedString() {
return queryRemovedString;
}
}

View File

@ -8,19 +8,33 @@ package org.hibernate.envers.internal.tools.query;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.persistence.criteria.JoinType;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.internal.entities.RevisionTypeType;
import org.hibernate.envers.internal.tools.MutableInteger;
import org.hibernate.envers.internal.tools.StringTools;
import org.hibernate.envers.internal.tools.Triple;
import org.hibernate.envers.tools.Pair;
import org.hibernate.query.Query;
import org.hibernate.sql.Template;
import org.hibernate.sql.ordering.antlr.ColumnMapper;
import org.hibernate.sql.ordering.antlr.ColumnReference;
import org.hibernate.sql.ordering.antlr.FormulaReference;
import org.hibernate.sql.ordering.antlr.OrderByAliasResolver;
import org.hibernate.sql.ordering.antlr.OrderByTranslation;
import org.hibernate.sql.ordering.antlr.SqlValueReference;
import org.hibernate.type.CustomType;
/**
@ -59,19 +73,30 @@ public class QueryBuilder {
*/
private final List<String> projections;
private final List<Pair<String, String>> orderFragments;
private final SessionFactoryImplementor sessionFactory;
/**
* @param entityName Main entity which should be selected.
* @param alias Alias of the entity
* @param sessionFactory Session factory
*/
public QueryBuilder(String entityName, String alias) {
this( entityName, alias, new MutableInteger(), new MutableInteger() );
public QueryBuilder(String entityName, String alias, SessionFactoryImplementor sessionFactory) {
this( entityName, alias, new MutableInteger(), new MutableInteger(), sessionFactory );
}
private QueryBuilder(String entityName, String alias, MutableInteger aliasCounter, MutableInteger paramCounter) {
private QueryBuilder(
String entityName,
String alias,
MutableInteger aliasCounter,
MutableInteger paramCounter,
SessionFactoryImplementor sessionFactory) {
this.entityName = entityName;
this.alias = alias;
this.aliasCounter = aliasCounter;
this.paramCounter = paramCounter;
this.sessionFactory = sessionFactory;
final Parameters rootParameters = new Parameters( alias, "and", paramCounter );
parameters.add( rootParameters );
@ -79,6 +104,7 @@ public class QueryBuilder {
froms = new ArrayList<>();
orders = new ArrayList<>();
projections = new ArrayList<>();
orderFragments = new ArrayList<>();
addFrom( entityName, alias, true );
}
@ -87,6 +113,7 @@ public class QueryBuilder {
private QueryBuilder(QueryBuilder other) {
this.entityName = other.entityName;
this.alias = other.alias;
this.sessionFactory = other.sessionFactory;
this.aliasCounter = other.aliasCounter.deepCopy();
this.paramCounter = other.paramCounter.deepCopy();
for (final Parameters params : other.parameters) {
@ -96,6 +123,7 @@ public class QueryBuilder {
froms = new ArrayList<>( other.froms );
orders = new ArrayList<>( other.orders );
projections = new ArrayList<>( other.projections );
orderFragments = new ArrayList<>( other.orderFragments );
}
public QueryBuilder deepCopy() {
@ -123,7 +151,13 @@ public class QueryBuilder {
public Parameters addJoin(JoinType joinType, String entityName, String alias, boolean select) {
Parameters joinConditionParameters = new Parameters( alias, Parameters.AND, paramCounter );
InnerOuterJoinParameter joinParameter = new InnerOuterJoinParameter( joinType, entityName, alias, select, joinConditionParameters );
InnerOuterJoinParameter joinParameter = new InnerOuterJoinParameter(
joinType,
entityName,
alias,
select,
joinConditionParameters
);
froms.add( joinParameter );
return joinConditionParameters;
}
@ -140,7 +174,7 @@ public class QueryBuilder {
* be later used as a value of a parameter.
*/
public QueryBuilder newSubQueryBuilder(String entityName, String alias) {
return new QueryBuilder( entityName, alias, aliasCounter, paramCounter );
return new QueryBuilder( entityName, alias, aliasCounter, paramCounter, sessionFactory );
}
public Parameters getRootParameters() {
@ -157,6 +191,10 @@ public class QueryBuilder {
orders.add( Triple.make( alias, propertyName, ascending ) );
}
public void addOrderFragment(String alias, String fragment) {
orderFragments.add( Pair.make( alias, fragment ) );
}
public void addProjection(String function, String alias, String propertyName, boolean distinct) {
final String effectivePropertyName = propertyName == null ? "" : ".".concat( propertyName );
if ( function == null ) {
@ -209,10 +247,47 @@ public class QueryBuilder {
}
}
// orders
if ( orders.size() > 0 ) {
if ( !orders.isEmpty() ) {
sb.append( " order by " );
StringTools.append( sb, getOrderList().iterator(), ", " );
}
else if ( !orderFragments.isEmpty() ) {
sb.append( " order by " );
final Iterator<Pair<String, String>> fragmentIterator = orderFragments.iterator();
while( fragmentIterator.hasNext() ) {
final Pair<String, String> fragment = fragmentIterator.next();
final OrderByTranslation orderByFragmentTranslation = Template.translateOrderBy(
fragment.getSecond(),
new ColumnMapper() {
@Override
public SqlValueReference[] map(String reference) throws HibernateException {
return new SqlValueReference[ 0 ];
}
},
sessionFactory,
sessionFactory.getJdbcServices().getDialect(),
sessionFactory.getSqlFunctionRegistry()
);
sb.append( orderByFragmentTranslation.injectAliases( new QueryOrderByAliasResolver( fragment.getFirst() ) ) );
if ( fragmentIterator.hasNext() ) {
sb.append( ", " );
}
}
}
}
private class QueryOrderByAliasResolver implements OrderByAliasResolver {
private String alias;
public QueryOrderByAliasResolver(String alias) {
this.alias = alias;
}
@Override
public String resolveTableAlias(String columnReference) {
return alias;
}
}
private List<String> getSelectAliasList() {
@ -294,8 +369,8 @@ public class QueryBuilder {
}
@Override
public void appendJoin(final boolean firstFromElement, final StringBuilder builder, final Map<String, Object> queryParamValues) {
if (!firstFromElement) {
public void appendJoin(boolean firstFromElement, StringBuilder builder, Map<String, Object> queryParamValues) {
if ( !firstFromElement ) {
builder.append( ", " );
}
builder.append( entityName ).append( ' ' ).append( getAlias() );
@ -309,7 +384,12 @@ public class QueryBuilder {
private final String entityName;
private final Parameters joinConditionParameters;
public InnerOuterJoinParameter(JoinType joinType, String entityName, String alias, boolean select, Parameters joinConditionParameters) {
public InnerOuterJoinParameter(
JoinType joinType,
String entityName,
String alias,
boolean select,
Parameters joinConditionParameters) {
super(alias, select);
this.joinType = joinType;
this.entityName = entityName;

View File

@ -86,7 +86,7 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
}
aliasToEntityNameMap.put( REFERENCED_ENTITY_ALIAS, entityName );
qb = new QueryBuilder( versionsEntityName, REFERENCED_ENTITY_ALIAS );
qb = new QueryBuilder( versionsEntityName, REFERENCED_ENTITY_ALIAS, versionsReader.getSessionImplementor().getFactory() );
}
@Override
@ -95,7 +95,7 @@ public abstract class AbstractAuditQuery implements AuditQueryImplementor {
}
protected Query buildQuery() {
Query query = qb.toQuery( versionsReader.getSession() );
Query query = qb.toQuery( versionsReader.getSessionImplementor() );
setQueryProperties( query );
return query;
}

View File

@ -17,8 +17,8 @@ import java.util.Map;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.action.spi.BeforeTransactionCompletionProcess;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.configuration.internal.AuditEntitiesConfiguration;
import org.hibernate.envers.configuration.internal.GlobalConfiguration;
@ -241,7 +241,11 @@ public class ValidityAuditStrategy implements AuditStrategy {
String propertyName,
AuditEntitiesConfiguration auditEntitiesConfiguration,
PersistentCollectionChangeData persistentCollectionChangeData, Object revision) {
final QueryBuilder qb = new QueryBuilder( persistentCollectionChangeData.getEntityName(), MIDDLE_ENTITY_ALIAS );
final QueryBuilder qb = new QueryBuilder(
persistentCollectionChangeData.getEntityName(),
MIDDLE_ENTITY_ALIAS,
( (SharedSessionContractImplementor) session ).getFactory()
);
final String originalIdPropName = auditEntitiesConfiguration.getOriginalIdPropName();
final Map<String, Object> originalId = (Map<String, Object>) persistentCollectionChangeData.getData().get(