Hibernate - @ElementCollection - 奇怪的删除/插入行为

问题描述 投票:0回答:5
@Entity
public class Person {

    @ElementCollection
    @CollectionTable(name = "PERSON_LOCATIONS", joinColumns = @JoinColumn(name = "PERSON_ID"))
    private List<Location> locations;

    [...]

}

@Embeddable
public class Location {

    [...]

}

鉴于以下类结构,当我尝试将新位置添加到人员位置列表中时,它总是会导致以下 SQL 查询:

DELETE FROM PERSON_LOCATIONS WHERE PERSON_ID = :idOfPerson

还有

A lotsa' inserts into the PERSON_LOCATIONS table

Hibernate (3.5.x / JPA 2) 删除给定人员的所有关联记录,并重新插入所有以前的记录以及新记录。

我认为 Location 上的 equals/hashcode 方法可以解决问题,但它没有改变任何东西。

任何提示表示赞赏!

java hibernate orm jpa jpa-2.0
5个回答
73
投票

JPA wikibook 的关于

ElementCollection
的页面以某种方式解释了这个问题:

CollectionTable 中的主键

JPA 2.0 规范没有 提供一种方法来定义

Id
Embeddable
但是,要删除或 更新一个元素
ElementCollection
映射,一些独特的 通常需要密钥。否则, 每次更新时,JPA 提供商都会 需要删除所有内容
CollectionTable
Entity
,以及 然后将值插入回去。
因此, JPA 提供商很可能会假设 所有的组合
Embeddable
中的字段是唯一的, 与外键结合 (
JoinColunm
(s))。然而这可能是 效率低下,或者根本不可行,如果
Embeddable
很大,或者很复杂。

这正是(粗体部分)这里发生的情况(Hibernate 不会为集合表生成主键,并且无法检测集合的什么元素发生了更改,并将从表中删除旧内容插入新内容)。

但是,如果您定义了一个

@OrderColumn
(指定用于维护列表的持久顺序的列 - 这将是有意义的,因为您使用的是
List
),Hibernate 将创建一个主键 (由 order 列和 join 列组成)并且能够在不删除整个内容的情况下更新集合表。

类似这样的东西(如果您想使用默认列名称):

@Entity
public class Person {
    ...
    @ElementCollection
    @CollectionTable(name = "PERSON_LOCATIONS", joinColumns = @JoinColumn(name = "PERSON_ID"))
    @OrderColumn
    private List<Location> locations;
    ...
}

参考文献


11
投票

除了 Pascal 的答案之外,您还必须将至少一列设置为 NOT NULL:

@Embeddable
public class Location {

    @Column(name = "path", nullable = false)
    private String path;

    @Column(name = "parent", nullable = false)
    private String parent;

    public Location() {
    }

    public Location(String path, String parent) {
        this.path = path;
        this.parent= parent;
    }

    public String getPath() {
        return path;
    }

    public String getParent() {
        return parent;
    }
}

此要求记录在 AbstractPersistentCollection:

HHH-7072 等情况的解决方法。 如果集合元素是一个完全由 对于可为 null 的属性,我们目前必须强制重新创建整个集合。 查看用途 有关详细信息,请参阅 AbstractCollectionPersister 构造函数中的 hasNotNullableColumns。 为了删除 逐行,这需要像“WHERE ( COL = ? OR ( COL is null AND ? is null ) )”这样的 SQL,而不是 当前的“WHERE COL = ?” (对于大多数数据库来说,null 会失败)。 注意 参数必须绑定两次。 直到我们最终将“参数绑定点”概念添加到 ORM 5+ 中的 AST,处理这种类型的情况要么极其困难,要么不可能。 强迫 娱乐并不理想,但 ORM 4 中确实没有其他选择。


3
投票

我们发现我们定义为 ElementCollection 类型的实体没有定义

equals
hashcode
方法并且具有可为 null 的字段。我们在实体类型上提供了这些(通过 @lombok 了解它的价值),它允许 hibernate(v 5.2.14)识别集合是否脏。

此外,我们之所以出现此错误,是因为我们处于标有注释

@Transaction(readonly = true)
的服务方法内。由于 hibernate 会尝试清除相关元素集合并重新插入,因此事务在刷新时会失败,并且会因这条很难跟踪的消息而中断:

HHH000346: Error during managed flush [Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1]

这是我们的实体模型出现错误的示例

@Entity
public class Entity1 {
@ElementCollection @Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}

public class Entity2 {
  private UUID someUUID;
}

改成这样

@Entity
public class Entity1 {
@ElementCollection @Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}

@EqualsAndHashCode
public class Entity2 {
  @Column(nullable = false)
  private UUID someUUID;
}

解决了我们的问题。祝你好运。


2
投票

我有同样的问题,但想映射枚举列表:

List<EnumType>

我的工作是这样的:

@ElementCollection
@CollectionTable(
        name = "enum_table",
        joinColumns = @JoinColumn(name = "some_id")
)
@OrderColumn
@Enumerated(EnumType.STRING)
private List<EnumType> enumTypeList = new ArrayList<>();

public void setEnumList(List<EnumType> newEnumList) {
    this.enumTypeList.clear();
    this.enumTypeList.addAll(newEnumList);
}

我的问题是

List
对象总是使用默认的 setter 进行替换,因此 hibernate 将其视为完全“新”的对象,尽管枚举没有改变。


0
投票

如上所述,覆盖 equals 和 hashcode 对我有用

© www.soinside.com 2019 - 2024. All rights reserved.