Hibernate错误:具有相同标识符值的不同对象已与会话关联

时间:2013-04-26 23:35:36

标签: java hibernate

我在这个配置中基本上有一些对象(真正的数据模型有点复杂):

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

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

对于我的数据,我有时遇到A有一组不同B对象的问题,这些B对象引用同一个C对象。

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

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

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

22 个答案:

答案 0 :(得分:74)

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

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

一种选择是确保引用同一行的对象B的实体实际上是指C的同一对象实例。或者关闭该成员变量的级联。这种方式当B持久存在时C不是。您必须单独手动保存C.如果C是一个类型/类别表,那么这样做可能是有意义的。

答案 1 :(得分:19)

只需将级联设置为MERGE即可,这应该可以解决问题。

答案 2 :(得分:10)

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

答案 3 :(得分:6)

我同意@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();
    }
}

Person.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)

答案 4 :(得分:5)

使用以下命令将对象ID从Hibernate转移到数据库的任务:

<generator class="native"/>

这解决了我的问题。

答案 5 :(得分:3)

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

getHibernateTemplate().flush();

将分离的对象显式设置为null也有帮助。

答案 6 :(得分:3)

添加注释 @GeneratedValue 到你要插入的bean。

答案 7 :(得分:3)

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

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

@Id
private Integer id;

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

答案 8 :(得分:2)

刚刚看到这条消息,但是在c#代码中。不确定它是否相关(但完全相同的错误信息)。

我正在使用断点调试代码,并在调试器处于断点时通过私有成员扩展了一些集合。重新运行代码而不挖掘结构会使错误消息消失。看起来私有懒惰加载集合的行为似乎使NHibernate加载了当时不应该加载的东西(因为它们是私有成员)。

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

希望是遇到这个问题的其他人的线索。

答案 9 :(得分:2)

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

答案 10 :(得分: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所解释的。是的,这是最好的答案。这个例子是如何发生的。

答案 11 :(得分:1)

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

query1 
query2 - get the error 
update

到此:

query2
query1
update

答案 12 :(得分:0)

这个问题的原因是你有不同的对象副本在你的子表中引用相同的原始数据,所以 spring 试图将你的对象视为新对象,但在保存它时标识有一个具有相同主键的原始数据。所以它给出了上述错误。

此问题的最佳解决方案是从 DB 加载整个对象(带有子实体的父实体)(您已经知道父对象的主键),然后更新从 DB 加载的对象中的值从您的新对象(其中您试图保存),然后保存您从具有新值的数据库加载的对象。

这将更新您在数据库中的值而不会出现上述错误。

PS- 不需要更新 ID,因为它们已经存在于从数据库加载的对象中,只更新需要更改的值

答案 13 :(得分:0)

另一种情况是可以生成自定义allocationSize的错误消息:

@Id
@Column(name = "idpar")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "paramsSequence")
@SequenceGenerator(name = "paramsSequence", sequenceName = "par_idpar_seq", allocationSize = 20)
private Long id;

不匹配

alter sequence par_idpar_seq increment 20;

可能会在插入过程中引起约束验证(很容易理解),或者“会话中已经有一个具有相同标识符值的不同对象”-这种情况不太明显。

答案 14 :(得分:0)

只需提交当前事务。

currentSession.getTransaction().commit();

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

答案 15 :(得分:0)

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

答案 16 :(得分:0)

请确保您的实体与所有映射实体具有相同的世代类型

Ex: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;
}

}

答案 17 :(得分:0)

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

答案 18 :(得分:0)

如果您使用EntityRepository,则使用saveAndFlush而不是save

答案 19 :(得分:0)

在我的情况下,只有flush()不起作用。我必须在flush()之后使用clear()。

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

答案 20 :(得分: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生成器类更改为标识

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

答案 21 :(得分:0)

在调用更新查询之前,您可能没有设置对象的标识符。