如何在Hibernate中实现多对多集合的延迟加载?

13

我可以懒加载一对多和多对一的关联,但无法处理多对多的关联。

我们有一个城市,其中有商家和地址。 商家可以有多个地址,多个商家可以拥有相同的地址。

当我们使用get方法加载商家时,

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

没问题,直到我们遍历它们之前,地址不会被加载。

但是当我们加载商家列表时,

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

即使我们没有获取地址,Hibernate也会自动加载它们。 以下是我的问题示例。 映射:
<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>

任何想法?

1
听起来有点奇怪。你能确认一下这个行为吗?你的集合是如何映射的? - Bozho
@Bozho 我可以通过记录查询来确认这种行为,我看到Hibernate加载了地址。我在问题中添加了映射。 - codea
1
这不是主题,但是许多对多关系中的一个不应该被标记为反向关系吗? - Ralph
@Ralph 或许我应该在 Address 类中标记集合 merchantinverse = "true",但既然它能够正常工作而且我不知道这会改变什么,所以我没有添加。 - codea
反向定义了关联的哪一侧维护它。如果两侧不匹配,则这很重要。 - Ralph
城市的Hibernate映射是什么? - Travis Stevens
2个回答

1

我找到了两种解决方法。简单的方法是使用事务。如果在业务方法中启动一个事务,您将能够在该方法的生命周期内随时惰性地初始化这些内容。如果您的事务由容器管理,则在该方法上简单地添加@TransactionAttribute(TransactionAttributeType.REQUIRED)即可完成此操作。另一种方法是使用Hibernate.initialize(object.getDesiredColletion()),这也会获取您的对象,但需要事务。

我的最后一个解决方案是,如果您没有事务。这个通用方法基本上会获取您的集合,并使用setter方法将它们设置在父对象中。您可以通过不传递ID并通用地获取它来改进此过程,如果您不关心更改Java上的安全设置,则可以直接将集合设置为父对象(即使它是私有的),在这种情况下,大部分代码都可以大大减少。

    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();
    }

}

2
我认为这不会解决我的问题。当我加载对象列表时,我不希望加载此对象的子项。谢谢。 - codea

-1

您可以使用条件对象来查询和使用FetchMode.EAGER。


OP已经说明,他不想加载其他关联实体。 - luksch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接