Grails的。 Hibernate延迟加载多个对象

时间:2013-02-24 22:13:10

标签: java hibernate grails

我在Grails中使用代理对象时遇到了困难。 假设我有以下

class Order {
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name="xxx", joinColumns = {@JoinColumn(name = "xxx")}, inverseJoinColumns = {@JoinColumn(name = "yyy")})
    @OrderBy("id")
      @Fetch(FetchMode.SUBSELECT)
      private List<OrderItem> items;
    } 

class Customer {
    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
    @JoinColumn(name = "xxx",insertable = false, nullable = false)
    private OrderItem lastItem;

    private Long lastOrderId;
}

在一些控制器类中

//this all happens during one hibernate session.
    def currentCustomer = Customer.findById(id)
//at this point currentCustomer.lastItem is a javassist proxy

def lastOrder = Order.findById(current.lastOrderId)
//lastOrder.items is a proxy
//Some sample actions to initialise collections 
lastOrder.items.each { println "${it.id}"}

迭代后lastOrder.items仍然包含currentCustomer.lastItem的代理。例如,如果lastOrder.items集合中有4个项目,则它们如下所示:

  • 物体
  • 物体
  • javassist proxy(所有字段都为null,包括id字段)。这与currentCustomer.lastItem中的对象相同。
  • 物体

此外,此代理对象将所有属性设置为null,并且在调用getter时不会初始化它。我必须在GrailsHibernateUtils.unwrapIdProxy()内的每个元素上手动调用lastOrder.items,以确保内部没有代理(这基本上会导致EAGER获取)。

这一个代理对象会导致一些非常奇怪的异常,这些异常很难在测试阶段跟踪。

有趣的事实:如果我更改操作的顺序(首先加载订单,然后加载客户),lastOrder.items内的每个元素都会被初始化。

问题是:有没有办法告诉Hibernate它应该在触摸时初始化集合,无论集合中的任何元素是否已在会话中代理?

2 个答案:

答案 0 :(得分:4)

我认为这里发生的事情是第一级缓存(存储在Hibernate的Session实例中)与相关对象上有不同的FetchType之间的有趣交互。

加载Customer时,它会被加载到Session缓存中,以及加载它的任何对象。这包括OrderItem对象的代理对象,因为您有FetchType.LAZY。 Hibernate只允许一个实例与任何特定ID相关联,因此任何使用该ID OrderItem进行操作的进一步操作将始终使用该代理 。如果您要求同一Session以另一种方式获取该特定OrderItem,那么加载Order包含该Order时,Order将拥有该代理,因为会话级身份规则。

这就是为什么当你颠倒订单时它“有效”。首先加载FetchType.EAGER,它的集合是OrderItem,因此它(和第一级缓存)已完全实现Customer的实例。现在加载一个lastItem,其OrderItem设置为其中一个已加载的OrderItem个实例,并且有一个真实的OrderItem,而不是代理。

您可以看到Hibernate manual中记录的身份规则:

  

对于附加到特定会话的对象... Hibernate保证数据库标识的JVM标识。

所有这一切,即使您获得Session代理,只要关联的OrderItem.getId()仍处于活动状态,它就可以正常工作。我不一定希望代理ID字段显示为在调试器中填充或类似,只是因为代理以“特殊”方式处理事物(即,它不是POJO)。但它应该以与基类相同的方式响应方法调用。因此,如果您有一个OrderItem方法,它应该在调用时返回ID,并且类似地在任何其他方法上返回。因为它被懒惰地初始化了,其中一些调用可能需要数据库查询。

这里唯一真正的问题可能只是让它变得混乱,以便任何特定的EAGER都可以成为代理。也许你想简单地改变关系,以便他们既懒惰又两者都渴望?

对于它的价值,你将ManyToMany关系称为LAZY而ManyToOne关系为OrderItem有点奇怪。这与通常的设置正好相反,所以我至少会考虑改变它(虽然我显然不知道你的整个用例)。考虑这个问题的一种方法:如果Customer的查询费用非常昂贵,那么在查询Customer时就会出现问题,加载所有它们的确非常昂贵立刻?或者相反,如果它足够便宜以加载所有这些,当你获得{{1}}时它肯定便宜得到它?

答案 1 :(得分:0)

我认为您可以通过这种方式强行加载或使用

def lastOrder = Order.withCriteria(uniqueResult: true) {
       eq('id', current.lastOrderId)
       items{}
   }

或使用HQL查询并使用&#39;获取所有&#39;