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

Posted

技术标签:

【中文标题】Hibernate - @ElementCollection - 奇怪的删除/插入行为【英文标题】:Hibernate - @ElementCollection - Strange delete/insert behavior 【发布时间】:2011-04-14 03:53:03 【问题描述】:
@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) 删除给定 Person 的所有关联记录并重新插入所有以前的记录以及新记录。

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

感谢任何提示!

【问题讨论】:

我遇到了相反的问题 - 当我更新父级时,Hibernate 没有删除任何内容。这是我在网上找到的唯一相关帖子,包括 JBoss 的 Hibernate 文档。结果是由于现在未知的原因,我将父级上的集合变量声明为final,而 Hibernate 默默地什么也没做。我拿出了final,它开始很好地更新和删除。 我发现fetch = FetchType.EAGER 在简单的列表操作中也会发生这种情况(不是更新或删除)l。 @OrderColumn 似乎没有效果 【参考方案1】:

JPA wikibook 关于ElementCollection 的页面以某种方式解释了该问题:

Primary keys in CollectionTable

JPA 2.0 规范没有 提供一种方法来定义Id Embeddable但是,删除或 更新一个元素 ElementCollection映射,有些独特 密钥通常是必需的。否则, 在每次更新时,JPA 提供者都会 需要从 CollectionTableEntity,和 然后将值插入回去。因此, 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;
    ...

参考文献

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

【讨论】:

任何想法如何使用休眠 xml 映射创建这样一个初级密钥? 我发现了相同的“全部删除”、“插入第 1 行”、“插入第 2 行”... sql 行为。我正在使用 OpenJPA2.2.2 并将 @Id 注释插入可嵌入的子实体并没有帮助。 OrderColumn 需要一个新的整数 sortorder db 列,并且我的 DB 模式不是由这个 JPA 应用程序维护的,所以它可能不是一个选项。 这也适用于地图吗?使用 MapKey(Join)Column 等。JPA 总是为我做删除。7 有谁知道这个解决方案是否适用于基本收藏? (例如,如果该字段是 List 而不是 List,它会起作用)还是仅适用于 Embeddables? @molinab297 是的,它确实适用于基本系列。上述解决方案的问题是,如果删除列表的第一个元素,所有元素的索引都会更新(向左移动一个位置)【参考方案2】:

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

【讨论】:

天哪,谢谢伙计!我在 @ElementCollection@OrderColumn@Embeddable@UniqueConstraints 一起使用时苦苦挣扎了好几个小时。在删除或更改java.util.List 的顺序时,我违反了唯一约束,因为休眠而不是删除整个集合试图进行更新。将标记为@Embeddable 的类的字段更改为可以为空就可以了! 看起来在最新的 Hibernate (5.2.3) 中,列是否可以为空不再相关。更新似乎每次都会执行,这实际上对我的应用程序来说是不幸的,因为我需要删除所有集合元素然后替换它们,因为我有一个 @UniqueConstraints,在执行更新时违反了它。考虑使用@OrderColumn 移动到@OneToMany,并将这个唯一约束定义为主键......好吧......,如果可能的话,让我们看看。 如果您认为这是一个回归问题,您可以添加 Jira 问题【参考方案3】:

我们发现我们定义为 ElementCollection 类型的实体没有定义 equalshashcode 方法并且具有可为空的字段。我们在实体类型上提供了这些(通过@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;

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

【讨论】:

【参考方案4】:

我遇到了同样的问题,但想映射一个枚举列表:List&lt;EnumType&gt;

我是这样工作的:

@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 对象总是使用默认设置器替换,因此休眠将它视为一个完全“新”的对象,尽管枚举没有改变。

【讨论】:

以上是关于Hibernate - @ElementCollection - 奇怪的删除/插入行为的主要内容,如果未能解决你的问题,请参考以下文章

hibernate问题

Spring和Hibernate的注解整合 hibernate3和hibernate4/5的区别

hibernate.merge()方法怎么用

hibernate 异常 怎么解决

Hibernate之Hibernate环境配置

(转)Hibernate框架基础——Hibernate API及Hibernate主配置文件