我在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个项目,则它们如下所示:
此外,此代理对象将所有属性设置为null,并且在调用getter时不会初始化它。我必须在GrailsHibernateUtils.unwrapIdProxy()
内的每个元素上手动调用lastOrder.items
,以确保内部没有代理(这基本上会导致EAGER获取)。
这一个代理对象会导致一些非常奇怪的异常,这些异常很难在测试阶段跟踪。
有趣的事实:如果我更改操作的顺序(首先加载订单,然后加载客户),lastOrder.items
内的每个元素都会被初始化。
问题是:有没有办法告诉Hibernate它应该在触摸时初始化集合,无论集合中的任何元素是否已在会话中代理?
答案 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;