When using the Criteria API with LEFT OUTER JOIN to add criterias to the children, the collections will only contain those children matching the criteria. (I am *not* using any filters)
Why is this?
Am I doing something wrong, or is this a bug?
Is there a workaround?
Hibernate version: 3.1.3
Name and version of the database you are using: hsqldb
Mapping documents:
Code:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="foo.Order" table="t_order">
<id name="orderId" column="order_id" type="int" unsaved-value="0" access="field" >
<generator class="identity" />
</id>
<set name="orderLines" cascade="all-delete-orphan" access="field" inverse="true" fetch="select">
<key column="order_id" />
<one-to-many class="foo.OrderLine" />
</set>
</class>
<class name="foo.OrderLine" table="order_line">
<id name="lineId" column="order_line_id" type="int" unsaved-value="0" access="field" >
<generator class="identity" />
</id>
<many-to-one name="order" column="order_id" class="foo.Order" />
<property name="articleId" column="article_id" type="string" />
</class>
</hibernate-mapping>
Entity classesCode:
package foo;
import java.util.*;
public class Order {
private int orderId;
public int getOrderId() {
return orderId;
}
private Set<OrderLine> orderLines = new HashSet<OrderLine>();
public Set<OrderLine> getLines() {
return Collections.unmodifiableSet(orderLines);
}
public void addLine(OrderLine orderLine){
orderLine.setOrder(this);
this.orderLines.add(orderLine);
}
public String toString() {
return "" + getOrderId() + " - " + getLines();
}
}
Code:
package foo;
public class OrderLine {
private int lineId = 0;
private Order order;
private String articleId;
public int getLineId() {
return lineId;
}
public Order getOrder() {
return order;
}
public String getArticleId() {
return articleId;
}
public void setOrder(Order order) {
this.order = order;
}
public void setArticleId(String articleId) {
this.articleId = articleId;
}
public String toString() {
return "[" + getLineId() + ":" + getArticleId() + "]";
}
}
Test case to show the problem:Code:
package foo;
import org.hibernate.test.TestCase;
import org.hibernate.Session;
import org.hibernate.Criteria;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Expression;
import org.hibernate.sql.JoinFragment;
import java.util.List;
public class SubcriteriaTest extends TestCase {
public SubcriteriaTest(String s) {
super(s);
}
protected String getBaseForMappings() {
return "foo/";
}
protected String[] getMappings() {
return new String[] { "Order.hbm.xml" };
}
public void testSubcriteria() {
Session s = openSession();
s.getTransaction().begin();
// Order with one mathing line and one non-matching
Order order = new Order();
OrderLine line = new OrderLine();
line.setArticleId("1000");
order.addLine(line);
line = new OrderLine();
line.setArticleId("3000");
order.addLine(line);
s.persist(order);
// Order with no lines
order = new Order();
s.persist(order);
// Order with non-matching line
order = new Order();
line = new OrderLine();
line.setArticleId("3000");
order.addLine(line);
s.persist(order);
s.flush();
s.clear();
// Get order, to verify persist()
order = (Order) s.get(Order.class, 1);
assertEquals("Order 1 has 2 lines", 2, order.getLines().size());
s.clear(); // (Removing this makes the problem disappear!!!)
// Search orders with specified article
List orders = findOrders(s, "3000", "3000");
assertEquals(2, orders.size());
order = (Order) orders.get(0);
assertEquals("Order 1 has 2 lines", 2, order.getLines().size()); // FAILS! Only contains matching line
// Search order with no lines, or
orders = findOrders(s, null, "1000");
assertEquals(2, orders.size());
order = (Order) orders.get(1);
assertTrue("No lines on order no 2", order.getLines().isEmpty());
order = (Order) orders.get(0);
assertEquals("Order 1 has 2 lines", 2, order.getLines().size()); // FAILS! Only contains matching line
s.close();
}
/**
* Find orders by criteria in search form.
* @param articleIdFrom Start of selection interval. If not provided, include orders
* with no lines.
* @param articleIdto End of selection interval.
*/
private List findOrders(Session s, String articleIdFrom, String articleIdto) {
Criteria criteria = s.createCriteria(Order.class);
// criteria.setFetchMode("orderLines", FetchMode.SELECT); // Note: Does not help
// Note: JoinFragment.LEFT_OUTER_JOIN must be used to include orders with no lines
Criteria linesCritera = criteria.createCriteria("orderLines", JoinFragment.LEFT_OUTER_JOIN);
/* Same result:
criteria.createAlias("orderLines", "lines", JoinFragment.LEFT_OUTER_JOIN);
*/
if(! isBlank(articleIdFrom) || ! isBlank(articleIdto)) {
if(! isBlank(articleIdFrom)) {
linesCritera.add(Expression.ge("articleId", articleIdFrom)); // >=
if(! isBlank(articleIdto)) // To also
linesCritera.add(Expression.le("articleId", articleIdto)); // <=
}
else if(! isBlank(articleIdto)) { // Only to
linesCritera.add(Expression.or(
Expression.isNull("articleId"), // Allow null
Expression.le("articleId", articleIdto)) // <=
);
}
}
criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY); // Needed b.o. left join
return criteria.list();
}
private static boolean isBlank(String s) {
return s == null || s.trim().length() == 0;
}
}