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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3742897/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 04:13:52  来源:igfitidea点击:

Hibernate - @ElementCollection - Strange delete/insert behavior

javahibernateormjpajpa-2.0

提问by nihilist84

@Entity
public class Person {

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

    [...]

}

@Embeddable
public class Location {

    [...]

}

Given the following class structure, when I try to add a new location to the list of Person's Locations, it always results in the following SQL queries:

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

DELETE FROM PERSON_LOCATIONS WHERE PERSON_ID = :idOfPerson

And

A lotsa' inserts into the PERSON_LOCATIONS table

Hibernate (3.5.x / JPA 2) deletes all associated records for the given Person and re-inserts all previous records, plus the new one.

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

I had the idea that the equals/hashcode method on Location would solve the problem, but it didn't change anything.

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

Any hints are appreciated!

任何提示表示赞赏!

采纳答案by Pascal Thivent

The problem is somehow explained in the page about ElementCollectionof the JPA wikibook:

ElementCollection在 JPA wikibook 的about 页面中以某种方式解释了这个问题:

Primary keys in CollectionTable

The JPA 2.0 specification does not provide a way to define the Idin the Embeddable. However, to delete or update a element of the ElementCollectionmapping, some unique key is normally required. Otherwise, on every update the JPA provider would need to delete everything from the CollectionTablefor the Entity, and then insert the values back.So, the JPA provider will most likely assume that the combination of all of the fields in the Embeddableare unique, in combination with the foreign key (JoinColunm(s)). This however could be inefficient, or just not feasible if the Embeddableis big, or complex.

CollectionTable 中的主键

JPA 2.0 规范没有提供IdEmbeddable. 但是,要删除或更新ElementCollection映射的元素, 通常需要一些唯一键。否则,在每次更新时,JPA 提供程序都需要从CollectionTablefor 中删除所有内容 Entity,然后将值插入回来。因此,JPA 提供者很可能会假设 中所有字段的组合Embeddable是唯一的,并结合外键 ( JoinColunm(s))。然而,如果Embeddable它很大或很复杂,这可能效率低下,或者只是不可行。

And this is exactly (the part in bold) what happens here (Hibernate doesn't generate a primary key for the collection table and has no way to detect what elementof the collection changed and will delete the old content from the table to insert the new content).

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

However, ifyou define an @OrderColumn(to specify a column used to maintain the persistent order of a list - which would make sense since you're using a List), Hibernate will create a primary key(made of the order column and the join column) and will be able to update the collection table without deleting the whole content.

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

Something like this (if you want to use the default column name):

像这样(如果你想使用默认的列名):

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

References

参考

  • JPA 2.0 规范
    • 第 11.1.12 节“ElementCollection 注释”
    • 第 11.1.39 节“OrderColumn 注释”
  • JPA 维基书

回答by Vlad Mihalcea

In addition to Pascal's answer, you have to also set at least one column as NOT NULL:

除了 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;
    }
}

This requirement is documented in AbstractPersistentCollection:

此要求记录在AbstractPersistentCollection 中

Workaround for situations like HHH-7072. If the collection element is a component that consists entirely of nullable properties, we currently have to forcefully recreate the entire collection. See the use of hasNotNullableColumns in the AbstractCollectionPersister constructor for more info. In order to delete row-by-row, that would require SQL like "WHERE ( COL = ? OR ( COL is null AND ? is null ) )", rather than the current "WHERE COL = ?" (fails for null for most DBs). Note that the param would have to be bound twice. Until we eventually add "parameter bind points" concepts to the AST in ORM 5+, handling this type of condition is either extremely difficult or impossible. Forcing recreation isn't ideal, but not really any other option in ORM 4.

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

回答by Mustafakidd

We discovered that entities we were defining as our ElementCollection types did not have an equalsor hashcodemethod defined and had nullable fields. We provided those (via @lombok for what it's worth) on the entity type and it allowed hibernate (v 5.2.14) to identify that the collection was or was not dirty.

我们发现我们定义为 ElementCollection 类型的实体没有定义equalsorhashcode方法并且具有可为空的字段。我们在实体类型上提供了那些(通过@lombok 获取它的价值),它允许休眠(v 5.2.14)识别集合是否脏。

Additionally, this error manifested for us because we were within a service method that was marked with the annotation @Transaction(readonly = true). Since hibernate would attempt to clear the related element collection and insert it all over again, the transaction would fail when being flushed and things were breaking with this very difficult to trace message:

此外,这个错误对我们来说是显而易见的,因为我们在一个用注解标记的服务方法中@Transaction(readonly = true)。由于 hibernate 会尝试清除相关的元素集合并重新插入它,因此事务在刷新时会失败,并且事情会因这个非常难以跟踪的消息而中断:

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

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

Here is an example of our entity model that had the error

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

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

public class Entity2 {
  private UUID someUUID;
}

Changing it to this

改成这个

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

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

Fixed our issue. Good luck.

修复了我们的问题。祝你好运。

回答by tomasulo

I had the same issue but wanted to map a list of enums: List<EnumType>.

我有同样的问题,但想映射一个枚举列表:List<EnumType>.

I got it working like this:

我让它像这样工作:

@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);
}

The issue with me was that the Listobject was always replaced using the default setter and therefore hibernate treated it as a completely "new" object although the enums did not change.

我的问题是该List对象总是使用默认的 setter 替换,因此 hibernate 将它视为一个全新的“新”对象,尽管枚举没有改变。