休眠错误:具有相同标识符值的另一个对象已与会话关联


91

在这个配置中,我本质上有一些对象(实际的数据模型要复杂一些):

  • A与B有多对多关系。(B具有inverse="true"
  • B与C具有多对一关系(我已cascade设置为"save-update"
  • C是一种类型/类别表。

另外,我可能应该提到主键是在保存时由数据库生成的。

使用我的数据,有时我会遇到这样的问题:A具有一组不同的B对象,而这些B对象引用了相同的C对象。

打电话时session.saveOrUpdate(myAObject),我收到了一个休眠错误消息:"a different object with the same identifier value was already associated with the session: C"。我知道休眠不能在同一会话中两次插入/更新/删除相同的对象,但是有什么办法解决吗?这似乎不是一种罕见的情况。

在研究此问题的过程中,我看到人们建议使用session.merge(),但是当我这样做时,所有“冲突”对象都会作为空白对象插入数据库,并且所有值都设置为null。显然,这不是我们想要的。

[编辑]我忘了提及的另一件事是(由于无法控制的体系结构原因),每次读取或写入都需要在单独的会话中完成。


看看这个答案可以帮助你..
joaonlima

Answers:


97

很有可能是因为B对象没有引用相同的Java C对象实例。它们引用数据库中的同一行(即相同的主键),但是它们是数据库的不同副本。

因此,正在发生的事情是,管理实体的Hibernate会话将跟踪哪个Java对象对应于具有相同主键的行。

一种选择是确保引用同一行的对象B的实体实际上引用了C的同一对象实例。或者,为该成员变量关闭级联。这样,当B持久时,C不会。但是,您将不得不分别手动保存C。如果C是类型/类别表,那么这样做可能很有意义。


3
谢谢jbx。如您所说,事实证明B对象引用了内存中的多个C实例。本质上发生的事情是,我的程序的一部分正在用C读取,并将其附加到B。另一部分是从数据库中加载具有相同C的另一个B。两者都被附加到A,这会触发保存时的错误。我已经将B-> C关系的<pre> cascade </ pre>设置为“ <pre> none </ pre>”,但是我仍然遇到相同的错误。在多对一或一对多关系中,是否有一种方法可以告诉Hibernate仅更改外键,而不必担心其余键?
约翰,

1
C的主键是否有任何ID生成策略?像序列发生器或类似的东西?
jbx

是的,每个数据库都有自己的序列。正如您提到的,级联实际上是问题所在。我们关闭了类型表的级联,对于其他表,我们使用了“合并”级联,这使我们可以调用merge()而不创建所有这些空行。我已相应地标记了您的答案,谢谢!
约翰,

14
我用过merge()而不是saveOrUpdate()和BOOM!它有效:)
Lahiru Ruhunage


13

您只需要做一件事。运行session_object.clear(),然后保存新对象。这将清除会话(正确命名)并从会话中删除有问题的重复对象。


9

我同意@Hemant Kumar,非常感谢。根据他的解决方案,我解决了我的问题。

例如:

@Test
public void testSavePerson() {
    try (Session session = sessionFactory.openSession()) {
        Transaction tx = session.beginTransaction();
        Person person1 = new Person();
        Person person2 = new Person();
        person1.setName("222");
        person2.setName("111");
        session.save(person1);
        session.save(person2);
        tx.commit();
    }
}

人.java

public class Person {
    private int id;
    private String name;

    @Id
    @Column(name = "id")
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Basic
    @Column(name = "name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

这段代码在我的应用程序中总是会犯错误: A different object with the same identifier value was already associated with the session,后来我发现我忘了自动增加主键!

我的解决方案是在您的主键上添加以下代码:

@GeneratedValue(strategy = GenerationType.AUTO)

6

这意味着您正在尝试使用对同一对象的引用在表中保存多行。

检查您的实体类的id属性。

@Id
private Integer id;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(unique = true, nullable = false)
private Integer id;

1
并非根据给出的说明有问题
Sudip Bhandari

5

使用以下方法将分配对象ID的任务从Hibernate转移到数据库:

<generator class="native"/>

这为我解决了问题。



3

解决上述问题的一种方法是覆盖hashcode()
在保存之前和之后,还要刷新休眠会话。

getHibernateTemplate().flush();

明确设置分离对象null也有帮助。


2

刚遇到此消息,但使用C#代码。不知道它是否相关(虽然完全相同的错误消息)。

我在调试带有断点的代码,并在调试器处于断点时通过私有成员扩展了一些集合。重新运行代码而不深入挖掘结构会使错误消息消失。看起来,调查私有延迟加载的集合已经使NHibernate加载了当时不应该加载的东西(因为它们属于私有成员)。

代码本身包装在一个相当复杂的事务中,该事务可以更新大量记录和许多依赖关系,作为该事务的一部分(导入过程)。

希望对遇到此问题的其他人有所帮助。


2

在Hibernate中找到“级联”属性并将其删除。当您将“ Cascade”设置为可用时,它将在与相关类有关系的另一个实体上调用其他操作(保存,更新和删除)。因此,相同的身份价值将发生。它与我合作。


1

我几天前都遇到了这个错误,并且花了太多时间修复这个错误。

 public boolean save(OrderHeader header) {
    Session session = sessionFactory.openSession();


    Transaction transaction = session.beginTransaction();

    try {
        session.save(header);

        for (OrderDetail detail : header.getDetails()) {
            session.save(detail);
        }

        transaction.commit();
        session.close();

        return true;
    } catch (HibernateException exception) {

        exception.printStackTrace();
        transaction.rollback();
        return false;
    }
}

在出现此错误之前,我没有在OrderDetil对象上提到ID生成类型。如果不生成Orderdetails的ID,则对于每个OrderDetail对象,其ID均保持为0。#jbx解释了这一点。是的,这是最好的答案。这个例子是如何发生的。


1

尝试先放置查询代码。那解决了我的问题。例如改变这个:

query1 
query2 - get the error 
update

对此:

query2
query1
update


0

当我插入这样的行时,由于主键生成错误,我遇到了这个问题:

public void addTerminal(String typeOfDevice,Map<Byte,Integer> map) {
        // TODO Auto-generated method stub
        try {
            Set<Byte> keySet = map.keySet();
            for (Byte byte1 : keySet) {
                Device device=new Device();
                device.setNumDevice(DeviceCount.map.get(byte1));
                device.setTimestamp(System.currentTimeMillis());
                device.setTypeDevice(byte1);
                this.getHibernateTemplate().save(device);
            }
            System.out.println("hah");
        }catch (Exception e) {
            // TODO: handle exception
            logger.warn("wrong");
            logger.warn(e.getStackTrace()+e.getMessage());
        }
}

我将id生成器类更改为Identity

<id name="id" type="int">
    <column name="id" />
    <generator class="identity"  />
 </id>

0

在我的情况下,只有flush()无法正常工作。我必须在flush()之后使用clear()。

public Object merge(final Object detachedInstance)
    {
        this.getHibernateTemplate().flush();
        this.getHibernateTemplate().clear();
        try
        {
            this.getHibernateTemplate().evict(detachedInstance);
        }
}


0

如果在我的IDE中打开了一个“表达式”选项卡,则该选项卡正在对导致此异常的对象进行休眠get调用。我正在尝试删除同一对象。我在delete调用上也有一个断点,这似乎是使此错误发生的必要条件。只需将另一个表达式选项卡设置为最前面的选项卡或更改设置以使ide不会在断点处停止就可以解决此问题。


0

确保,您的实体与所有映射实体具有相同的生成类型

例如:UserRole

public class UserRole extends AbstractDomain {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String longName;

private String shortName;

@Enumerated(EnumType.STRING)
private CommonStatus status;

private String roleCode;

private Long level;

@Column(columnDefinition = "integer default 0")
private Integer subRoleCount;

private String modification;

@ManyToOne(fetch = FetchType.LAZY)
private TypeOfUsers licenseType;

}

模块:

public class Modules implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String longName;

private String shortName;

}

带有映射的主实体

public class RoleModules implements Serializable{

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
private UserRole role;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
private Modules modules;

@Type(type = "yes_no")
private boolean isPrimaryModule;

public boolean getIsPrimaryModule() {
    return isPrimaryModule;
}

}


0

除了所有先前的答案,如果您为类使用Value Object而不在VO Transformer类中设置id属性,则可能在大型项目中解决此问题。


0

只需提交当前交易即可。

currentSession.getTransaction().commit();

现在您可以开始另一笔交易并对实体执行任何操作

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.