【问题标题】:hibernate insert to a collection causes a delete then all the items in the collection to be inserted again休眠插入到集合会导致删除,然后再次插入集合中的所有项目
【发布时间】:2010-04-15 21:35:52
【问题描述】:

我有很多关系 CohortGroup 和 Employee。每当我将 Employee 插入到 CohortGroup 中时,休眠都会从解析表中删除该组,并再次插入所有成员以及新成员。为什么不直接添加新的?

Group中的注解:

@ManyToMany(cascade = { PERSIST, MERGE, REFRESH })
@JoinTable(name="MYSITE_RES_COHORT_GROUP_STAFF",
joinColumns={@JoinColumn(name="COHORT_GROUPID")},
inverseJoinColumns={@JoinColumn(name="USERID")})
public List<Employee> getMembers(){
  return members;
}

员工的另一面

@ManyToMany(mappedBy="members",cascade = { PERSIST, MERGE, REFRESH } )
public List<CohortGroup> getMemberGroups(){
  return memberGroups;
}

代码片段

Employee emp = edao.findByID(cohortId);
CohortGroup group = cgdao.findByID(Long.decode(groupId));
group.getMembers().add(emp);
cgdao.persist(group);

下面是日志中报的sql

delete from swas.MYSITE_RES_COHORT_GROUP_STAFF where COHORT_GROUPID=?
insert into swas.MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)
insert into swas.MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)
insert into swas.MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)
insert into swas.MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)
insert into swas.MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)
insert into swas.MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)

这种接缝确实效率低下,并导致了一些问题。如果有多个请求将员工添加到组中,那么有些请求会被覆盖。

equals 和 hashCode 之类的接缝可能是造成这种情况的一个原因。下面是这些方法的实现。有任何危险信号吗?

队列组

    @Override
public int hashCode() {
    final int prime = 31;
    int result = getName().hashCode();
    result = prime * result + ((emp == null) ? 0 : emp.hashCode());
    return result;
}
@Override
public boolean equals(Object obj) {
    if (this == obj) {return true;}
    if (!(obj instanceof CohortGroup)) {return false;}
    CohortGroup other = (CohortGroup) obj;
    if(!getName().equals(other.getName())){return false;}
    if (emp == null && other.getOwner() != null) {
        return false;
    } else if (!emp.equals(other.getOwner())) {
        return false;
    }
    return true;
}

员工

       @Override
public boolean equals(Object obj) {
    if (this == obj) {return true;}
    if (obj == null) {return false;}
    if (!(obj instanceof Employee)) {return false;}
    Employee other = (Employee) obj;
    if (EMPLID == null && other.getEMPLID() != null) {
        return false;
    } else if (!EMPLID.equals(other.getEMPLID())) {
        return false;
    }
    return true;
}

   @Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((EMPLID == null) ? 0 : EMPLID.hashCode());
    return result;
}

我向 CohortGroup 添加了一个 addMember 方法,该方法添加到关系的双方:

    public void addMember(Employee emp){
    this.getMembers().add(emp);
    emp.getMemberGroups().add(this);

}

继续感谢所有提供帮助的人。

【问题讨论】:

  • 是的,我也有同样的行为。我刚刚学会了忍受它:-) 当你在外键上设置一个列表/集时会发生这种情况
  • 那么你会怎么做才能忍受它呢?降低用户界面速度以防止它发生。我有一个 ajax 应用程序,可以在用户单击时以最快的速度拍摄这些请求。这会导致插入时出现一些间歇性缺失。
  • 我或多或少有同样的问题...相信! stackoverflow.com/questions/179259

标签: java hibernate orm jpa


【解决方案1】:

我高度怀疑您没有正确覆盖 equalshashCode。错误地覆盖它们可能会导致您遇到的那种行为(因为哈希键被用作映射中的键)。仔细检查您对 equalshashCode 所做的操作。

使用 your 带有良好 equalshashCode 注释的实体,此代码(逻辑上等效):

Session session = HibernateUtil.beginTransaction();
Employee emp = (Employee) session.load(Employee.class, 1L);
CohortGroup group = (CohortGroup) session.load(CohortGroup.class, 1L);
group.getMembers().add(emp);
emp.getMemberGroup().add(group); // set the other side too!!
session.saveOrUpdate(group);
HibernateUtil.commitTransaction();

在我的机器上产生以下输出:

08:10:32.426 [main] DEBUG o.h.e.d.AbstractFlushingEventListener - 处理刷新时间级联 08:10:32.431 [main] DEBUG o.h.e.d.AbstractFlushingEventListener - 脏检查集合 08:10:32.432 [main] DEBUG org.hibernate.engine.CollectionEntry - 收集脏:[com.stackoverflow.q2649145.CohortGroup.members#1] 08:10:32.432 [main] 调试 org.hibernate.engine.CollectionEntry - 集合脏:[com.stackoverflow.q2649145.Employee.memberGroup#1] 08:10:32.443 [main] DEBUG org.hibernate.engine.Collections - 发现集合:[com.stackoverflow.q2649145.CohortGroup.members#1],原为:[com.stackoverflow.q2649145.CohortGroup.members#1](初始化) 08:10:32.448 [main] DEBUG org.hibernate.engine.Collections - 发现集合:[com.stackoverflow.q2649145.Employee.memberGroup#1],原为:[com.stackoverflow.q2649145.Employee.memberGroup#1](未初始化) 08:10:32.460 [main] DEBUG o.h.e.d.AbstractFlushingEventListener - 刷新:0 次插入,0 次更新,0 次删除到 2 个对象 08:10:32.461 [main] DEBUG o.h.e.d.AbstractFlushingEventListener - Flushed: 0 (re)creations, 2 update, 0 removes to 2 collections 08:10:32.463 [main] DEBUG org.hibernate.pretty.Printer - 列出实体: 08:10:32.473 [main] 调试 org.hibernate.pretty.Printer - com.stackoverflow.q2649145.CohortGroup{id=1, members=[com.stackoverflow.q2649145.Employee#1]} 08:10:32.474 [main] 调试 org.hibernate.pretty.Printer - com.stackoverflow.q2649145.Employee{id=1, memberGroup=} 08:10:32.474 [main] 调试 o.h.p.c.AbstractCollectionPersister - 插入集合:[com.stackoverflow.q2649145.CohortGroup.members#1] 08:10:32.480 [main] DEBUG org.hibernate.jdbc.AbstractBatcher - 即将打开 PreparedStatement(打开 PreparedStatements:0,全局:0) 08:10:32.491 [main] DEBUG org.hibernate.SQL - 插入 MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) 值 (?, ?) 休眠:插入 MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) 值 (?, ?) 08:10:32.496 [main] TRACE org.hibernate.type.LongType - 将“1”绑定到参数:1 08:10:32.497 [main] TRACE org.hibernate.type.LongType - 将“1”绑定到参数:2 08:10:32.499 [main] DEBUG o.h.p.c.AbstractCollectionPersister - 完成插入集合:插入 1 行

插入前不删除!

顺便提一下,在处理双向关联时,您应该在双方上设置链接,就像我在组员工上所做的那样。

或者在你的类中添加防御性链接管理方法,例如在CohortGroup

public void addToMembers(Employee emp) {
    this.getMembers().add(emp);
    emp.getMemberGroup().add(this);
}

public void removeFromMembers(Employee emp) {
    this.getMembers().remove(emp);
    emp.getMemberGroup().remove(this);
}

【讨论】:

  • 我会仔细检查哈希码和等号。它们已实现,我只是让 Eclipse 为我创建它们。如果这不能解决问题,我将开始您的其他建议。为了繁荣,我会发布我的结果。
  • 我很确定我已经正确实现了哈希码和 equals,但行为仍然存在。第一次将组和员工添加到其中时,不会删除。如果组中已经有超过一名员工,就会发生这种情况。不确定您的测试是否将员工添加到已经有员工的组中。
  • 帕斯卡,感谢您的帮助。如果我想显示 hashcode 和 equals 方法,我会在哪里发布?作为此问题的答案或编辑上述问题?
  • @Mark 最好编辑你的问题。用答案来回答。
  • 我在 equals 和 hashCode 方法中放了一个 system.out。执行插入时不会打印任何内容。这是否意味着我找错地方了?
【解决方案2】:

我遇到了同样的问题,经过反复试验发现,如果我使用 Set 而不是 List 作为我的集合,则不会发生删除。烦人,因为我使用的是 JSF 并且 UI 组件只会遍历列表。但它就在那里。

【讨论】:

  • 这是问题的根源。阅读assarconsulting.blogspot.com/2009/08/…了解更多详情。
  • -1 因为我在使用 Sets 时遇到了同样的问题。您的回答将帮助任何在一个休眠会话中维护实体集的人,因此他们可以依赖标准的 java 身份(==)。然而,在两个休眠会话之间,集合将显示与列表相同的问题。问题归结为正确实现equals和hashCode。
【解决方案3】:

正如其他人所建议的,这可能是hashcodeequals 的问题。

具体来说:Hibernate 代理会导致您在 equals 方法中使用的 instaceof 出现问题。这意味着坏消息。

检查一下: http://community.jboss.org/wiki/ProxyVisitorPattern

【讨论】:

  • 会改变 equals 方法来比较类 getName (this.getClass().getName().equals(other.getClass().getName()) 解决问题,或者是否暗示更改为使用访问者模式来检索这些列表?
  • 不改变方法是行不通的。因为代理类有有趣的名字。您可以做的一件事是添加一个类似public Class getThisClass() { return this.getClass(); } 的方法。当您在代理上调用getClass 时,您将获得代理的类。当您致电getThisClass 时,它代表您的“真实”班级。与访问者相比,这是一种黑客行为。
  • 在调用它之前我不需要添加一些检查以确保该方法存在吗? equals 执行 instanceof 的原因是确保强制转换传入的对象是安全的。所以我要么不明白 getThisClass 在从 equals 方法调用时如何工作,要么访问者选项仍然是探索的最佳候选者。
  • 我使用的对象不是任何东西的子类。它们确实实现了一些接口,但它们没有扩展任何东西。如果我理解我正在阅读的内容,那么这可能不是原因“在没有对象层次结构的简单情况下非常好。在这种情况下,类型转换和 instanceof 在代理上完美地工作,因为它是一个直接子类。”
【解决方案4】:

我有同样的问题。我从列表更改为设置。它对我有用。

@ManyToMany(cascade = { PERSIST, MERGE, REFRESH })
@JoinTable(name="MYSITE_RES_COHORT_GROUP_STAFF",
joinColumns={@JoinColumn(name="COHORT_GROUPID")},
inverseJoinColumns={@JoinColumn(name="USERID")})
public Set<Employee> getMembers(){
  return members;
}

@ManyToMany(mappedBy="members",cascade = { PERSIST, MERGE, REFRESH } )
public Set<CohortGroup> getMemberGroups(){
  return memberGroups;
}

【讨论】:

    【解决方案5】:

    您必须在 CohortGroupEmployee 实体上定义 hashCode()equals()。这可以由您的 IDE 自动完成,它可以在主键上(有时不是一个好主意)或在业务键上(首选)。

    阅读this article

    【讨论】:

    • 感谢文章的链接,它有帮助
    【解决方案6】:

    我的插件按照我现在期望的方式运行。感谢 Pascal 和 z5h,我学到了很多东西。我相信我已经正确实现了 hashCode 和 equals 。不过,这从来没有为我解决问题。相反,我实现了一个中间实体。

    下面是我的 Employee、CohortGroup 和现在的 CohortGroupMemeber 类中的映射。

    员工:

    @OneToMany(mappedBy="member")
    public List<CohortGroupMember> getMemberGroups(){
       return memberGroups;
    }
    public void setMemberGroups(List<CohortGroupMember> grps){
       memberGroups = grps;
    }
    

    群组成员

    @ManyToOne
    @JoinColumn(name="USERID")
    public Employee getMember(){
        return emp;
    }
    public void setMember(Employee e){
        emp = e;
    }
    @ManyToOne
    @JoinColumn(name="COHORT_GROUPID")
    public CohortGroup getGroup(){
        return group;
    }
    public void setGroup(CohortGroup cg){
        group   = cg;
    }
    

    队列组

    @OneToMany(mappedBy="group")
    public List<CohortGroupMember> getMembers(){
        return members;
    }
    public void setMembers(List<CohortGroupMember> emps){
        members = emps;
    }
    

    为此我遵循的书是 Java Persistence with Hibernate 第 7.2.3 章

    【讨论】:

    • 虽然这是一个解决方案。更好的解决方案是使用 Set 而不是 List。请参阅我链接到 Marc 答案的文章,了解原因。
    • @JustinKSU 谢谢伙计,我真的很感谢那篇很棒的文章!!!你总能学到一些关于休眠的新知识:)
    【解决方案7】:

    在持久化上下文中加载对象,如果存在则调用合并,如果不存在则保存。

    Employee emp = new Employee();
    //set emp attribute and members.
    Session session = ...
    Transaction transaction = ...
    Employee db = (Employee) session.get(Employee .class,emp.getId());
    if(db != null)  session.merge(emp);
    else  session.save(emp);
    /*session.saveOrUpdate(emp); This will delete the join table and reinsert entry  
    again*/
    transaction.commit();
    session.close();
    

    【讨论】:

      猜你喜欢
      • 2016-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-11
      • 2011-02-02
      • 1970-01-01
      相关资源
      最近更新 更多