/* * 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; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.criteria.JoinType; import org.hibernate.envers.AuditReader; import org.hibernate.envers.query.AuditEntity; import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; import org.hibernate.envers.test.Priority; import org.hibernate.envers.test.integration.query.entities.Address; import org.hibernate.envers.test.integration.query.entities.Car; import org.hibernate.envers.test.integration.query.entities.Person; import org.junit.Test; /** * @author Felix Feisst (feisst dot felix at gmail dot com) */ @SuppressWarnings("unchecked") public class AssociationToOneLeftJoinQueryTest extends BaseEnversJPAFunctionalTestCase { private Car car1; private Car car2; private Car car3; private Person person1; private Person person2; private Address address1; @Override protected Class<?>[] getAnnotatedClasses() { return new Class<?>[] { Car.class, Person.class, Address.class }; } @Test @Priority(10) public void initData() { final EntityManager em = getEntityManager(); // revision 1 em.getTransaction().begin(); address1 = new Address("address1", 1); em.persist(address1); Address address2 = new Address("address2", 2); em.persist(address2); person1 = new Person("person1", 30, address1); em.persist(person1); person2 = new Person("person2", 20, null); em.persist(person2); Person person3 = new Person("person3", 10, address1); em.persist(person3); car1 = new Car("car1"); car1.setOwner(person1); em.persist(car1); car2 = new Car("car2"); car2.setOwner(person2); em.persist(car2); car3 = new Car("car3"); em.persist(car3); em.getTransaction().commit(); // revision 2 em.getTransaction().begin(); person2.setAge(21); em.getTransaction().commit(); } @Test public void testLeftJoinOnAuditedEntity() { final AuditReader auditReader = getAuditReader(); // all cars where the owner has an age of 20 or where there is no owner at all List<Car> resultList = auditReader.createQuery() .forEntitiesAtRevision( Car.class, 1 ) .traverseRelation( "owner", JoinType.LEFT, "p" ) .up().add( AuditEntity.or( AuditEntity.property( "p", "age").eq( 20 ), AuditEntity.relatedId( "owner" ).eq( null ) ) ) .addOrder( AuditEntity.property( "make" ).asc() ).getResultList(); assertEquals( "The result list should have 2 results, car1 because its owner has an age of 30 and car3 because it has no owner at all", 2, resultList.size() ); Car car0 = resultList.get(0); Car car1 = resultList.get(1); assertEquals( "Unexpected car at index 0", car2.getId(), car0.getId() ); assertEquals( "Unexpected car at index 0", car3.getId(), car1.getId() ); } /** * In a first attempt to implement left joins in Envers, a full join * has been performed and than the entities has been filtered in the * where clause. However, this approach did only work for inner joins * but not for left joins. One of the defects in this approach is, * that audit entities, which have a null 'relatedId' are and do not * match the query criterias, still joined to other entities which matched * match the query criterias. * This test ensures that this defect is no longer in the current implementation. */ @Test public void testEntitiesWithANullRelatedIdAreNotJoinedToOtherEntities() { final AuditReader auditReader = getAuditReader(); List<Car> resultList = auditReader.createQuery() .forEntitiesAtRevision( Car.class, 1 ) .traverseRelation( "owner", JoinType.LEFT, "p" ) .up().add( AuditEntity.and( AuditEntity.property( "make" ).eq( "car3" ), AuditEntity.property( "p", "age" ).eq( 30 ) ) ) .getResultList(); assertTrue( "Expected no cars to be returned, because car3 does not have an owner", resultList.isEmpty() ); } /** * In a first attempt to implement left joins in Envers, a full join * has been performed and than the entities has been filtered in the * where clause. However, this approach did only work for inner joins * but not for left joins. One of the defects in this approach is, * that audit entities, which have a null 'relatedId' and do match * the query criterias, have been returned multiple times by a query. * This test ensures that this defect is no longer in the current implementation. */ @Test public void testEntitiesWithANullRelatedIdAreNotReturnedMoreThanOnce() { final AuditReader auditReader = getAuditReader(); List<Car> resultList = auditReader.createQuery() .forEntitiesAtRevision( Car.class, 1 ) .traverseRelation( "owner", JoinType.LEFT, "p" ) .up().add( AuditEntity.or( AuditEntity.property( "make" ).eq( "car3" ), AuditEntity.property( "p", "age" ).eq( 10 ) ) ) .getResultList(); assertEquals( "Expected car3 to be returned but only once", 1, resultList.size() ); assertEquals( "Unexpected car at index 0", car3.getId(), resultList.get(0).getId() ); } }