HHH-9178 - Fix QueryException when querying audited entities with embeddables.

This commit is contained in:
Chris Cranford 2016-05-11 16:01:02 -05:00
parent 69b8202c75
commit a67b42ca64
4 changed files with 329 additions and 3 deletions

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.envers.query.criteria.internal;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.boot.internal.EnversService;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.RelationDescription;
@ -14,6 +15,9 @@ import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.envers.internal.tools.query.QueryBuilder;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.internal.property.PropertyNameGetter;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.ComponentType;
import org.hibernate.type.Type;
/**
* @author Adam Warski (adam at warski dot org)
@ -46,7 +50,29 @@ public class SimpleAuditExpression implements AuditCriterion {
RelationDescription relatedEntity = CriteriaTools.getRelatedEntity( enversService, entityName, propertyName );
if ( relatedEntity == null ) {
parameters.addWhereWithParam( propertyName, op, value );
// HHH-9178 - Add support to component type equality.
// This basically will allow = and <> operators to perform component-based equality checks.
// Any other operator for a component type will not be supported.
// Non-component types will continue to behave normally.
final SessionImplementor session = versionsReader.getSessionImplementor();
final Type type = getPropertyType( session, entityName, propertyName );
if ( type != null && type.isComponentType() ) {
if ( !"=".equals( op ) && !"<>".equals( op ) ) {
throw new AuditException( "Component-based criterion is not supported for op: " + op );
}
final ComponentType componentType = (ComponentType) type;
for ( int i = 0; i < componentType.getPropertyNames().length; i++ ) {
final Object componentValue = componentType.getPropertyValue( value, i, session );
parameters.addWhereWithParam(
propertyName + "_" + componentType.getPropertyNames()[ i ],
op,
componentValue
);
}
}
else {
parameters.addWhereWithParam( propertyName, op, value );
}
}
else {
if ( !"=".equals( op ) && !"<>".equals( op ) ) {
@ -55,10 +81,28 @@ public class SimpleAuditExpression implements AuditCriterion {
") isn't supported and can't be used in queries."
);
}
Object id = relatedEntity.getIdMapper().mapToIdFromEntity( value );
relatedEntity.getIdMapper().addIdEqualsToQuery( parameters, id, null, "=".equals( op ) );
}
}
/**
* Get the property type of a given property in the specified entity.
*
* @param session the session
* @param entityName the entity name
* @param propertyName the property name
* @return the property type of the property or {@code null} if the property name isn't found.
*/
private Type getPropertyType(SessionImplementor session, String entityName, String propertyName) {
// rather than rely on QueryException from calling getPropertyType(), this allows a non-failure way
// to determine whether to return null or lookup the value safely.
final EntityPersister persister = session.getSessionFactory().getMetamodel().entityPersister( entityName );
for ( String name : persister.getPropertyNames() ) {
if ( name.equals( propertyName ) ) {
return persister.getPropertyType( propertyName );
}
}
return null;
}
}

View File

@ -0,0 +1,107 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.envers.test.integration.query.embeddables;
import java.util.List;
import javax.persistence.EntityManager;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.query.AuditQuery;
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
import org.hibernate.envers.test.Priority;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals;
/**
* Test which supports using {@link AuditEntity} to test equality/inequality
* between embeddable components.
*
* @author Chris Cranford
*/
@TestForIssue(jiraKey = "HHH-9178")
public class EmbeddableQuery extends BaseEnversJPAFunctionalTestCase {
private Integer personId;
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { Person.class, NameInfo.class };
}
@Test
@Priority(10)
public void initData() {
EntityManager em = getOrCreateEntityManager();
try {
// Revision 1
em.getTransaction().begin();
NameInfo ni = new NameInfo( "John", "Doe" );
Person person1 = new Person( "JDOE", ni );
em.persist( person1 );
em.getTransaction().commit();
// Revision 2
em.getTransaction().begin();
person1 = em.find( Person.class, person1.getId() );
person1.getNameInfo().setFirstName( "Jane" );
em.merge( person1 );
em.getTransaction().commit();
// Revision 3
em.getTransaction().begin();
person1 = em.find( Person.class, person1.getId() );
person1.setName( "JDOE2" );
em.merge( person1 );
em.getTransaction().commit();
personId = person1.getId();
}
finally {
em.close();
}
}
@Test
public void testRevisionCounts() {
assertEquals( 3, getAuditReader().getRevisions( Person.class, personId ).size() );
}
@Test
public void testAuditQueryUsingEmbeddableEquals() {
final NameInfo nameInfo = new NameInfo( "John", "Doe" );
final AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision( Person.class, 1 );
query.add( AuditEntity.property( "nameInfo" ).eq( nameInfo ) );
List<?> results = query.getResultList();
assertEquals( 1, results.size() );
final Person person = (Person) results.get( 0 );
assertEquals( nameInfo, person.getNameInfo() );
}
@Test
public void testAuditQueryUsingEmbeddableNotEquals() {
final NameInfo nameInfo = new NameInfo( "Jane", "Doe" );
final AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision( Person.class, 1 );
query.add( AuditEntity.property( "nameInfo" ).ne( nameInfo ) );
assertEquals( 0, query.getResultList().size() );
}
@Test
public void testAuditQueryUsingEmbeddableNonEqualityCheck() {
try {
final NameInfo nameInfo = new NameInfo( "John", "Doe" );
final AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision( Person.class, 1 );
query.add( AuditEntity.property( "nameInfo" ).le( nameInfo ) );
}
catch ( Exception ex ) {
assertTyping( AuditException.class, ex );
}
}
}

View File

@ -0,0 +1,77 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.envers.test.integration.query.embeddables;
import javax.persistence.Embeddable;
/**
* @author Chris Cranford
*/
@Embeddable
public class NameInfo {
private String firstName;
private String lastName;
NameInfo() {
}
public NameInfo(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public int hashCode() {
int result;
result = ( firstName != null ? firstName.hashCode() : 0 );
result = result * 31 + ( lastName != null ? lastName.hashCode() : 0 );
return result;
}
@Override
public boolean equals(Object obj) {
if ( obj == this ) {
return true;
}
if ( !( obj instanceof NameInfo ) ) {
return false;
}
NameInfo that = (NameInfo) obj;
if ( firstName != null ? !firstName.equals( that.firstName) : that.firstName != null ) {
return false;
}
if ( lastName != null ? !lastName.equals( that.lastName) : that.lastName != null ) {
return false;
}
return true;
}
@Override
public String toString() {
return "NameInfo{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
'}';
}
}

View File

@ -0,0 +1,98 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.envers.test.integration.query.embeddables;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.hibernate.envers.Audited;
/**
* @author Chris Cranford
*/
@Entity
@Audited
public class Person {
@Id
@GeneratedValue
private Integer id;
private String name;
private NameInfo nameInfo;
Person() {
}
public Person(String name, NameInfo nameInfo) {
this.name = name;
this.nameInfo = nameInfo;
}
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 NameInfo getNameInfo() {
return nameInfo;
}
public void setNameInfo(NameInfo nameInfo) {
this.nameInfo = nameInfo;
}
@Override
public int hashCode() {
int result;
result = ( id != null ? id.hashCode() : 0 );
result = 31 * result + ( name != null ? name.hashCode() : 0 );
result = 31 * result + ( nameInfo != null ? nameInfo.hashCode() : 0 );
return result;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( !( obj instanceof Person ) ) {
return false;
}
Person that = (Person) obj;
if ( id != null ? !id.equals( that.id ) : that.id != null ) {
return false;
}
if ( name != null ? !name.equals( that.name) : that.name != null ) {
return false;
}
if ( nameInfo != null ? !nameInfo.equals( that.nameInfo ) : that.nameInfo != null ) {
return false;
}
return true;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", nameInfo=" + nameInfo +
'}';
}
}