0

我想覆盖等于基于业务密钥而不是对象身份,但我不能让它与休眠一起工作。

我知道有人问过类似的问题,阅读答案(像这样)有助于理解不破坏等价关系,使用 instanceof 代替 getClass,使用 getter 代替字段引用。等等,但我的 equals/hashCode 实现仍然会破坏地图的休眠人口。

我没有收到错误,休眠将所有 3 个实体正确保存在数据库中的地图中,但是当我再次使用地图读取实体时,它只读取 1 而不是全部 3。hibernate 记录的 sql 在 mysql 中执行时选择正确的所有 3 个实体。

该项目基于 Spring 3.2.1、Hibernate 4.1.5 和 JPA 注释

更新

在调试休眠时,它似乎为 3 个映射实体中的每一个调用 hashCode,但除了休眠生成的 id 之外,所有值都是空的。因此,由覆盖方法计算的 hashCode 始终相同。我想这就是为什么只有一个实体被添加到地图中的原因。但我不明白为什么hibernate在从数据库加载数据之前计算hashCode?

例子

在下面的示例中,我在具有 3 个简单实体的项目中重现了错误(所有实体都具有休眠生成的 id):

  • 物品
  • 语言
  • 语言详情

Item 有一个 LanguageDetails 的 Map,其中 Language 是关键:

@OneToMany(mappedBy="item", orphanRemoval=true, cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@MapKey(name="language")
private Map<Language, LanguageDetails> languageDetails = new TreeMap<Language, LanguageDetails>();

Language 中的业务键是一个名为code的字符串。我对覆盖equals的最佳猜测是使用eclipse生成equals/hashCode(并用instanceof替换getClass,用getter替换字段引用):

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

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

以下集成测试在上面的 equals/hashCode 下失败,在没有以下情况下成功:

Item item = new Item();
Language english = languageService.getByCode("en");
Language german = languageService.getByCode("de");
Language spanish = languageService.getByCode("es");

item.getLanguageDetails().put(english, new LanguageDetails(item, english, "ice cream"););
item.getLanguageDetails().put(spanish, new LanguageDetails(item, spanish, "helado"););
item.getLanguageDetails().put(german, new LanguageDetails(item, german, "eis"););

//save - stores all 3 languageDetails correct in mysql
int id = itemService.create(item); 

//read - loads only 1 languageDetails (de)
Item readItem = itemService.getById(id); 

//java.lang.AssertionError: expected:<3> but was:<1>
Assert.assertEquals(item.getLanguageDetails().size(), readItem.getLanguageDetails().size());

我究竟做错了什么?

4

1 回答 1

1

我花了将近一周的时间进行调试、编写解决方法并试图理解这个问题,在我越来越绝望的试错尝试中,我找到了解决方案:

我将休眠版本从 4.1.5 更改为 4.1.8,我所有的痛苦都消失了。这是休眠中的一个错误,已在版本4.1.7中修复...

下次我一定会更早检查错误报告...

于 2013-02-02T23:37:32.117 回答