所以,我有A级和B级。
他们使用以下配置共享主键:
在A组中,我将B类作为孩子参考
@OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
public B getB()
{
return b;
}
在B类中,为了从父类A获取ID,我使用以下注释:
@Id
@GeneratedValue(generator = "customForeignGenerator")
@org.hibernate.annotations.GenericGenerator(name = "customForeignGenerator", strategy = "foreign", parameters = @org.hibernate.annotations.Parameter(name = "property", value = "a"))
@Column(name = "a_id")
public Long getId()
{
return id;
}
@MapsId("id")
@OneToOne(mappedBy = "b")
@PrimaryKeyJoinColumn
public A getA()
{
return a;
}
问题是uppon用
保存A.session.saveOrUpdate(aInstance);
DB返回以下错误:
Duplicate entry '123456' for key 'PRIMARY'
这告诉我们两件事,首先是@MapsId
正常工作,将A的ID分配给B,第二个是hibernate决定它是'保存&#39 ;并且不是“更新”,这只会在saveOrUpdate
时发生,当Id为空时? (奇怪?)
通常的解决方案是get
来自DB的旧B和merge
,如果存在的话,但这会产生很多问题,例如将旧的A从数据库转换为会话或进行会话可怕的#34; a different object with the same identifier value was already associated with the session
"对于相关对象的休眠错误。也不是非常友好的表现,做了无聊的DB命中。
我的anotations中有错误吗?我做错了吗?这个的正常配置是什么?
修改
这有点违背了使用@MapsId
,手动设置ID的目的,但由于没有找到解决方案,我确实手动设置了ID:
if(aInstance.getId() != null)
aInstance.getB().setId(aInstance.getId());
session.saveOrUpdate(aInstance);
直到很久以前,这又返回了以下错误:
org.hibernate.StaleStateException:
Batch update returned unexpected row count from update: 0 actual row count: 0 expected: 1
但由于某种原因,它停止抛出错误,现在它的工作原理。在所有情况下,之前的代码仍然有效,因为aInstance
可能没有Id,在这种情况下,MapId可以在BD中完美地插入新的A和B.问题仅出在Update上。
是\它是一个hibernate错误?大概。当StaleStateException
再次出现时,我会让你们知道。
目前这是一个临时解决方案,直到有人提出实际解决方案。
答案 0 :(得分:1)
我终于找到了所有问题的答案。
要了解问题的根源,我们必须提醒saveOrUpdate(object)
如何运作。
1)如果object
设置了ID,saveOrUpdate
将会Update
,否则它将Save
。
2)如果hibernate认定它是Save
但对象已在数据库上,您想要更新,则会发生Duplicate entry '123456' for key 'PRIMARY'
异常。
3)如果hibernate确定它是Update
但对象不在DB中,则要保存,StaleStateException
异常发生。
问题依赖于以下事实:如果数据库中存在aInstance
且已有ID,则@MapsId
会将该ID提供给B
,而忽略规则如上所述,Hibernate认为B
也可能存在于DB中。只有当A
和B
在DB中不存在或者它们都存在时,它才能正常工作。
因此变通方法解决方案是确保Set
仅ID ,如果每个对象都存在于DB中,则仅,并将ID设置为null没有:
B dbB = (B) unmarshaller.getDetachedSession().createCriteria(B.class).add(Restrictions.idEq(aInstance.getId())).uniqueResult();
if (dbB != null) //exists in DB
{
aInstance.getB().setId(aInstance.getId()); //Tell hibernate it is an Update
//Do the same for any other child classes to B with the same strategy if there are any in here
}
else
{
aInstance.getB().setId(null); //Tell hibernate it is a Save
}
unmarshaller.getDetachedSession().clear();
(使用分离会话,以便主会话保持清除不需要的对象,避免" object with the same identifier in session
"异常)
如果您不需要DB对象,并且只想知道它是否存在于DB中,您可以使用Count,使其更轻:
String query = "select count(*) from " + B.class.getName() + " where id = " + aInstance.getId();
Long count = DataAccessUtils.uniqueResult(hibernateTemplate.find(query));
if (count != null && count > 0)
{
aInstance.getB().setId(aInstance.getId()); // update
}
else
{
aInstance.getB().setId(null); // save
}
现在你可以saveOrUpdate(aInstance);
但就像我说的那样,@MapsId
策略不是非常友好的Hibernate。