Ste*_*all 179 java session spring hibernate detach
我有一种情况需要将分离的对象重新附加到休眠会话,尽管会话中已经存在相同标识的对象,这将导致错误.
现在,我可以做两件事之一.
getHibernateTemplate().update( obj )
当且仅当对象在hibernate会话中不存在时,这才有效.抛出异常,说明当我稍后需要时,会话中已存在具有给定标识符的对象.
getHibernateTemplate().merge( obj )
当且仅当hibernate会话中存在对象时,此方法才有效.如果我使用它,当我需要稍后在会话中的对象时抛出异常.
鉴于这两种情况,我如何一般地将会话附加到对象?我不想使用异常来控制这个问题解决方案的流程,因为必须有一个更优雅的解决方案......
小智 176
所以似乎没有办法在JPA中重新附加陈旧的分离实体.
merge()
将陈旧状态推送到DB,并覆盖任何干预更新.
refresh()
无法在分离的实体上调用.
lock()
不能在一个分离的实体上调用,即使它可以,它确实重新附加了实体,使用参数'LockMode.NONE'调用'lock'意味着你锁定但不锁定,是API设计中最违反直觉的一部分我见过的.
所以你被卡住了.有一种detach()
方法,但没有attach()
或reattach()
.您无法使用对象生命周期中的明显步骤.
从关于JPA的类似问题的数量来看,似乎即使JPA声称拥有一个连贯的模型,它肯定与大多数程序员的心理模型不匹配,他们被诅咒浪费了很多时间试图了解如何获得JPA做最简单的事情,最终在他们的应用程序中使用缓存管理代码.
似乎唯一的方法是丢弃陈旧的分离实体,并使用相同的id执行查找查询,这将查找L2或DB.
MIK
Ste*_*ole 28
所有这些答案都错过了一个重要的区别.update()用于(重新)将对象图附加到Session.您传递的对象是被管理的对象.
merge()实际上不是(重新)附件API.注意merge()有一个返回值?那是因为它会返回托管图,这可能不是您传递的图.merge()是一个JPA API,其行为受JPA规范的约束.如果传入merge()的对象已经被管理(已经与Session关联)那么这就是Hibernate使用的图形; 传入的对象是从merge()返回的相同对象.但是,如果传递给merge()的对象被分离,则Hibernate会创建一个受管理的新对象图,并将状态从已分离的图复制到新的托管图上.同样,这完全由JPA规范决定和管理.
就"确保管理此实体或使其管理"的通用策略而言,这取决于您是否还要考虑尚未插入的数据.假设你这样做,请使用类似的东西
if ( session.contains( myEntity ) ) {
// nothing to do... myEntity is already associated with the session
}
else {
session.saveOrUpdate( myEntity );
}
Run Code Online (Sandbox Code Playgroud)
注意我使用了saveOrUpdate()而不是update().如果您不想在此处理尚未插入的数据,请使用update()代替...
Vla*_*cea 22
JPA 定义了以下实体状态:
从未与 Hibernate Session
(aka Persistence Context
)关联且未映射到任何数据库表行的新创建的对象被视为处于 New (Transient) 状态。
要成为持久化,我们需要显式调用该EntityManager#persist
方法或利用传递持久化机制。
持久实体已与数据库表行相关联,并且由当前运行的持久上下文管理。将检测到对此类实体所做的任何更改并将其传播到数据库(在会话刷新时间期间)。
使用 Hibernate,我们不再需要执行 INSERT/UPDATE/DELETE 语句。Hibernate 采用事务性的后写工作方式,并且在当前Session
刷新时间期间的最后一个负责任的时刻同步更改。
一旦当前运行的持久性上下文关闭,所有以前管理的实体都将分离。将不再跟踪后续更改,也不会发生自动数据库同步。
您可以使用EntityManager
接口定义的各种方法更改实体状态。
要更好地理解 JPA 实体状态转换,请考虑下图:
使用 JPA 时,要将分离的实体重新关联到活动的EntityManager
,您可以使用合并操作。
使用本机 Hibernate API 时,除了 之外merge
,您还可以使用更新方法将分离的实体重新附加到活动的 Hibernate 会话,如下图所示:
合并会将分离的实体状态(源)复制到托管实体实例(目标)。
考虑到我们已经持久化了以下Book
实体,现在实体被分离EntityManager
,因为用于持久化实体的 已关闭:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
Run Code Online (Sandbox Code Playgroud)
当实体处于分离状态时,我们对其进行如下修改:
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Run Code Online (Sandbox Code Playgroud)
现在,我们想要将更改传播到数据库,因此我们可以调用该merge
方法:
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
Run Code Online (Sandbox Code Playgroud)
Hibernate 将执行以下 SQL 语句:
SELECT
b.id,
b.author AS author2_0_,
b.isbn AS isbn3_0_,
b.title AS title4_0_
FROM
book b
WHERE
b.id = 1
-- Merging the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
Run Code Online (Sandbox Code Playgroud)
如果合并实体在当前 中没有等效项,EntityManager
则会从数据库中获取新的实体快照。
一旦有托管实体,JPA 将分离实体的状态复制到当前托管的实体上,并且在 Persistence Context 期间flush
,如果脏检查机制发现托管实体已更改,则会生成 UPDATE。
因此,在使用 时
merge
,即使在合并操作之后,分离的对象实例也将继续保持分离状态。
Hibernate,但不是 JPA 支持通过该update
方法重新附加。
一个 HibernateSession
只能为给定的数据库行关联一个实体对象。这是因为 Persistence Context 充当内存缓存(一级缓存),并且只有一个值(实体)与给定的键(实体类型和数据库标识符)相关联。
仅当没有其他 JVM 对象(匹配同一数据库行)已与当前 Hibernate 关联时,才可以重新附加实体Session
。
考虑到我们已经持久化了Book
实体,并且我们在Book
实体处于分离状态时修改了它:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Run Code Online (Sandbox Code Playgroud)
我们可以像这样重新附加分离的实体:
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
Run Code Online (Sandbox Code Playgroud)
而 Hibernate 会执行以下 SQL 语句:
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
Run Code Online (Sandbox Code Playgroud)
该
update
方法需要你unwrap
的EntityManager
一个休眠Session
。
与 不同merge
,提供的分离实体将与当前的持久性上下文重新关联,并且无论实体是否已修改,都会在刷新期间安排更新。
为了防止这种情况,您可以使用@SelectBeforeUpdate
Hibernate 注释,它将触发一个 SELECT 语句,该语句获取加载状态,然后由脏检查机制使用。
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
Run Code Online (Sandbox Code Playgroud)
可能发生的一个问题update
是,持久化上下文是否已经包含与以下示例具有相同 id 和相同类型的实体引用:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(book);
return book;
});
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
try {
doInJPA(entityManager -> {
Book book = entityManager.find(
Book.class,
_book.getId()
);
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(_book);
});
} catch (NonUniqueObjectException e) {
LOGGER.error(
"The Persistence Context cannot hold " +
"two representations of the same entity",
e
);
}
Run Code Online (Sandbox Code Playgroud)
现在,当执行上面的测试用例时,Hibernate 将抛出 aNonUniqueObjectException
因为第二个EntityManager
已经包含一个Book
与我们传递给的标识符相同的实体update
,并且持久化上下文不能保存同一实体的两个表示。
org.hibernate.NonUniqueObjectException:
A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1]
at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682)
at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)
Run Code Online (Sandbox Code Playgroud)
merge
如果您使用乐观锁定,则首选该方法,因为它可以防止丢失更新。
这update
有利于批量更新,因为它可以防止merge
操作生成额外的 SELECT 语句,从而减少批量更新的执行时间。
cwa*_*ash 19
Undiplomatic答案:您可能正在寻找扩展的持久化上下文.这是Seam框架背后的主要原因之一...如果您特别想在Spring中使用Hibernate,请查看这篇 Seam的文档.
外交答案:这在Hibernate文档中有所描述.如果您需要更多说明,请查看Java Persistence with Hibernate的第9.3.2节"使用分离对象".如果你做的不仅仅是使用Hibernate的CRUD,我强烈建议你拿到这本书.
Joh*_*zzo 12
如果您确定您的实体未被修改(或者您同意任何修改将丢失),那么您可以将其重新连接到具有锁定的会话.
session.lock(entity, LockMode.NONE);
Run Code Online (Sandbox Code Playgroud)
它将不会锁定任何内容,但它会从会话缓存中获取实体,或者(如果没有找到)从数据库中读取它.
当您从"旧"(例如来自HttpSession)实体导航关系时,防止LazyInitException非常有用.您首先"重新附加"该实体.
使用get也可以工作,除非你获得映射的继承(这将在getId()上抛出异常).
entity = session.get(entity.getClass(), entity.getId());
Run Code Online (Sandbox Code Playgroud)
小智 9
我回到了JavaDoc org.hibernate.Session
,发现了以下内容:
通过调用或
save()
,可以使瞬态实例持久化.通过调用可以使持久实例变为瞬态.由或方法返回的任何实例都是持久的.独立实例可以通过调用持久化,,或.瞬态或分离实例的状态也可以通过调用而持久化为新的持久实例.persist()
saveOrUpdate()
delete()
get()
load()
update()
saveOrUpdate()
lock()
replicate()
merge()
因此update()
,saveOrUpdate()
,lock()
,replicate()
并且merge()
是候选人的选择.
update()
:如果存在具有相同标识符的持久实例,则将抛出异常.
saveOrUpdate()
:保存或更新
lock()
:已弃用
replicate()
:保持给定分离实例的状态,重用当前标识符值.
merge()
:返回具有相同标识符的持久对象.给定的实例不会与会话关联.
因此,lock()
不应该直接使用并且基于功能要求可以选择它们中的一个或多个.
我用NHibernate在C#中这样做了,但它应该在Java中以相同的方式工作:
public virtual void Attach()
{
if (!HibernateSessionManager.Instance.GetSession().Contains(this))
{
ISession session = HibernateSessionManager.Instance.GetSession();
using (ITransaction t = session.BeginTransaction())
{
session.Lock(this, NHibernate.LockMode.None);
t.Commit();
}
}
}
Run Code Online (Sandbox Code Playgroud)
在每个对象上调用First Lock,因为Contains始终为false.问题是NHibernate按数据库ID和类型比较对象.Contains使用equals
方法,如果没有覆盖,则通过引用进行比较.使用该equals
方法,它没有任何例外:
public override bool Equals(object obj)
{
if (this == obj) {
return true;
}
if (GetType() != obj.GetType()) {
return false;
}
if (Id != ((BaseObject)obj).Id)
{
return false;
}
return true;
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
167507 次 |
最近记录: |