我有一个GORM对象,我正在进行集成测试。它有一个beforeUpdate
挂钩,用于保存以前密码哈希的历史记录。代码看起来像这样:
class Credentials {
List passwordHistory = []
String username
String password
static hasMany = [passwordHistory : String]
def beforeUpdate() {
// of course I'm not really storing plain-text passwords, but
// this is just for illustration.
if (isDirty('password')) { passwordHistory << password }
}
}
在集成测试中,我想知道原因:
appUser.credentials.password = newPassword
appUser.save(flush: true)
sessionFactory.currentSession.flush()
AppUser.withNewSession {
appUser = AppUser.get(appUser.id)
appUser.credentials.empty // eagerly fetch the list while session is open
}
assert !appUser.credentials.passwordHistory.empty() // contains the previous password
有效,但
appUser.credentials.password = newPassword
appUser.save()
sessionFactory.currentSession.flush()
AppUser.withNewSession {
appUser = AppUser.get(appUser.id)
appUser.credentials.empty // eagerly fetch the list while session is open
}
assert !appUser.credentials.passwordHistory.empty() // is empty
没有。不同之处在于flush: true
调用中的appUser.save()
。我认为对save()
的调用将对象附加到当前会话,但刷新当前会话不会将密码添加到passwordHistory
列表。这里到底发生了什么?
答案 0 :(得分:1)
如果我正确地解释Grails代码,那么您实际上正在处理两个不同的会话。来自文档:
默认情况下,集成测试在数据库事务中运行 在每次测试结束时回滚。这意味着保存了数据 测试期间不会持久保存到数据库中。
如果您仔细研究Grails GORM方法逻辑,您将看到当您处于事务中时,GORM会从ThreadLocal
类维护的TransactionSynchronizationManager
资源映射中获取其会话。如果找不到,则会打开一个新会话并将其绑定到地图 - 重要的区别是,它会明确地打开一个新会话。它不只是致电sessionFactory.getCurrentSession()
。
在save()
GORM逻辑结束时,如果您传入flush:true
,它将刷新与该事务关联的会话 - 从TransactionSynchronizationManager
中的资源映射获取的会话。
另一方面,当您致电flush()
时,您在sessionFactory.getCurrentSession()
会话中调用它,我相信这是一个与您使用的CurrentSessionContext
线程绑定的会话通过Hibernate SessionFactory
。 CurrentSessionContext
的实际实现不是重点,因为(除非我缺少Grails特定的实现),它不会返回TransactionSynchronizationManager
所持有的相同会话。
答案 1 :(得分:0)
在第一种情况下,您刷新会话,因此对象变得“脏”。 使用newSession闭包打开新会话时,可以修改对象,但如果要使修改“脏”,则必须在新会话打开时显式调用save()方法。 如果这样做,关闭后,您可以看到您在闭包中对象所做的修改。
在withNewSession之前的第二种情况下,对象不是脏的,因此修改对象时需要非显式调用,这就是为什么在第二种情况下看到列表为空的原因。