在Hibernate中,为什么saveOrUpdate在数据库中已存在对象时给出异常
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Hibernate中,为什么saveOrUpdate在数据库中已存在对象时给出异常相关的知识,希望对你有一定的参考价值。
以前,当我使用Hibernate向数据库添加实体时,我常常检查它是否已经添加。但是为了提高性能,我忘记了这个检查,只是尝试添加而不检查,因为我使用saveOrUpdate()
我的理解,如果Hibernate发现它已经添加它只会更新和我的保存所做的更改。
但相反,它失败了
18/08/2018 21.58.34:BST:Errors:addError:SEVERE: Adding Error:Database Error:Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.jthink.songlayer.MusicBrainzReleaseWrapper#95f6f584-407f-4b26-9572-bb8c6e9c580a]
java.lang.Exception
at com.jthink.songkong.analyse.general.Errors.addError(Errors.java:28)
at com.jthink.songkong.exception.ExceptionHandling.handleHibernateException(ExceptionHandling.java:209)
at com.jthink.songkong.db.ReleaseCache.addToDatabase(ReleaseCache.java:394)
at com.jthink.songkong.db.ReleaseCache.add(ReleaseCache.java:65)
@Entity
public class MusicBrainzReleaseWrapper
{
@Id
private String guid;
@Version
private int version;
@org.hibernate.annotations.Index(name = "IDX__MUSICBRAINZ_RELEASE_WRAPPER_NAME")
@Column(length = 1000)
private String name;
@Lob
@Column(length = 512000)
private String xmldata;
public String getGuid()
{
return guid;
}
public void setGuid(String guid)
{
this.guid = guid;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getXmldata()
{
return xmldata;
}
public void setXmldata(String xmldata)
{
this.xmldata = xmldata;
}
}
private static boolean addToDatabase(Release release)
{
Session session = null;
try
{
session = HibernateUtil.beginTransaction();
//Marshall to String
StringWriter sw = new StringWriter();
Marshaller m = jc.createMarshaller();
m.marshal(release, sw);
sw.flush();
MusicBrainzReleaseWrapper wrapper = new MusicBrainzReleaseWrapper();
wrapper.setGuid(release.getId());
wrapper.setName(release.getTitle().toLowerCase(Locale.UK));
wrapper.setXmldata(sw.toString());
session.saveOrUpdate(wrapper);
session.getTransaction().commit();
MainWindow.logger.info("Added to db release:" + release.getId() + ":" + release.getTitle());
return true;
}
catch (ConstraintViolationException ce)
{
MainWindow.logger.warning("Release already exists in db:"+release.getId()+":"+release.getTitle());
return true;
}
catch(GenericJDBCException jde)
{
MainWindow.logger.log(Level.SEVERE, "Failed:" +jde.getMessage());
ExceptionHandling.handleDatabaseException(jde);
}
catch(HibernateException he)
{
MainWindow.logger.log(Level.SEVERE, "Failed:" +he.getMessage());
ExceptionHandling.handleHibernateException(he);
}
catch(Exception e)
{
MainWindow.logger.log(Level.WARNING,"Failed AddReleaseToDatabase:"+release.getId()+ ':' +e.getMessage(),e);
throw new RuntimeException(e);
}
finally
{
HibernateUtil.closeSession(session);
}
return false;
}
用于在调用addToDatabase之前先检查
if(ReleaseCache.get(release.getId())==null)
{
addToDatabase(release)
}
您遇到的问题与通过MusicBrainzReleaseWrapper上的@Version注释启用的乐观锁定直接相关。 saveOrUpdate确实可以添加或更新实体,但这只有在实体版本与您尝试添加或合并的分离对象的实体版本相同时。
在您的特定示例中,分离对象的版本在数据库中的最后一个版本之前,因此无法对过时数据执行操作。
更新:
MusicBrainzReleaseWrapper wrapper = session.get(release.getId()):
//the wrapper is managed object
if (wrapper == null) {
//initilize wrapper with the values from release
.......
session.save(wrapper)
}
else {
// do not set ID here. ID is aready present!!!
// never manuay set the version field here
wrapper.setName(release.getTitle().toLowerCase(Locale.UK));
wrapper.setXmldata(sw.toString());
session.saveOrUpdate(wrapper);
//In case you don't need update logic at all
// remove the @Version field from the entity
// and do othing in the else clause , or throw exception
// or log error or anything you see fit
}
Hiberante对象有一个实体的3个状态。它们是: - Transient或New - Detached(从DB获取对象并关闭hibernate会话) - Persistent(从DB获取对象并打开hibernate会话)在saveOrUpdate方法中,它要么保存瞬态对象,要么更新分离的/持久对象。在您的代码中,您尝试创建Transient / New对象并在其中设置旧ID。这就是你遇到错误的原因。首先使用id获取对象然后更新它的正确方法。
不.saveOrUpdate
方法用于将实体持久化或合并到当前会话。它不符合你的期望。保存或更新实体是应用程序的特定逻辑。 Hibernate不执行任何应用程序的特定逻辑。
Session.merge()
可以直接保存以前未知的实例,但请注意,它不一定会避免针对数据库的额外选择。
@Pavan对Hibernate(或JPA)术语中的瞬态或分离实体是正确的。这两种状态都意味着Hibernate在其会话中(在StatefulPersistenceContext
中)尚未获得对该实体实例的引用,但是分离显然意味着数据库已知它。
merge()
指示Hibernate停止并检查分离的实例。第一次检查是在会话中的@Id
值,但如果它还没有,它必须命中数据库。saveOrUpdate()
指示Hibernate,调用者知道只检查StatefulPersistenceContext
的@Id
是安全的。如果它不存在,则假定该实体是瞬态的(即新的),并且Hibernate将继续进行插入操作。
saveOrUpdate()
适用于会话已知的实例(有或没有@Id
值)。
在你的情况下,显然Hibernate不知道分离的实例,所以你需要使用merge()
。但这也意味着Hibernate必须检查数据库中之前没有见过的实例 - 如果实体具有@Id
值。
要回到您问题中的原始意图,更新而不选择更难...
对于更新,Hibernate喜欢知道实体的先前状态。如果它使用动态更新(因此不更新所有列),这是有道理的,但否则你会认为它可以直接进行更新。我知道的唯一选择是直接更新查询(通过HQL或JPQL),但如果您有实体实例,这几乎不方便。也许别人知道怎么做。
以上是关于在Hibernate中,为什么saveOrUpdate在数据库中已存在对象时给出异常的主要内容,如果未能解决你的问题,请参考以下文章
在Hibernate中,为什么saveOrUpdate在数据库中已存在对象时给出异常
Hibernate的实体类中为什么要继承Serializable?