Search code examples
javahibernatehibernate-4.x

How can I conditionally load a collection mapped in an object?


Given the following mapping

<class name="com.domain.Season" table="cm.pub.jsn_mstr">
    <id name="seasonCode" column="season_code" length="1"/>
    <property name="name" type="string" column="name" length="20"/>
    <set name="promotions" lazy="false">
        <key column="season_code"/>
        <one-to-many class="com.domain.Promotion" not-found="ignore"/>
    </set>
</class>

How can I include or exclude the load of promotions? I could use lazy="true" though I'm using Jackson to serialize the result which is after the session is closed.

public Collection<Season> getSeasons(boolean withPromotions) {
    final Session session = sessionFactory.getCurrentSession();
    try {
        session.beginTransaction();
        return (List<Season>) session.createQuery("from Season s").list();
    } finally {
        session.getTransaction().commit();
    }
}

UPDATE: Problem with using lazy loading.

The getSeasons method above is used in an MVC controller that will retrieve seasons, then using jackson serialize them to JSON (using Spring/MVC's view-resolver) so I don't actually access the objects myself, therefore any attempt to lazily load the collection results in an exception (as jackson will call an iterator on all collection properties).

Here's an example that shows an exception will get thrown:

public Collection<Season> getSeasons(boolean withPromotions) {
    final Session session = sessionFactory.getCurrentSession();
    final List<Season> r;
    try {
        session.beginTransaction();
        r = (List<Season>) session.createQuery(
                withPromotions
                ? "from Season s join fetch s.promotions"
                : "from Season s"
                ).list();
    } finally {
        session.getTransaction().commit();
    }
    try {
        for (Season s : r) {
            for (Promotion p : s.getPromotions()) {
                // Exception thrown here as we attempted to get an iterator.
                LOG.debug("Promotion: " + p.getName());
            }
        }
    } catch (Exception ex) {
        LOG.error("Couldn't get promotions", ex);
    }
    return r;
}

And of course this time the mapping needs to have lazy="true" otherwise it will always eager read the collection.

<class name="com.domain.Season" table="cm.pub.jsn_mstr">
    <id name="seasonCode" column="jsn_seas" length="1"/>
    <set name="promotions" lazy="true">
        <key column="jpr_seas"/>
        <one-to-many class="com.domain.Promotion" not-found="ignore"/>
    </set>
</class>

Data type for promotions field is Collection<Promotion>.


Solution

  • I've found the only way I could do this is to not map the collection and instead send off two queries, and adding to the collection myself.

    This gives me greater control of the association and also improves performance as I'm only sending off two queries instead of 1+n (where n is the rows retrieved from the first query).