【问题标题】:Hibernate - @ElementCollection - Strange delete/insert behaviorHibernate - @ElementCollection - 奇怪的删除/插入行为
【发布时间】: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 似乎没有效果

标签: java hibernate orm jpa jpa-2.0


【解决方案1】:

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

Primary keys in 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;
    ...
}

参考文献

【讨论】:

  • 任何想法如何使用休眠 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 类型的实体没有定义 equals 或 hashcode 方法并且具有可为空的字段。我们在实体类型上提供了这些(通过@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 对象总是使用默认设置器替换,因此休眠将它视为一个完全“新”的对象,尽管枚举没有改变。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-04
      • 1970-01-01
      • 1970-01-01
      • 2016-07-15
      • 1970-01-01
      相关资源
      最近更新 更多