diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/EntitiesModifiedAtRevisionQuery.java b/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/EntitiesModifiedAtRevisionQuery.java index b29e774d02..64d7446cb8 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/EntitiesModifiedAtRevisionQuery.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/query/internal/impl/EntitiesModifiedAtRevisionQuery.java @@ -6,6 +6,7 @@ */ package org.hibernate.envers.query.internal.impl; +import java.util.Collection; import java.util.List; import org.hibernate.envers.boot.internal.EnversService; @@ -15,6 +16,8 @@ import org.hibernate.envers.internal.reader.AuditReaderImplementor; import org.hibernate.envers.query.criteria.AuditCriterion; import org.hibernate.query.Query; +import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.REVISION_PARAMETER; + /** * In comparison to {@link EntitiesAtRevisionQuery} this query returns an empty collection if an entity * of a certain type has not been changed in a given revision. @@ -74,6 +77,11 @@ public class EntitiesModifiedAtRevisionQuery extends AbstractAuditQuery { } Query query = buildQuery(); + // add named parameter (used for ValidityAuditStrategy and association queries) + Collection params = query.getParameterMetadata().getNamedParameterNames(); + if ( params.contains( REVISION_PARAMETER ) ) { + query.setParameter( REVISION_PARAMETER, revision ); + } List queryResult = query.list(); return applyProjections( queryResult, revision ); } diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/AssociationEntitiesModifiedQueryTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/AssociationEntitiesModifiedQueryTest.java new file mode 100644 index 0000000000..0487682e3a --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/query/AssociationEntitiesModifiedQueryTest.java @@ -0,0 +1,178 @@ +/* + * 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 . + */ +package org.hibernate.envers.test.integration.query; + +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.criteria.JoinType; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.query.AuditEntity; +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-11981") +public class AssociationEntitiesModifiedQueryTest extends BaseEnversJPAFunctionalTestCase { + @Entity(name = "TemplateType") + @Audited(withModifiedFlag = true) + public static class TemplateType { + @Id + private Integer id; + private String name; + + TemplateType() { + this( null, null ); + } + + TemplateType(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Template") + @Audited(withModifiedFlag = true) + public static class Template { + @Id + private Integer id; + private String name; + @ManyToOne + private TemplateType templateType; + + Template() { + this( null, null, null ); + } + + Template(Integer id, String name, TemplateType type) { + this.id = id; + this.name = name; + this.templateType = type; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public TemplateType getTemplateType() { + return templateType; + } + + public void setTemplateType(TemplateType templateType) { + this.templateType = templateType; + } + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { TemplateType.class, Template.class }; + } + + @Test + @Priority(10) + public void initData() { + // Revision 1 + doInJPA( this::entityManagerFactory, entityManager -> { + final TemplateType type1 = new TemplateType( 1, "Type1" ); + final TemplateType type2 = new TemplateType( 2, "Type2" ); + final Template template = new Template( 1, "Template1", type1 ); + entityManager.persist( type1 ); + entityManager.persist( type2 ); + entityManager.persist( template ); + } ); + + // Revision 2 + doInJPA( this::entityManagerFactory, entityManager -> { + final TemplateType type = entityManager.find( TemplateType.class, 2 ); + final Template template = entityManager.find( Template.class, 1 ); + template.setTemplateType( type ); + entityManager.merge( template ); + } ); + + // Revision 3 + doInJPA( this::entityManagerFactory, entityManager -> { + final Template template = entityManager.find( Template.class, 1 ); + entityManager.remove( template ); + } ); + } + + @Test + public void testEntitiesModifiedAtRevision1WithAssociationQueries() { + doInJPA( this::entityManagerFactory, entityManager -> { + List results = getEntitiesModifiedAtRevisionUsingAssociationQueryResults( 1 ); + assertEquals( 1, results.size() ); + assertEquals( "Type1", ( (TemplateType) results.get( 0 ) ).getName() ); + } ); + } + + @Test + public void testEntitiesModifiedAtRevision2WithAssociationQueries() { + doInJPA( this::entityManagerFactory, entityManager -> { + List results = getEntitiesModifiedAtRevisionUsingAssociationQueryResults( 2 ); + assertEquals( 1, results.size() ); + assertEquals( "Type2", ( (TemplateType) results.get( 0 ) ).getName() ); + } ); + } + + @Test + public void testEntitiesModifiedAtRevision3WithAssociationQueries() { + doInJPA( this::entityManagerFactory, entityManager -> { + List results = getEntitiesModifiedAtRevisionUsingAssociationQueryResults( 3 ); + assertEquals( 0, results.size() ); + } ); + } + + private List getEntitiesModifiedAtRevisionUsingAssociationQueryResults(Number revision) { + // Without fix HHH-11981, throw org.hibernate.QueryException - Parameter not bound : revision + return getAuditReader().createQuery() + .forEntitiesModifiedAtRevision( Template.class, revision ) + .traverseRelation( "templateType", JoinType.INNER ) + .addProjection( AuditEntity.selectEntity( false ) ) + .up() + .add( AuditEntity.property( "templateType" ).hasChanged() ) + .getResultList(); + } +}