Search This Blog

Friday, 2 November 2012

Eager fetching does not work for parallel bags

In our previous posts we saw how Hibernate allows the use of eager fetching for collections. We also saw that with more than one collection having eager fetch enabled, how the Cartesian product problem was encountered. But this problem will never occur with Bags. Why? Because bags do not support this feature.
Consider the example of a Basket which has a bag of dry fruits and bag of exotic fruits.
public class FruitBag {
    private Integer id;
    private String color;
    private Collection<DryFruit> dryFruits = new LinkedList<DryFruit>();
    private Collection<ExoticFruit> exoticFruits = new LinkedList<ExoticFruit>();
}
The hbm for the same is as below:
<?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 package="com.collection.bag.join">
    <class name="FruitBag" table="FRUIT_BAG">
        <id name="id" type="integer">
            <column name="ID" />
            <generator class="native" />
        </id>
        <property name="color" type="string">
            <column name="COLOR" length="50" not-null="true" />
        </property>
        <bag name="dryFruits" cascade="all-delete-orphan" inverse="true"
            fetch="join">
            <key column="BAG_ID" not-null="true" />
            <one-to-many class="DryFruit" />
        </bag>
        <bag name="exoticFruits" cascade="all-delete-orphan" inverse="true"
            fetch="join">
            <key column="BAG_ID" not-null="true" />
            <one-to-many class="ExoticFruit" />
        </bag>

    </class>
</hibernate-mapping>
On trying to load a FruitBag object :
public static void testLoad() {
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    FruitBag basket = (FruitBag) session.load(FruitBag.class, fruitBagId);
    System.out.println("Number of fruits in " + basket.getColor() + " is "
        + (basket.getDryFruits().size() + basket.getExoticFruits()
            .size()));
    transaction.commit();
    session.close();
}
On running the code an exception is all we get
Exception in thread "main" org.hibernate.HibernateException: cannot simultaneously
fetch multiple bags
    at org.hibernate.loader.BasicLoader.postInstantiate(BasicLoader.java:66)
    at org.hibernate.loader.entity.EntityLoader.<init>(EntityLoader.java:75)
    at org.hibernate.loader.entity.EntityLoader.<init>(EntityLoader.java:43)
    at org.hibernate.loader.entity.EntityLoader.<init>(EntityLoader.java:33)
    at org.hibernate.loader.entity.BatchingEntityLoader.createBatchingEntityLoader(
BatchingEntityLoader.java:103)
    at org.hibernate.persister.entity.AbstractEntityPersister.createEntityLoader(Ab
stractEntityPersister.java:1748)
    at org.hibernate.persister.entity.AbstractEntityPersister.createEntityLoader(Ab
stractEntityPersister.java:1752)
    at org.hibernate.persister.entity.AbstractEntityPersister.createLoaders(Abstrac
tEntityPersister.java:2984)
    at org.hibernate.persister.entity.AbstractEntityPersister.postInstantiate(Abstr
actEntityPersister.java:2977)
    at org.hibernate.persister.entity.SingleTableEntityPersister.postInstantiate(Si
ngleTableEntityPersister.java:690)
    at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:290)
    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1294)
    at com.collection.bag.join.TestJoinBagProblem.main(TestJoinBagProblem.java:16)
The reason is simple:
Bags allow duplicates. As we saw earlier, when eager fetching parallel collections a lot of duplicate data is generated. Knowing which of the duplicates are actually valid Bag Entities is difficult. Sub selects or fetch selects need to be used here.
Once one of the joins was removed, the code worked.I removed fetch="join" from dryFruits and the code worked fine.
/* load com.collection.bag.join.FruitBag */ 
    select
        fruitbag0_.ID as ID0_1_,
        fruitbag0_.COLOR as COLOR0_1_,
        exoticfrui1_.BAG_ID as BAG3_3_,
        exoticfrui1_.ID as ID3_,
        exoticfrui1_.ID as ID2_0_,
        exoticfrui1_.Name as Name2_0_,
        exoticfrui1_.bag_id as bag3_2_0_ 
    from
        FRUIT_BAG fruitbag0_ 
    left outer join
        EXOTIC_FRUIT exoticfrui1_ 
            on fruitbag0_.ID=exoticfrui1_.BAG_ID 
    where
        fruitbag0_.ID= ?
    /* load one-to-many com.collection.bag.join.FruitBag.dryFruits */ 
    select
        dryfruits0_.BAG_ID as BAG3_1_,
        dryfruits0_.ID as ID1_,
        dryfruits0_.ID as ID1_0_,
        dryfruits0_.Name as Name1_0_,
        dryfruits0_.bag_id as bag3_1_0_ 
    from
        DRY_FRUIT dryfruits0_ 
    where
        dryfruits0_.BAG_ID= ?
It is when we have more than one bag with join fetch, that the code fails. It in fact fails at start up itself. Hibernate doesn't even attempt to generate the queries.

1 comment: