4

我在使用 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 代理(所有字段为空,包括 id 字段)。这与 currentCustomer.lastItem 中的对象相同。
  • 目的

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

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

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

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

4

2 回答 2

4

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

当您加载时,它会连同任何随它一起加载的对象一起Customer被放入缓存中。Session这包括该对象的代理对象OrderItem,因为您有FetchType.LAZY. Hibernate 只允许一个实例与任何特定 ID 相关联,因此任何进一步的操作将OrderItem使用该 ID将始终使用该代理。如果您要求以另一种方式Session获得该特定OrderItem信息,就像您通过加载Order包含它的方式一样Order,则由于会话级身份规则,该代理将具有代理。

这就是为什么当您颠倒顺序时它会“起作用”。加载第Order一个,它的集合是FetchType.EAGER,所以它(和一级缓存)已经完全实现了OrderItem. 现在加载一个Customer已将其lastItem设置为已加载OrderItem实例之一的 a,然后,您将拥有一个真实的OrderItem,而不是代理。

您可以查看Hibernate 手册中记录的身份规则:

对于附加到特定会话的对象... 用于数据库身份的 JVM 身份由 Hibernate 保证。

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

这里唯一真正的问题可能只是拥有它会让人感到困惑,因此任何特定的东西OrderItem都可能是代理或不是代理。也许你想简单地改变关系,让他们要么都懒惰,要么都渴望?

对于它的价值,你有 ManyToMany 关系 asEAGER和 ManyToOne as 有点奇怪LAZY。这与通常的设置完全相反,所以我至少会考虑改变它(尽管我显然不知道你的整个用例)。一种思考方式:如果OrderItem完全获取 an 的成本太高以至于在查询时出现问题,那么一次加载所有Customer这些肯定也太昂贵了吗?或者相反,如果它足够便宜来加载所有这些,那么它肯定足够便宜,当你得到一个?Customer

于 2013-02-28T14:00:38.463 回答
0

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

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

或使用带有“获取所有”的 HQL 查询

于 2013-03-01T16:52:09.213 回答