/* * 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.test.criteria; import java.util.Iterator; import java.util.List; import java.util.Map; import org.junit.Test; import org.hibernate.Criteria; import org.hibernate.FetchMode; import org.hibernate.Session; import org.hibernate.criterion.CriteriaSpecification; import org.hibernate.criterion.Restrictions; import org.hibernate.sql.JoinFragment; import org.hibernate.sql.JoinType; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * @author Mattias Jiderhamn * @author Gail Badner */ public class OuterJoinCriteriaTest extends BaseCoreFunctionalTestCase { private Order order1; private Order order2; private Order order3; @Override public String[] getMappings() { return new String[] { "criteria/Order.hbm.xml" }; } @Test public void testSubcriteriaWithNonNullRestrictions() { Session s = openSession(); s.getTransaction().begin(); Criteria rootCriteria = s.createCriteria( Order.class ); Criteria subCriteria = rootCriteria.createCriteria( "orderLines", JoinFragment.LEFT_OUTER_JOIN ); assertNotSame( rootCriteria, subCriteria ); // add restrictions to subCriteria, ensuring we stay on subCriteria assertSame( subCriteria, subCriteria.add( Restrictions.eq( "articleId", "3000" ) ) ); List orders = rootCriteria.list(); // order1 and order3 should be returned because each has articleId == "3000" // both should have their full collection assertEquals( 2, orders.size() ); for ( Iterator it = orders.iterator(); it.hasNext(); ) { Order o = (Order) it.next(); if ( order1.getOrderId() == o.getOrderId() ) { assertEquals( order1.getLines().size(), o.getLines().size() ); } else if ( order3.getOrderId() == o.getOrderId() ) { assertEquals( order3.getLines().size(), o.getLines().size() ); } else { fail( "unknown order" ); } } s.getTransaction().commit(); s.close(); } @Test public void testSubcriteriaWithNonNullRestrictionsAliasToEntityMap() { Session s = openSession(); s.getTransaction().begin(); Criteria rootCriteria = s.createCriteria( Order.class, "o" ); Criteria subCriteria = rootCriteria.createCriteria( "orderLines", "ol", JoinFragment.LEFT_OUTER_JOIN ); assertNotSame( rootCriteria, subCriteria ); // add restriction to subCriteria, ensuring we stay on subCriteria assertSame( subCriteria, subCriteria.add( Restrictions.eq( "articleId", "3000" ) ) ); List orders = rootCriteria.setResultTransformer( Criteria.ALIAS_TO_ENTITY_MAP ).list(); // order1 and order3 should be returned because each has articleId == "3000"; // the orders should both should have their full collection; assertEquals( 2, orders.size() ); for ( Iterator it = orders.iterator(); it.hasNext(); ) { Map map = (Map) it.next(); Order o = ( Order ) map.get( "o" ); // the orderLine returned from the map should have articleId = "3000" OrderLine ol = ( OrderLine ) map.get( "ol" ); if ( order1.getOrderId() == o.getOrderId() ) { assertEquals( order1.getLines().size(), o.getLines().size() ); assertEquals( "3000", ol.getArticleId() ); } else if ( order3.getOrderId() == o.getOrderId() ) { assertEquals( order3.getLines().size(), o.getLines().size() ); assertEquals( "3000", ol.getArticleId() ); } else { fail( "unknown order" ); } } s.getTransaction().commit(); s.close(); } @Test public void testSubcriteriaWithNullOrNonNullRestrictions() { Session s = openSession(); s.getTransaction().begin(); Criteria rootCriteria = s.createCriteria( Order.class ); Criteria subCriteria = rootCriteria.createCriteria( "orderLines", JoinFragment.LEFT_OUTER_JOIN ); assertNotSame( rootCriteria, subCriteria ); // add restrictions to subCriteria, ensuring we stay on subCriteria // add restriction to subCriteria, ensuring we stay on subCriteria assertSame( subCriteria, subCriteria.add( Restrictions.or( Restrictions.isNull( "articleId" ), // Allow null Restrictions.eq( "articleId", "1000" ) ) ) ); List orders = rootCriteria.list(); // order1 should be returned because it has an orderline with articleId == "1000"; // order2 should be returned because it has no orderlines assertEquals( 2, orders.size() ); for ( Iterator it = orders.iterator(); it.hasNext(); ) { Order o = ( Order ) it.next(); if ( order1.getOrderId() == o.getOrderId() ) { // o.getLines() should contain all of its orderLines assertEquals( order1.getLines().size(), o.getLines().size() ); } else if ( order2.getOrderId() == o.getOrderId() ) { assertEquals( order2.getLines() , o.getLines() ); assertTrue( o.getLines().isEmpty() ); } else { fail( "unknown order" ); } } s.getTransaction().commit(); s.close(); } @Test public void testSubcriteriaWithNullOrNonNullRestrictionsAliasToEntityMap() { Session s = openSession(); s.getTransaction().begin(); Criteria rootCriteria = s.createCriteria( Order.class, "o" ); Criteria subCriteria = rootCriteria.createCriteria( "orderLines", "ol", JoinFragment.LEFT_OUTER_JOIN ); assertNotSame( rootCriteria, subCriteria ); // add restriction to subCriteria, ensuring we stay on subCriteria assertSame( subCriteria, subCriteria.add( Restrictions.or( Restrictions.isNull( "ol.articleId" ), // Allow null Restrictions.eq( "ol.articleId", "1000" ) ) ) ); List orders = rootCriteria.setResultTransformer( Criteria.ALIAS_TO_ENTITY_MAP ).list(); // order1 should be returned because it has an orderline with articleId == "1000"; // order2 should be returned because it has no orderlines assertEquals( 2, orders.size() ); for ( Iterator it = orders.iterator(); it.hasNext(); ) { Map map = (Map) it.next(); Order o = ( Order ) map.get( "o" ); // the orderLine returned from the map should either be null or have articleId = "1000" OrderLine ol = ( OrderLine ) map.get( "ol" ); if ( order1.getOrderId() == o.getOrderId() ) { // o.getLines() should contain all of its orderLines assertEquals( order1.getLines().size(), o.getLines().size() ); assertNotNull( ol ); assertEquals( "1000", ol.getArticleId() ); } else if ( order2.getOrderId() == o.getOrderId() ) { assertEquals( order2.getLines() , o.getLines() ); assertTrue( o.getLines().isEmpty() ); assertNull( ol ); } else { fail( "unknown order" ); } } s.getTransaction().commit(); s.close(); } @Test public void testSubcriteriaWithClauseAliasToEntityMap() { Session s = openSession(); s.getTransaction().begin(); Criteria rootCriteria = s.createCriteria( Order.class, "o" ); Criteria subCriteria = rootCriteria.createCriteria( "orderLines", "ol", JoinFragment.LEFT_OUTER_JOIN, Restrictions.or( Restrictions.isNull( "ol.articleId" ), // Allow null Restrictions.eq( "ol.articleId", "1000" ) ) ); assertNotSame( rootCriteria, subCriteria ); List orders = rootCriteria.setResultTransformer( Criteria.ALIAS_TO_ENTITY_MAP ).list(); // all orders should be returned (via map.get( "o" )) with their full collections; assertEquals( 3, orders.size() ); for ( Iterator it = orders.iterator(); it.hasNext(); ) { Map map = ( Map ) it.next(); Order o = ( Order ) map.get( "o" ); // the orderLine returned from the map should either be null or have articleId = "1000" OrderLine ol = ( OrderLine ) map.get( "ol" ); if ( order1.getOrderId() == o.getOrderId() ) { // o.getLines() should contain all of its orderLines assertEquals( order1.getLines().size(), o.getLines().size() ); assertNotNull( ol ); assertEquals( "1000", ol.getArticleId() ); } else if ( order2.getOrderId() == o.getOrderId() ) { assertTrue( o.getLines().isEmpty() ); assertNull( ol ); } else if ( order3.getOrderId() == o.getOrderId() ) { assertEquals( order3.getLines().size(), o.getLines().size() ); assertNull( ol); } else { fail( "unknown order" ); } } s.getTransaction().commit(); s.close(); } @Test public void testAliasWithNonNullRestrictions() { Session s = openSession(); s.getTransaction().begin(); Criteria rootCriteria = s.createCriteria( Order.class ); // create alias, ensuring we stay on the root criteria assertSame( rootCriteria, rootCriteria.createAlias( "orderLines", "ol", JoinFragment.LEFT_OUTER_JOIN ) ); // add restrictions to rootCriteria assertSame( rootCriteria, rootCriteria.add( Restrictions.eq( "ol.articleId", "3000" ) ) ); List orders = rootCriteria.list(); // order1 and order3 should be returned because each has articleId == "3000" // the contained collections should only have the orderLine with articleId == "3000" assertEquals( 2, orders.size() ); for ( Iterator it = orders.iterator(); it.hasNext(); ) { Order o = (Order) it.next(); if ( order1.getOrderId() == o.getOrderId() ) { assertEquals( 1, o.getLines().size() ); assertEquals( "3000", o.getLines().iterator().next().getArticleId() ); } else if ( order3.getOrderId() == o.getOrderId() ) { assertEquals( 1, o.getLines().size() ); assertEquals( "3000", o.getLines().iterator().next().getArticleId() ); } else { fail( "unknown order" ); } } s.getTransaction().commit(); s.close(); } @Test public void testAliasWithNullOrNonNullRestrictions() { Session s = openSession(); s.getTransaction().begin(); Criteria rootCriteria = s.createCriteria( Order.class ); // create alias, ensuring we stay on the root criteria assertSame( rootCriteria, rootCriteria.createAlias( "orderLines", "ol", JoinFragment.LEFT_OUTER_JOIN ) ); // add restrictions to rootCriteria assertSame( rootCriteria, rootCriteria.add( Restrictions.or( Restrictions.isNull( "ol.articleId" ), // Allow null Restrictions.eq( "ol.articleId", "1000" ) ) ) ); List orders = rootCriteria.list(); // order1 should be returned because it has an orderline with articleId == "1000"; // the contained collection for order1 should only have the orderLine with articleId == "1000"; // order2 should be returned because it has no orderlines assertEquals( 2, orders.size() ); for ( Object order : orders ) { Order o = (Order) order; if ( order1.getOrderId() == o.getOrderId() ) { assertEquals( "1000", o.getLines().iterator().next().getArticleId() ); } else if ( order2.getOrderId() == o.getOrderId() ) { assertEquals( 0, o.getLines().size() ); } else { fail( "unknown order" ); } } s.getTransaction().commit(); s.close(); } @Test public void testNonNullSubcriteriaRestrictionsOnRootCriteria() { Session s = openSession(); s.getTransaction().begin(); Criteria rootCriteria = s.createCriteria( Order.class ); Criteria subCriteria = rootCriteria.createCriteria( "orderLines", "ol", JoinFragment.LEFT_OUTER_JOIN ); assertNotSame( rootCriteria, subCriteria ); // add restriction to rootCriteria (NOT subcriteria) assertSame( rootCriteria, rootCriteria.add( Restrictions.eq( "ol.articleId", "3000" ) ) ); List orders = rootCriteria.list(); // results should be the same as testAliasWithNonNullRestrictions() (using Criteria.createAlias()) // order1 and order3 should be returned because each has articleId == "3000" // the contained collections should only have the orderLine with articleId == "3000" assertEquals( 2, orders.size() ); for ( Iterator it = orders.iterator(); it.hasNext(); ) { Order o = (Order) it.next(); if ( order1.getOrderId() == o.getOrderId() ) { assertEquals( 1, o.getLines().size() ); assertEquals( "3000", o.getLines().iterator().next().getArticleId() ); } else if ( order3.getOrderId() == o.getOrderId() ) { assertEquals( 1, o.getLines().size() ); assertEquals( "3000", o.getLines().iterator().next().getArticleId() ); } else { fail( "unknown order" ); } } s.getTransaction().commit(); s.close(); } @Test @TestForIssue( jiraKey = "HHH-9597") public void testMultipleSubCriteriaRestrictionsOnCollections() { Session s = openSession(); s.getTransaction().begin(); Criteria rootCriteria = s.createCriteria(Order.class, "order"); rootCriteria.createCriteria( "order.orderLines", "line", JoinType.LEFT_OUTER_JOIN ) .add(Restrictions.eq("line.articleId", "3000")); rootCriteria.createCriteria( "order.orderContacts", "contact", JoinType.LEFT_OUTER_JOIN ) .add(Restrictions.eq("contact.contact", "Contact1")); // result should be order1, because that's the only Order with: // 1) orderLines containing an OrderLine with articleId == "3000" // and 2) orderContacts containing an OrderContact with contact == "Contact1" // Since both restrictions are in subcriteria, both collections should be non-filtered // (i.e. includes all elements in both collections) Order result = (Order) rootCriteria.uniqueResult(); assertEquals( order1.getOrderId(), result.getOrderId() ); assertEquals( 2, result.getContacts().size() ); assertEquals( 2, result.getLines().size() ); s.getTransaction().commit(); s.close(); } @Test @TestForIssue( jiraKey = "HHH-9597") public void testMultipleRootCriteriaRestrictionsOnCollections() { Session s = openSession(); s.getTransaction().begin(); Criteria rootCriteria = s.createCriteria(Order.class, "order"); rootCriteria.createAlias( "order.orderLines", "line", JoinType.LEFT_OUTER_JOIN ) .add(Restrictions.eq("line.articleId", "3000")); rootCriteria.createAlias("order.orderContacts", "contact", JoinType.LEFT_OUTER_JOIN) .add( Restrictions.eq( "contact.contact", "Contact1" ) ); // result should be order1, because that's the only Order with: // 1) orderLines containing an OrderLine with articleId == "3000" // and 2) orderContacts containing an OrderContact with contact == "Contact1" // Since both restrictions are in root criteria, both collections should be filtered // to contain only the elements that satisfy the restrictions. Order result = (Order) rootCriteria.uniqueResult(); assertEquals( order1.getOrderId(), result.getOrderId() ); assertEquals( 1, result.getLines().size() ); assertEquals( "3000", result.getLines().iterator().next().getArticleId() ); assertEquals( 1, result.getContacts().size() ); assertEquals( "Contact1", result.getContacts().iterator().next().getContact() ); s.getTransaction().commit(); s.close(); } @Test @TestForIssue( jiraKey = "HHH-9597") public void testRootAndSubCriteriaRestrictionsOnCollections() { // the result of all Criteria in this test will be order1, because that's the only Order with: // 1) orderLines containing an OrderLine with articleId == "3000" // and 2) orderContacts containing an OrderContact with contact == "Contact1" Session s = openSession(); s.getTransaction().begin(); Criteria rootCriteria = s.createCriteria(Order.class, "order"); rootCriteria.createCriteria("order.orderContacts", "contact", JoinType.LEFT_OUTER_JOIN) .add( Restrictions.eq( "contact.contact", "Contact1" ) ); rootCriteria.createAlias("order.orderLines", "line", JoinType.LEFT_OUTER_JOIN) .add(Restrictions.eq("line.articleId", "3000")); // Since restriction on orderLines is on root criteria, that collection should be filtered. // Since restriction on orderContacts is on a subcriteria, that collection should be // non-filtered (contain all its elements) Order result = (Order) rootCriteria.uniqueResult(); assertEquals( order1.getOrderId(), result.getOrderId() ); assertEquals( 1, result.getLines().size() ); assertEquals( "3000", result.getLines().iterator().next().getArticleId() ); assertEquals( 2, result.getContacts().size() ); s.getTransaction().commit(); s.close(); // The following should have the same result as the previous, since it has the same // restrictions applied in reverse order. s = openSession(); s.getTransaction().begin(); rootCriteria = s.createCriteria(Order.class, "order"); rootCriteria.createCriteria("order.orderContacts", "contact", JoinType.LEFT_OUTER_JOIN) .add(Restrictions.eq("contact.contact", "Contact1")); rootCriteria.createAlias( "order.orderLines", "line", JoinType.LEFT_OUTER_JOIN ) .add(Restrictions.eq("line.articleId", "3000")); // Since restriction on orderLines is on root criteria, that collection should be filtered. // Since restriction on orderContacts is on a subcriteria, that collection should be // non-filtered (contain all its elements) result = (Order) rootCriteria.uniqueResult(); assertEquals( order1.getOrderId(), result.getOrderId() ); assertEquals( 1, result.getLines().size() ); assertEquals( "3000", result.getLines().iterator().next().getArticleId() ); assertEquals( 2, result.getContacts().size() ); s.getTransaction().commit(); s.close(); // Even though the following seem redundant, there was a failure due to HHH-9597 // that reproduced when filtering Order.orderContacts, but not order.orderLines. s = openSession(); s.getTransaction().begin(); rootCriteria = s.createCriteria(Order.class, "order"); rootCriteria.createAlias( "order.orderContacts", "contact", JoinType.LEFT_OUTER_JOIN ) .add(Restrictions.eq("contact.contact", "Contact1")); rootCriteria.createCriteria( "order.orderLines", "line", JoinType.LEFT_OUTER_JOIN ) .add(Restrictions.eq("line.articleId", "3000")); // Since restriction on orderContacts is on root criteria, that collection should be filtered. // Since restriction on orderLines is on a subcriteria, that collection should be // non-filtered (contain all its elements) result = (Order) rootCriteria.uniqueResult(); assertEquals( order1.getOrderId(), result.getOrderId() ); assertEquals( 2, result.getLines().size() ); assertEquals( 1, result.getContacts().size() ); assertEquals( "Contact1", result.getContacts().iterator().next().getContact() ); s.getTransaction().commit(); s.close(); // The following should have the same result as the previous, since it has the same // restrictions applied in reverse order. s = openSession(); s.getTransaction().begin(); rootCriteria = s.createCriteria(Order.class, "order"); rootCriteria.createCriteria( "order.orderLines", "line", JoinType.LEFT_OUTER_JOIN ) .add(Restrictions.eq("line.articleId", "3000")); rootCriteria.createAlias( "order.orderContacts", "contact", JoinType.LEFT_OUTER_JOIN ) .add(Restrictions.eq("contact.contact", "Contact1")); result = (Order) rootCriteria.uniqueResult(); assertEquals( order1.getOrderId(), result.getOrderId() ); assertEquals( 2, result.getLines().size() ); assertEquals( 1, result.getContacts().size() ); assertEquals( "Contact1", result.getContacts().iterator().next().getContact() ); s.getTransaction().commit(); s.close(); } @Test @TestForIssue( jiraKey = "HHH-9597") public void testSubCriteriaRestrictionsOnCollectionsNestedInManyToOne() { // the result of all Criteria in this test will be order1, because that's the only Order with: // 1) orderContacts containing an OrderContact with contact == "Contact1" // and 2) orderAddress.notifiedAddresses containing an Address with addressText == "over the rainbow" Session s = openSession(); s.getTransaction().begin(); Criteria rootCriteria = s.createCriteria(Order.class, "order"); rootCriteria.createCriteria("order.orderContacts", "contact", JoinType.LEFT_OUTER_JOIN) .add( Restrictions.eq( "contact.contact", "Contact1" ) ); rootCriteria.createCriteria( "order.orderAddress", "orderAddress", JoinType.LEFT_OUTER_JOIN ) .createCriteria( "orderAddress.notifiedAddresses", "notifiedAddress", JoinType.LEFT_OUTER_JOIN ) .add( Restrictions.eq( "notifiedAddress.addressText", "over the rainbow" ) ); // Since restrictions are on subcriteria, the collections should n on orderLines is on root criteria, that collection should be filtered. // Since restriction on orderContacts is on a subcriteria, that collection should be // non-filtered (contain all its elements) Order result = (Order) rootCriteria.uniqueResult(); assertEquals( order1.getOrderId(), result.getOrderId() ); assertEquals( 2, result.getContacts().size() ); assertEquals( 2, result.getOrderAddress().getNotifiedAddresses().size() ); s.getTransaction().commit(); s.close(); // The following should have the same result as the previous, since it has the same // restrictions applied in reverse order. s = openSession(); s.getTransaction().begin(); rootCriteria = s.createCriteria(Order.class, "order"); rootCriteria.createCriteria( "order.orderAddress", "orderAddress", JoinType.LEFT_OUTER_JOIN ) .createCriteria( "orderAddress.notifiedAddresses", "notifiedAddress", JoinType.LEFT_OUTER_JOIN ) .add( Restrictions.eq( "notifiedAddress.addressText", "over the rainbow" ) ); rootCriteria.createCriteria("order.orderContacts", "contact", JoinType.LEFT_OUTER_JOIN) .add( Restrictions.eq( "contact.contact", "Contact1" ) ); // Since restrictions are on subcriteria, the collections should n on orderLines is on root criteria, that collection should be filtered. // Since restriction on orderContacts is on a subcriteria, that collection should be // non-filtered (contain all its elements) result = (Order) rootCriteria.uniqueResult(); assertEquals( order1.getOrderId(), result.getOrderId() ); assertEquals( 2, result.getContacts().size() ); assertEquals( 2, result.getOrderAddress().getNotifiedAddresses().size() ); s.getTransaction().commit(); s.close(); } protected void prepareTest() { Session s = openSession(); s.getTransaction().begin(); // Order with one mathing line order1 = new Order(); OrderLine line = new OrderLine(); line.setArticleId( "1000" ); order1.addLine( line ); line = new OrderLine(); line.setArticleId( "3000" ); order1.addLine( line ); s.persist( order1 ); OrderContact contact = new OrderContact(); contact.setContact( "Contact1" ); order1.addContact( contact ); contact = new OrderContact(); contact.setContact( "Contact2" ); order1.addContact( contact ); OrderAddress orderAddress = new OrderAddress(); Address address = new Address(); address.setAddressText( "over the rainbow" ); orderAddress.setDeliveryAddress( address ); Address otherAddress = new Address(); otherAddress.setAddressText( "other place" ); orderAddress.getNotifiedAddresses().add( address ); orderAddress.getNotifiedAddresses().add( otherAddress ); order1.setOrderAddress( orderAddress ); s.persist( order1 ); // Order with no lines order2 = new Order(); contact = new OrderContact(); contact.setContact( "Contact1" ); order2.addContact( contact ); s.persist( order2 ); // Order with non-matching line order3 = new Order(); line = new OrderLine(); line.setArticleId( "3000" ); order3.addLine( line ); s.persist( order3 ); s.getTransaction().commit(); s.close(); } protected void cleanupTest() { Session s = openSession(); s.getTransaction().begin(); List orders = s.createQuery( "from Order" ).list(); for( Object order : orders ) { s.delete( order ); } s.createQuery( "delete from OrderContact" ).executeUpdate(); s.createQuery( "delete from OrderLine" ).executeUpdate(); s.createQuery( "delete from Order" ).executeUpdate(); s.getTransaction().commit(); s.close(); } private static boolean isBlank(String s) { return s == null || s.trim().length() == 0; } }