非法尝试将集合与两个打开的会话相关联 - 调用 Session.update() 时

Illegal attempt to associate a collection with two open sessions - when calling Session.update()

我有 2 个实体:UserPlayer

User 实体与 Player@OneToOne 关系,PlayerUser.[=30= 有 @OneToOne 关系]

出于优化目的,我将 User 存储在缓存中,并尝试为每个请求检索它们。

因为检索实体(来自缓存)与当前休眠 Session 分离,我调用当前 Sessionupdate() 方法重新附加它。

为避免错误,我还使用 Session 对象的 contains() 方法检查实体是否已附加到会话。

这是一个代码示例,向您展示我是如何进行的:

User user = User.getCached(User.class, userId);
if (user == null) {
    user = User.find("from User user left join fetch user.player where user.id = ?", userId).first();
    user = User.addToCache(User.class, user);
}
if (!getSession().contains(user.player))
    getSession().update(user.player);   // Throws the exception

调用此代码以检索连接的用户。它首先尝试从缓存中检索它。如果用户不在缓存中,我从数据库加载它并将其插入缓存中。然后我更新实体以将其附加到会话(如果未附加)。

有时 update() 方法会在我调用它时抛出以下异常:Illegal attempt to associate a collection with two open sessions

这是否意味着播放器已经连接到会话? 如果是,为什么 contains() 方法返回 false 呢? 如果不是,为什么我会收到此异常?以及如何避免它?

编辑

当我遇到此异常时,如果我尝试修改我的实体的值并调用 save(),我会遇到另一个异常,表明该实体已分离。如果之前的异常表明它已经关联到一个会话,如何分离它?

PS: 每个线程我只能有 1 个会话

我认为您在这里遇到了并发问题:假设有多个并行 HTTP 请求请求相同 User。解决方案是使用一些 synchronization.

update方法有很多缺点:

  • 它总是尝试在使用分离的对象时发出更新,否则它无法判断在分离实体时是否发生了某些变化
  • 它可能与已加载的实体副本冲突

这就是为什么 merge is a better alternative, because merge works like this:

  • 合并将把分离的实体状态(源)复制到托管实体实例(目标)。如果合并实体在当前 Session 中没有对等实体,则将从数据库中获取一个。即使在合并操作之后,分离的对象实例将继续保持分离状态。

所以 merge 操作更安全,如果实体没有改变,当它被分离时,dirty checking mechanism 将防止不必要的更新。

1_ 尝试在检索用户之前清除会话

2_ 添加 CascadeType.DETACH 到@OneToOne 注释。