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

Posted

技术标签:

【中文标题】休眠错误:具有相同标识符值的不同对象已与会话关联【英文标题】:Hibernate Error: a different object with the same identifier value was already associated with the session 【发布时间】:2013-04-21 05:34:10 【问题描述】:

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

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"。我知道hibernate不能在同一个会话中两次插入/更新/删除同一个对象,但是有没有办法解决这个问题?这种情况似乎并不少见。

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

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

【问题讨论】:

看看这个answer对你有没有帮助.. 【参考方案1】:

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

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

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

【讨论】:

感谢 jbx。正如您所说,事实证明 B 对象是指内存中的多个 C 实例。基本上发生的事情是我的程序的一部分正在读取 C,并将其附加到 B。另一部分是从数据库加载具有相同 C 的不同 B。两者都附加到 A ,这会触发保存错误。我已将 B->C 关系的
cascade
设置为“
none
”,但我仍然遇到同样的错误。在多对一或一对多的关系中,有没有办法告诉 Hibernate 只更改外键而不担心其余的?
C的主键有没有ID生成策略?像序列生成器或类似的东西? 是的,每个在数据库中都有自己的序列。正如你所提到的,级联原来是问题所在。我们关闭了类型表的级联,对于其他我们使用“合并”级联,这允许我们调用 merge() 而不创建所有这些空行。我已经相应地标记了你的答案,谢谢! 我使用了 merge() 而不是 saveOrUpdate() 和 BOOM!它有效:) @AmirhoseinAl 这与答案有什么关系?就你的问题提出一个新问题并很好地描述它【参考方案2】:

只需将级联设置为 MERGE,就可以了。

【讨论】:

【参考方案3】:

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

【讨论】:

如何获取具有clear() 方法的session_object session.clear() 还会从会话中删除所有其他(非违规)对象。所以这不是一个好的选择。【参考方案4】:

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

【讨论】:

添加@GeneratedValue 注解后,可能需要增加连接池大小。此外,GenerationType.IDENTITY 对我有用,否则,它会抛出“hibernate_sequence 不存在”【参考方案5】:

这意味着您试图在表中保存多行并引用同一个对象。

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

@Id
private Integer id;

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

【讨论】:

不是根据给出的描述提出问题的情况【参考方案6】:

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

<generator class="native"/>

这解决了我的问题。

【讨论】:

【参考方案7】:

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

getHibernateTemplate().flush();

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

【讨论】:

【参考方案8】:

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

【讨论】:

谢谢!这正是我的问题【参考方案9】:

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

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

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

希望能给遇到此问题的其他人提供线索。

【讨论】:

【参考方案10】:

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

【讨论】:

【参考方案11】:

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

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

【讨论】:

【参考方案12】:

尝试将查询代码放在前面。 这解决了我的问题。 例如改变这个:

query1 
query2 - get the error 
update

到这里:

query2
query1
update

【讨论】:

【参考方案13】:

生成相同错误消息的另一种情况,自定义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;

可能会在插入期间导致约束验证(这很容易理解)或“具有相同标识符值的不同对象已与会话关联” - 这种情况不太明显。

【讨论】:

有这个问题,添加 allocationSize = 1 ,修复它,因为 db 中的序列生成器定义为增量 1 并且 hibernate 的 allocationSize 默认为 50(出于某种奇怪的原因)。【参考方案14】:

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

【讨论】:

如果他不是,他不会有这个问题。问题是他有两个具有相同标识符的对象。【参考方案15】:

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

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>

【讨论】:

【参考方案16】:

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

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

【讨论】:

【参考方案17】:

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

【讨论】:

【参考方案18】:

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

【讨论】:

【参考方案19】:

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

例如:用户角色

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;

【讨论】:

【参考方案20】:

除了之前的所有答案之外,如果您为您的类使用值对象未在 VO Transformer 类中设置 id 属性,则可能会在大型项目中解决此问题。

【讨论】:

【参考方案21】:

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

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

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

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

【讨论】:

【参考方案22】:

如果您使用的是弹簧数据,另一种解决方法:

将对entityManager.persist() 的调用替换为对repository.save() 的调用,并且 将对entityManager.query().getResultList()等的调用替换为对repository.findBy...的调用

这样,spring 数据会跟踪对象。它支持多个 get 和 persist 调用。

【讨论】:

【参考方案23】:

而不仅仅是@Id 尝试 @ID @GeneratedValue(策略 = GenerationType.AUTO) 它对我有用

【讨论】:

您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。【参考方案24】:

在我的例子中,我有 OneToOne 关系,在用外键保存一行后,尝试用相同的外键保存另一行会抛出相同的异常。这意味着需求不是OneToOne关系,而应该是ManyToOne。所以我将其更改为 ManyToOne 并开始工作。

【讨论】:

【参考方案25】:

此错误通常是因为您在尝试插入重复数据时违反了唯一类型的列或主键。

【讨论】:

【参考方案26】:

只需提交当前事务。

currentSession.getTransaction().commit();

现在您可以开始另一个事务并对实体执行任何操作

【讨论】:

不要使用这种方法。完成整个工作后应该进行提交,因为事务处理非常昂贵。

以上是关于休眠错误:具有相同标识符值的不同对象已与会话关联的主要内容,如果未能解决你的问题,请参考以下文章

休眠:具有相同标识符值的不同对象已与会话相关联[重复]

Grails NonUniqueObjectException:具有相同标识符值的不同对象已与会话关联

Struts / hibernate NonUniqueObjectException:具有相同标识符值的不同对象已与会话关联

org.hibernate.NonUniqueObjectException:具有相同标识符值的不同对象已与会话关联

具有相同标识符值的不同对象已与保存时的会话错误相关联[重复]

memge和saveOrUpdate的区别