I am confused about how Criteria.setFetchMode() is supposed to be used. I've setup three simple classes: A, B, and C.
Class A contains an instance of Class B.
Class B contains an instance of Class C.
Each class has one other property and an ID.
A has a name (String).
B has a color (String).
C has a size (Integer).
My data looks like the following:
A:
Code:
id name b_id
1 foo 1
2 bar 2
B:
Code:
id color c_id
1 red 1
2 green <null>
3 blue 3
C:
Code:
id size
1 10
2 20
3 30
4 40
5 50
The problem that I'm having is that if I try to use Criteria to fetch a list of "A" objects, sorted by the size field from C, Hibernate is always using inner joins. I understand why it would want to do that by default, but I'm trying to override that behavior with Criteria.setFetchMode().
Using a Criteria that was built for A.class, I've tried both of the following (among dozens of other failed iterations):
Code:
criteria.setFetchMode("b.c", FetchMode.JOIN);
Code:
criteria.setFetchMode("a.b.c", FetchMode.JOIN);
Tracing this with a debugger, it looks like the decision is being made in CriteriaLoader.getJoinType(). Specifically this piece of code:
Code:
if ( translator.isJoin(path) ) {
return JoinFragment.INNER_JOIN;
}
else {
FetchMode fm = translator.getRootCriteria()
.getFetchMode(path);
I see paths of "b" and "b.c" being tested in that if statement, and both return true, which then returns JoinFragment.INNER_JOIN. With my calls to setFetchMode() above, I'm trying to override this behavior, but so far have not been able to do so.
In the information below, I've included an HQL statement that does exactly what I want. The trouble is that for the real application where I'm trying to do this, HQL would be a giant pain to use. This is the backend of a complex search screen, and I have no way of knowing until runtime which fields will be searched on, sorted by, etc. The HQL would have to be built up in pieces at runtime, which is why using a Criteria has been so nice up until I ran into this problem with joining.
If someone could point me toward what I'm doing wrong with this method call, I'd greatly appreciate it.
Hibernate version: 3.0.5
Mapping documents:hibernate.cfg.xml
Code:
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory name="local">
<property name="show_sql">true</property>
<property name="auto-import">false</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="connection.url">jdbc:mysql://localhost:3306/criteria</property>
<property name="connection.user">crit_test</property>
<property name="connection.password">crit_test</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<mapping resource="A.hbm.xml"/>
<mapping resource="B.hbm.xml"/>
<mapping resource="C.hbm.xml"/>
</session-factory>
</hibernate-configuration>
A.hbm.xml:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="A" table="A" lazy="true">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name"/>
<many-to-one name="b" class="B" column="b_id"/>
</class>
</hibernate-mapping>
B.hbm.xml:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="B" table="B" lazy="true">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="color"/>
<many-to-one name="c" class="C" column="c_id"/>
</class>
</hibernate-mapping>
C.hbm.xml:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="C" table="C" lazy="true">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="size"/>
</class>
</hibernate-mapping>
Code between sessionFactory.openSession() and session.close():Criteria query:
Code:
Criteria criteria = session.createCriteria(A.class, "a");
Criteria criteriaB = criteria.createCriteria("b");
Criteria criteriaC = criteriaB.createCriteria("c");
criteriaC.addOrder(Order.asc("size"));
criteria.setFetchMode("b.c", FetchMode.JOIN);
List list = criteria.list();
Iterator i = list.iterator();
while (i.hasNext()) {
A a = (A) i.next();
System.out.println(a);
}
HQL query:
Code:
Query query = session.createQuery(
"from A as a " +
" left join a.b as b " +
" left join b.c as c " +
" order by c.size asc ");
List list = query.list();
Iterator i = list.iterator();
while (i.hasNext()) {
A a = (A) ((Object[])i.next())[0];
System.out.println(a);
}
Full stack trace of any exception that occurs:No exception occurs.
Name and version of the database you are using:MySQL 4.0.24-debug (Windows)
The generated SQL (show_sql=true):Output from Criteria query:
Code:
Hibernate: select this_.id as id2_, this_.name as name0_2_, this_.b_id as b3_0_2_, b1_.id as id0_, b1_.color as color1_0_, b1_.c_id as c3_1_0_, c2_.id as id1_, c2_.size as size2_1_ from A this_ inner join B b1_ on this_.b_id=b1_.id inner join C c2_ on b1_.c_id=c2_.id order by c2_.size asc
A@c0c8b5[id=1,name=foo,b=B@7a9224[id=1,color=red,c=C@158015a[id=1,size=10]]]
Output from HQL query:
Code:
Hibernate: select a0_.id as id0_, b1_.id as id1_, c2_.id as id2_, a0_.name as name0_0_, a0_.b_id as b3_0_0_, b1_.color as color1_1_, b1_.c_id as c3_1_1_, c2_.size as size2_2_ from A a0_ left outer join B b1_ on a0_.b_id=b1_.id left outer join C c2_ on b1_.c_id=c2_.id order by c2_.size asc
A@1e16483[id=2,name=bar,b=B@1b82d69[id=2,color=green,c=<null>]]
A@1c6d11a[id=1,name=foo,b=B@1ca209e[id=1,color=red,c=C@123a389[id=1,size=10]]]
Supporting Java codeA.java:
Code:
import org.apache.commons.lang.builder.ToStringBuilder;
public class A {
private Long id;
private String name;
private B b;
public String toString () {
return new ToStringBuilder(this)
.append("id", id)
.append("name", name)
.append("b", b)
.toString();
}
// ... getters and setters ...
}
B.java:
Code:
import org.apache.commons.lang.builder.ToStringBuilder;
public class B {
private Long id;
private String color;
private C c;
public String toString() {
return new ToStringBuilder(this)
.append("id", id)
.append("color", color)
.append("c", c)
.toString();
}
// ... getters and setters ...
}
C.java:
Code:
import org.apache.commons.lang.builder.ToStringBuilder;
public class C {
private Long id;
private Integer size;
public String toString() {
return new ToStringBuilder(this)
.append("id", id)
.append("size", size)
.toString();
}
// ... getters and setters ...
}