2008-10-07 56 views
20

在我的应用我有这方面的Hibernate映射类型(一般情况):当我尝试更新/插入时,为什么Hibernate尝试删除?

class RoleRule { 
    private Role role; 
    private PermissionAwareEntity entity; // hibernate-mapped entity for which permission is granted 
    private PermissionType permissionType; // enum 

    @ManyToOne 
    @JoinColumn(name = "ROLE_ID") 
    public Role getRole() { 
    return role; 
    } 
    public void setRole(Role role) { 
    this.role = role; 
    } 

} 

class Role { 
    private Set<RoleRule> rules = new HashSet<RoleRule>(0); 

    @OneToMany(cascade=CascadeType.ALL) 
    @JoinColumn(name="ROLE_ID") 
    public Set<RoleRule> getRules() { 
    return rules; 
    } 
    public void setRules(Set<RoleRule> rules) { 
    this.rules = rules; 
    } 

} 

类都equals() & hashCode()覆盖。

我的应用程序允许调整角色(仅由系统管理员操作,不用担心)以及其他字段允许创建新的角色规则。创建新规则时,我尝试创建一个新的RoleRule对象,并将其插入角色的字段rules。我打电话session.update(role)将更改应用到数据库。

现在到了最丑陋的部分... Hibernate的决定做关闭交易和冲洗时注意以下事项

  1. 将新规则到数据库中。优秀。
  2. 更新其他角色字段(不是集合)。到现在为止还挺好。
  3. 更新现有规则,即使其中没​​有任何更改。我可以忍受这一点。
  4. 再次更新现有规则。这里有一个膏会从日志,包括自动评论:
/* delete one-to-many row Role.rules */ 
update ROLE_RULE set ROLE_ID=null where ROLE_ID=? and ROLE_RULE_ID=?

当然,所有的字段不为空,而这个操作壮观失败。

任何人都可以尝试解释为什么Hibernate会这样做吗?更重要的是,我怎样才能解决这个问题?

编辑:我很肯​​定这是什么做的映射,然后我的老板,一时兴起,删除了这两个类的equals()hashCode(),使用Eclipse重建他们,神不知鬼不觉这个问题解决了。

虽然我仍然很好奇我的问题。任何人都可以提出为什么Hibernate会这样做?

+0

您使用哪种驱动程序和方言? – 2008-10-07 16:20:08

+0

该应用程序在Oracle 10g上运行,但这并不重要。 – Yuval 2008-10-07 17:18:09

+0

最重要的是,您的帖子中缺少`equals`和`hashCode`方法的实现。根据由hibernate或数据库生成的数据库标识值**构建“equals”和“hashCode”方法是一个坏主意,这使得它们依赖于任何可以在构建时间更糟后更新的字段。 – 2016-03-01 00:41:52

回答

3

我不知道,如果这是解决方案,但你可能也想尝试:

@OneToMany(mappedBy = "role") 

而且没有@JoinColumn注解?我认为这两个实体都试图“拥有”这个关联,这就是为什么SQL可能会混乱的原因?

另外,如果你想确保只有受影响的列得到更新,您可以使用特定于Hibernate的注解放在类:

@Entity 
@org.hibernate.annotations.Entity(
    dynamicInsert = true, dynamicUpdate = true 
) 
7

我通常使用更新的集合的两种方法(很多Hibernate中的一对多)。蛮力的方法是清除集合,调用保存在父级上,然后调用flush。然后添加所有收集成员并再次调用保存在父项上。这将删除所有,然后插入全部。中间的冲洗是关键,因为它会在插入之前强制删除。最好只在小集合上使用这种方法,因为它重新插入所有这些方法。

第二种方式更难编码,但效率更高。你循环遍历新的一组孩子并手动修改那些仍然存在的孩子,删除那些没有的孩子,然后添加新的孩子。在伪代码,这将是:

copy the list of existing records to a list_to_delete 
for each record from the form 
    remove it from the list_to_delete 
    if the record exists (based on equals()? key?) 
    change each field that the user can enter 
    else if the record doesn't exist 
    add it to the collection 
end for 
for each list_to_delete 
    remove it 
end for 
save 

我搜索了几个小时的休眠论坛,试图找到解决这个问题的正确方法。你应该能够更新你的集合来保证它的准确性,然后保存父对象,但正如你发现的那样,Hibernate会在删除它们之前尝试从父对象中分离子对象,如果外键不为null,它会将失败。

4

查看问题'Overriding equals and hashCode in Java'的回答。

它解释了如何重写equals和hashCode方法,这些方法在重写之后似乎是您的问题。

错误地覆盖它们会导致hibernate删除你的集合并重新插入它们。 (因为散列键被用作地图中的键)

0

Brian Deterling的回答帮助我克服了幻影删除。我希望他已经写了一个真实的代码。这是我从他的建议中得到的结果1.发布某人使用它或评论我的代码。

// snFile and task share many to many relationship 

@PersistenceContext 
private EntityManager em; 

public SnFile merge(SnFile snFile) { 
     log.debug("Request to merge SnFile : {}", snFile); 

     Set<Task> tasks = taskService.findBySnFilesId(snFile.getId()); 
     if(snFile.getTasks() != null) { 
      snFile.getTasks().clear(); 
     } 
     em.merge(snFile); 
     em.flush(); 
     if(tasks != null) { 
      if(snFile.getTasks() != null) 
       snFile.getTasks().addAll(tasks); 
      else 
       snFile.setTasks(tasks); 
     } 

     return em.merge(snFile); 
    } 
相关问题