圣杯。Hibernate延迟加载多个对象

WeM*_*are 5 java grails hibernate

我在处理 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;
}
Run Code Online (Sandbox Code Playgroud)

在一些控制器类中

//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}"}
Run Code Online (Sandbox Code Playgroud)

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

  • 目的
  • 目的
  • javassist 代理(所有字段均为空,包括 id 字段)。这与 currentCustomer.lastItem 中的对象相同。
  • 目的

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

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

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

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

sha*_*kan 5

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

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

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

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

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

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

这里唯一真正的问题可能只是它令人困惑,因此任何特定的都OrderItem可能是代理或不是代理。也许您只想简单地改变这种关系,使他们要么都是懒惰的,要么都是渴望的?

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