非法尝试将集合与两个打开的会话相关联 - 调用 Session.update() 时
Illegal attempt to associate a collection with two open sessions - when calling Session.update()
我有 2 个实体:User
和 Player
User
实体与 Player
有 @OneToOne
关系,Player
与 User
.[=30= 有 @OneToOne
关系]
出于优化目的,我将 User
存储在缓存中,并尝试为每个请求检索它们。
因为检索实体(来自缓存)与当前休眠 Session
分离,我调用当前 Session
的 update()
方法重新附加它。
为避免错误,我还使用 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 注释。
我有 2 个实体:User
和 Player
User
实体与 Player
有 @OneToOne
关系,Player
与 User
.[=30= 有 @OneToOne
关系]
出于优化目的,我将 User
存储在缓存中,并尝试为每个请求检索它们。
因为检索实体(来自缓存)与当前休眠 Session
分离,我调用当前 Session
的 update()
方法重新附加它。
为避免错误,我还使用 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 注释。