Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to lazy load a many-to-many collection in hibernate?

I can lazy load one-to-many and many-to-one associations but I can't with the many-to-many associations.

We have a city in which we have merchants which have addresses. Merchants can have multiple addresses and multiple merchants can have the same addresses.

When we load a merchant with a get,

Merchant merchant = (Merchant) hib_session.get(Merchant.class, id);
System.out.println(merchant.getName());

it's ok, addresses aren't load until we iterate over them.

But when we load a list of merchants,

City city = (City) hib_session.get(City.class, city_name);
for(Merchant merchant : city.getMerchants()) {
  System.out.println(merchant.getName());
}

even if we don't get the addresses, hibernate automatically loads them.

Here's an example of my problem.

The mapping:

<class name="Merchant" table="Merchants" lazy="true">
  <id name="id"
    type="long"
    column="id">
    <generator class="native"></generator>
  </id>
  <set name="addresses" table="AdressesMerchant" lazy="true">
    <key column="merchant_id"></key>
    <many-to-many class="Adresses" column="address_id"/>
  </set>
</class>

<class name="Address" table="Adresses" lazy="true">
  <id name="id"
    type="long"
    column="id">
    <generator class="native"></generator>
  </id>
  <set name="merchants" table="AdressesMerchant" lazy="true">
    <key column="adress_id"/>
    <many-to-many column="merchant_id" class="Merchant"/>
  </set>
</class>

Any ideas?

like image 356
codea Avatar asked Jan 26 '11 14:01

codea


1 Answers

There are two fixes I have found for this. The easy one is to have a transaction. If you start a transaction in your bussiness method you will be able to lazily initialize these at any time within the life cycle of that method. If your transactions are container managed a simple @TransactionAttribute(TransactionAttributeType.REQUIRED) on that method will be enough to accomplish this. Another way is to use Hibernate.initialize(object.getDesiredColletion()) this will also fetch your objects but a transaction is also required.

My last solution is if you do not have a transaction. This generic method will basically get your colletions and use the setter method to set them in your parent object. You can improve this process by not passing in an id and obtaining it generically and if you dont care about changing the security settings on java, you can set the collections to your parent object directly (even if it is private) in which case much of this code can be grately reduced.

    public Object fetchCollections(Object parent, Long id, Class<?>... childs) {

    logger.debug("Need to fetch " + (childs.length) + " collections");
    String fieldName = "";
    String query = "";
    for (int i = 0; i < childs.length; i++) {
        logger.debug("Fetching colletion " + (i + 1) + " of "
                + (childs.length));
        logger.debug("Collection type is " + childs[i].getSimpleName());

        fieldName = findFieldName(childs[i], parent.getClass());
        if (fieldName == null) {
            logger.debug("Trying to search with parent class");
            logger.debug(parent.getClass().getSuperclass());
            fieldName = findFieldName(childs[i], parent.getClass()
                    .getSuperclass());

        }
        logger.debug("Creating query");
        query = "from " + childs[i].getSimpleName() + " obj " + "where "
        + " obj." + fieldName + ".id=" + id;
        logger.debug("Query= " + query);
        Set collection = new HashSet(em.createQuery(query).getResultList());
         setCollection(parent, collection, fieldName, childs[i]);

    }

    return parent;

}


private String findFieldName(Class parentClass, Class childClass) {
    String fieldName = null;
    boolean isCollection = false;
    logger.debug("Searching for field of type "
            + childClass.getSimpleName());
    for (Field f : parentClass.getDeclaredFields()) {

        String type = f.getGenericType().toString();
        if (f.getType().isInterface()
                && f.getGenericType().toString().contains("java.util.Set")) {
            logger.debug("This field is a collection");
            isCollection = true;
            type = type.substring(type.indexOf("<") + 1);
            type = type.substring(0, type.length() - 1);
        }

        if (isCollection) {
            logger.debug("Field= " + f.getName() + "  "
                    + f.getGenericType());
            if (type.equals(childClass.getName())) {
                logger.debug("*****MATCH FOUND");
                fieldName = f.getName();
                break;
            }
        } else {
            logger.debug("Type=" + f.getType().getName() + "  childType="
                    + childClass.getName());
            if (f.getType().getName().equals(childClass.getName())) {
                logger.debug("*****MATCH FOUND");
                fieldName = f.getName();
                break;
            }

        }

    }

    return fieldName;
}   


    private void setCollection(Object result, Set collection, String fieldName,
        Class childClass) {

    String methodName = "set" + fieldName.substring(0, 1).toUpperCase()
    + fieldName.substring(1);
    logger.debug("trivial setter is :" + methodName);
    Class<?>[] args = new Class<?>[] { java.util.Set.class };
    // try the trivial case
    boolean methodFound = false;
    Method method = null;
    try {
        method = result.getClass().getMethod(methodName, args);
        methodFound = true;
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        logger.debug("Method not found by trivial method");

    }

    if (!methodFound) {
        FindMethod: for (Method m : result.getClass().getMethods()) {
            // logger.debug(m.getName());
            for (Type t : m.getGenericParameterTypes()) {
                // logger.debug("\t"+t);
                String type = t.toString();
                type = type.substring(type.indexOf("<") + 1);
                type = type.substring(0, type.length() - 1);
                if (type.equals(childClass.getName())) {
                    logger.debug("***Found the Setter Method");
                    method = m;
                    break FindMethod;
                }
            }// end for parameter Types

        }// end for Methods

    }// end if

    invokeMethod(method, result, false, collection);

}



private void invokeMethod(Method method, Object obj, boolean initialize,
        Object... args) {

    try {
        if (method != null) {
            if (initialize)
                Hibernate.initialize(method.invoke(obj, args));
            else
                method.invoke(obj, args);

        }
        logger.debug("Method executed successfully");
    } catch (IllegalArgumentException e) {

        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }

}
like image 107
Erick Rosas Avatar answered Nov 02 '22 22:11

Erick Rosas