Answers:
这是我对方法的理解。这些主要是基于API尽管在实践中我并未全部使用。
saveOrUpdate 根据某些检查调用保存或更新。例如,如果不存在标识符,则调用save。否则,将调用更新。
保存 保持实体。如果不存在,将分配一个标识符。如果有的话,它实际上是在进行更新。返回生成的实体ID。
更新 尝试使用现有标识符持久化实体。如果不存在标识符,则认为会引发异常。
saveOrUpdateCopy 已过时,不应再使用。相反,有...
合并 现在,我的知识开始动摇。这里重要的是临时实体,分离实体和持久实体之间的区别。有关对象状态的更多信息,请在此处查看。使用保存和更新,您可以处理持久对象。它们链接到会话,因此Hibernate知道发生了什么变化。但是,当您有一个临时对象时,就不会涉及会话。在这些情况下,您需要使用merge进行更新,并坚持保存。
持久 如上所述,它用于瞬态对象。它不返回生成的ID。
╔══════════════╦═══════════════════════════════╦════════════════════════════════╗
║ METHOD ║ TRANSIENT ║ DETACHED ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ sets id if doesn't ║ sets new id even if object ║
║ save() ║ exist, persists to db, ║ already has it, persists ║
║ ║ returns attached object ║ to DB, returns attached object ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ sets id on object ║ throws ║
║ persist() ║ persists object to DB ║ PersistenceException ║
║ ║ ║ ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ ║ ║
║ update() ║ Exception ║ persists and reattaches ║
║ ║ ║ ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ copy the state of object in ║ copy the state of obj in ║
║ merge() ║ DB, doesn't attach it, ║ DB, doesn't attach it, ║
║ ║ returns attached object ║ returns attached object ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ ║ ║
║saveOrUpdate()║ as save() ║ as update() ║
║ ║ ║ ║
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝
update
临时对象很好,但我没有例外。
请参见Hibernate论坛,以了解持久和保存之间的细微差别。看起来不同之处在于最终执行INSERT语句的时间。由于save确实返回了标识符,因此无论事务状态如何,INSERT语句都必须立即执行(这通常是一件坏事)。Persist不会仅在分配标识符的情况下在当前运行的事务之外执行任何语句。保存/持久化都可用于瞬态实例,即尚未分配标识符的实例,因此不会保存在数据库中。
Update和Merge都适用于分离的实例,即在数据库中具有相应条目但当前未附加到Session(或由Session管理)的实例。它们之间的区别是传递给函数的实例发生了什么。update尝试重新附加该实例,这意味着该会话现在必须不存在持久实体的其他实例,否则将引发异常。merge只是将所有值复制到Session中的持久实例(如果当前未加载,则将加载该实例)。输入对象未更改。因此,合并比更为笼统更新,但可能会使用更多资源。
save() - If an INSERT has to be executed to get the identifier, then this INSERT happens immediately, no matter if you are inside or outside of a transaction. This is problematic in a long-running conversation with an extended Session/persistence context.
能否请您告诉我,如何在会话外进行插入,为什么这样做不好?
INSERT
ed 值被使用时,标识符由数据库生成。因此,在这种情况下,你不能现在无需产生它返回一个标识符,并生成它,你必须运行INSERT
现在。因为,长时间运行的事务不会运行,现在却只能在提交时,执行的唯一方法INSERT
是现在的TX之外运行。
该链接很好地解释了:
http://www.stevideter.com/2008/12/07/saveorupdate-versus-merge-in-hibernate/
我们都有很少遇到的那些问题,以至于当我们再次看到它们时,我们知道我们已经解决了这个问题,但是不记得如何解决。
在Hibernate中使用Session.saveOrUpdate()时抛出的NonUniqueObjectException是我的一种。我将向复杂的应用程序添加新功能。我所有的单元测试都工作正常。然后,在测试UI并尝试保存对象时,我开始收到一条消息“会话中已经存在具有相同标识符值的不同对象”的异常。这是Java Persistence with Hibernate的一些示例代码。
Session session = sessionFactory1.openSession();
Transaction tx = session.beginTransaction();
Item item = (Item) session.get(Item.class, new Long(1234));
tx.commit();
session.close(); // end of first session, item is detached
item.getId(); // The database identity is "1234"
item.setDescription("my new description");
Session session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();
Item item2 = (Item) session2.get(Item.class, new Long(1234));
session2.update(item); // Throws NonUniqueObjectException
tx2.commit();
session2.close();
要了解导致此异常的原因,了解分离的对象以及对分离的对象调用saveOrUpdate()(或仅调用update())时会发生的情况非常重要。
当我们关闭一个单独的Hibernate Session时,我们正在使用的持久对象将被分离。这意味着数据仍在应用程序的内存中,但是Hibernate不再负责跟踪对象的更改。
如果我们随后修改分离的对象并想要对其进行更新,则必须重新附加该对象。在重新连接过程中,Hibernate将检查同一对象是否还有其他副本。如果找到任何副本,它必须告诉我们它不知道“真实”副本是什么了。也许对我们希望保存的其他副本进行了其他更改,但是Hibernate并不了解它们,因为当时没有管理它们。
Hibernate不会保存可能的不良数据,而是通过NonUniqueObjectException告诉我们有关该问题的信息。
那么我们该怎么办呢?在Hibernate 3中,我们具有merge()(在Hibernate 2中,使用saveOrUpdateCopy())。此方法将强制Hibernate将任何更改从其他分离的实例复制到要保存的实例上,从而在保存之前合并内存中的所有更改。
Session session = sessionFactory1.openSession();
Transaction tx = session.beginTransaction();
Item item = (Item) session.get(Item.class, new Long(1234));
tx.commit();
session.close(); // end of first session, item is detached
item.getId(); // The database identity is "1234"
item.setDescription("my new description");
Session session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();
Item item2 = (Item) session2.get(Item.class, new Long(1234));
Item item3 = session2.merge(item); // Success!
tx2.commit();
session2.close();
重要的是要注意,merge返回对实例的最新版本的引用。它不是将项目重新附加到会话。如果测试实例是否相等(item == item3),在这种情况下,您会发现它返回false。从现在开始,您可能要使用item3。
还需要注意的是,Java Persistence API(JPA)没有分离和重新连接对象的概念,而是使用EntityManager.persist()和EntityManager.merge()。
我发现一般来说,使用Hibernate时,saveOrUpdate()通常足以满足我的需求。通常,只有在具有可以引用相同类型对象的对象时,才需要使用merge。最近,该异常的原因是在代码中验证了引用不是递归的。作为验证的一部分,我正在将同一对象加载到我的会话中,从而导致错误。
您在哪里遇到这个问题?合并对您有用吗,还是您需要其他解决方案?您是希望始终使用合并,还是仅在特定情况下需要使用合并?
实际上,休眠save()
和persist()
方法之间的区别取决于我们使用的生成器类。
如果分配了我们的生成器类,则save()
和persist(
)方法之间没有区别。因为generator'assigned'的意思是,作为程序员,我们需要给主键值以保存在数据库中的权限[希望您知道这个generators概念]如果不是分配的Generator类,则假设我们的Generator类名是Increment意味着休眠它会自行将主键ID值分配给数据库[除了分配的生成器之外,休眠仅用于保管主键ID值记住],因此在这种情况下,如果我们调用save()
或persist()
方法,它将记录插入到正常情况下是数据库,但可以听到的是, save()
方法可以返回由休眠生成的主键id值,我们可以通过
long s = session.save(k);
在这种情况下,persist()
永远不会将任何价值返还给客户。
我找到了一个很好的示例,展示了所有休眠保存方法之间的区别:
http://www.journaldev.com/3481/hibernate-session-merge-vs-update-save-saveorupdate-persist-example
简而言之,根据上面的链接:
保存()
坚持()
saveOrUpdate()
可以与事务一起使用,也可以不与事务一起使用,就像save()一样,如果在不使用事务的情况下使用它,则映射的实体将不被保存;我们刷新会话。
根据提供的数据插入或更新查询的结果。如果数据库中存在数据,则执行更新查询。
update()
合并()
对于所有这些的实际示例,请参考我上面提到的链接,其中显示了所有这些不同方法的示例。
正如我在本文中所解释的,大多数情况下,您应该首选JPA方法,并且update
批处理任务。
JPA或Hibernate实体可以处于以下四个状态之一:
从一种状态到另一种状态的转换是通过EntityManager或Session方法完成的。
例如,JPA EntityManager
提供以下实体状态转换方法。
Hibernate的Session
实现所有的JPA EntityManager
方法,并提供一些额外的实体状态转换方法,如save
,saveOrUpdate
和update
。
要将实体的状态从“瞬态”(“新”)更改为“托管”(“持久”),我们可以使用persist
JPA提供的方法,该方法EntityManager
也由Hibernate继承Session
。
该
persist
方法触发PersistEvent
由DefaultPersistEventListener
Hibernate事件侦听器处理的。
因此,在执行以下测试用例时:
doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
LOGGER.info(
"Persisting the Book entity with the id: {}",
book.getId()
);
});
Hibernate生成以下SQL语句:
CALL NEXT VALUE FOR hibernate_sequence
-- Persisting the Book entity with the id: 1
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
请注意,在id
将Book
实体附加到当前的持久性上下文之前已分配。这是必需的,因为受管实体存储在Map
结构中,其中密钥由实体类型及其标识符组成,而值是实体引用。这就是为什么JPA EntityManager
和Hibernate Session
被称为一级缓存的原因。
打电话时 persist
,实体仅附加到当前正在运行的Persistence Context,并且INSERT可以推迟到flush
调用之前。
唯一的例外是IDENTITY生成器,它会立即触发INSERT,因为这是它获取实体标识符的唯一方法。因此,Hibernate无法使用IDENTITY生成器批量插入实体。有关此主题的更多详细信息,请参阅本文。
特定save
于Hibernate的方法早于JPA,自Hibernate项目开始以来就一直可用。
该
save
方法触发SaveOrUpdateEvent
由DefaultSaveOrUpdateEventListener
Hibernate事件侦听器处理的。因此,该save
方法等效于update
和saveOrUpdate
方法。
若要查看该save
方法的工作原理,请考虑以下测试用例:
doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
Session session = entityManager.unwrap(Session.class);
Long id = (Long) session.save(book);
LOGGER.info(
"Saving the Book entity with the id: {}",
id
);
});
当运行上述测试用例时,Hibernate生成以下SQL语句:
CALL NEXT VALUE FOR hibernate_sequence
-- Saving the Book entity with the id: 1
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
如您所见,结果与persist
方法调用相同。但是,与不同persist
,save
方法方法返回实体标识符。
有关更多详细信息,请参阅本文。
特定update
于Hibernate的方法旨在绕过脏检查机制,并在刷新时强制实体更新。
该
update
方法触发SaveOrUpdateEvent
由DefaultSaveOrUpdateEventListener
Hibernate事件侦听器处理的。因此,该update
方法等效于save
和saveOrUpdate
方法。
要查看该update
方法的工作原理,请考虑以下示例,该示例将Book
实体保留在一个事务中,然后在该实体处于分离状态时对其进行修改,并使用update
方法调用强制执行SQL UPDATE 。
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
LOGGER.info("Modifying the Book entity");
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
在执行上述测试用例时,Hibernate生成以下SQL语句:
CALL NEXT VALUE FOR hibernate_sequence
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
-- Modifying the Book entity
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
请注意,UPDATE
在持久性上下文刷新期间(即在提交之前)执行了,这就是为什么Updating the Book entity
首先记录消息。
@SelectBeforeUpdate
以避免不必要的更新现在,即使实体处于分离状态时未更改,也始终将执行UPDATE。为了防止这种情况,您可以使用@SelectBeforeUpdate
Hibernate批注,该批注将触发SELECT
获取的语句loaded state
然后由脏检查机制使用。
因此,如果我们使用注释对Book
实体进行@SelectBeforeUpdate
注释:
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
并执行以下测试用例:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
});
Hibernate执行以下SQL语句:
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
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
注意,UPDATE
由于Hibernate脏检查机制检测到该实体未修改,因此这次没有执行。
Hibernate的特定saveOrUpdate
方法只是一个别名save
和update
。
该
saveOrUpdate
方法触发SaveOrUpdateEvent
由DefaultSaveOrUpdateEventListener
Hibernate事件侦听器处理的。因此,该update
方法等效于save
和saveOrUpdate
方法。
现在,如以下示例所示,可以saveOrUpdate
在需要保留实体或强制使用时使用UPDATE
。
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");
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(_book);
});
NonUniqueObjectException
能与发生的一个问题save
,update
和saveOrUpdate
是如果持久性上下文已经包含具有相同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
);
}
现在,当执行上面的测试用例时,Hibernate将抛出a,NonUniqueObjectException
因为第二个EntityManager
已经包含一个Book
具有与我们传递给它的标识符相同的标识符的实体update
,并且Persistence Context无法容纳同一实体的两个表示形式。
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)
为了避免NonUniqueObjectException
,您需要使用merge
JPA提供的方法以及EntityManager
Hibernate继承的方法Session
。
如本文所述,merge
如果在持久性上下文中找不到实体引用,则从数据库中获取新的实体快照,并且它将复制传递给该merge
方法的分离实体的状态。
该
merge
方法触发MergeEvent
由DefaultMergeEventListener
Hibernate事件侦听器处理的。
要了解该merge
方法的工作原理,请考虑以下示例,该示例将Book
实体保留在一个事务中,然后在该实体处于分离状态时对其进行修改,然后将该分离的实体传递merge
给子序列“持久性上下文”。
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
LOGGER.info("Modifying the Book entity");
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
当运行上述测试用例时,Hibernate执行以下SQL语句:
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
-- Modifying the Book entity
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
请注意,由返回的实体引用merge
与我们传递给该merge
方法的分离引用不同。
现在,尽管您应该merge
在复制分离的实体状态时更喜欢使用JPA,但是SELECT
在执行批处理任务时,其他问题可能会出现问题。
因此,您应该优先使用 update
当您确定当前运行的持久性上下文中没有附加任何实体引用并且分离的实体已被修改时。
有关此主题的更多详细信息,请参阅本文。
要保留实体,您应该使用JPA persist
方法。要复制独立实体状态,merge
应该是首选。该update
方法仅对批处理任务有用。该save
和saveOrUpdate
只是别名update
,并在所有你不应该可能使用它们。
一些开发人员save
即使在实体已经被管理的情况下也进行调用,但这是一个错误并触发了冗余事件,因为对于托管实体,UPDATE是在Persistence上下文刷新时自动处理的。
有关更多详细信息,请参阅本文。
以下答案均不正确。所有这些方法似乎都是相似的,但实际上它们的作用完全不同。很难发表简短的评论。最好提供有关这些方法的完整文档的链接:http : //docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html
以上答案均不完整。尽管Leo Theobald的答案看起来最接近答案。
基本要点是休眠状态如何处理实体的状态以及状态发生变化时它如何处理它们。还必须看到所有与刷新和提交有关的内容,每个人似乎都完全忽略了这些内容。
切勿使用HIBERNATE的保存方法。忘了它甚至还存在吗!
坚持
正如大家所解释的,Persist基本上将实体从“瞬态”状态转换为“托管”状态。此时,slush或commit可以创建一个insert语句。但是实体仍将保持“托管”状态。同花顺不会改变。
在这一点上,如果您再次“坚持”,将没有任何改变。如果我们尝试保留一个持久化的实体,将不会再有任何节省。
当我们试图驱逐实体时,乐趣就开始了。
逐出是Hibernate的特殊功能,它将使实体从“托管”过渡到“独立”。我们不能对分离的实体调用持久化。如果这样做,那么Hibernate会引发异常,并且整个事务都会在提交时回滚。
合并与更新
这是两个有趣的函数,它们以不同的方式处理时会做不同的事情。他们俩都试图将实体从“已分离”状态转换为“托管”状态。但是做的不一样。
了解一个事实,即“分离”意味着某种“离线”状态。并且托管表示“在线”状态。
观察下面的代码:
Session ses1 = sessionFactory.openSession();
Transaction tx1 = ses1.beginTransaction();
HibEntity entity = getHibEntity();
ses1.persist(entity);
ses1.evict(entity);
ses1.merge(entity);
ses1.delete(entity);
tx1.commit();
什么时候做的?您认为会发生什么?如果您说这将引发异常,那么您是正确的。这将引发异常,因为merge已对处于分离状态的实体对象起作用。但这不会改变对象的状态。
在后台,合并将引发选择查询,并基本上返回处于附加状态的实体的副本。观察下面的代码:
Session ses1 = sessionFactory.openSession();
Transaction tx1 = ses1.beginTransaction();
HibEntity entity = getHibEntity();
ses1.persist(entity);
ses1.evict(entity);
HibEntity copied = (HibEntity)ses1.merge(entity);
ses1.delete(copied);
tx1.commit();
上面的示例之所以有效,是因为merge将一个新实体带入处于持久状态的上下文中。
当与Update一起应用时,相同效果很好,因为update实际上不会带来实体合并之类的副本。
Session ses1 = sessionFactory.openSession();
Transaction tx1 = ses1.beginTransaction();
HibEntity entity = getHibEntity();
ses1.persist(entity);
ses1.evict(entity);
ses1.update(entity);
ses1.delete(entity);
tx1.commit();
同时在调试跟踪中,我们可以看到Update并没有引发select之类的SQL查询,例如merge。
删除
在上面的示例中,我使用了delete而不是谈论delete。删除基本上会将实体从托管状态转换为“已删除”状态。并且在刷新或提交时将发出删除命令来存储。
但是,可以使用persist方法将实体从“已删除”状态恢复为“托管”状态。
希望以上解释能澄清任何疑问。