Hibernate @MapsId为什么会出现这个错误?

时间:2016-05-19 10:05:09

标签: java hibernate

所以,我有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再次出现时,我会让你们知道。

目前这是一个临时解决方案,直到有人提出实际解决方案。

1 个答案:

答案 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中。只有当AB在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。