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

时间:2010-09-18 18:20:33

标签: java hibernate orm jpa jpa-2.0

@Entity
public class Person {

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

    [...]

}

@Embeddable
public class Location {

    [...]

}

鉴于以下类结构,当我尝试将新位置添加到Person的位置列表时,它总是会导致以下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方法可以解决问题,但它没有改变任何东西。

任何提示都表示赞赏!

4 个答案:

答案 0 :(得分:57)

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

  

Primary keys in CollectionTable

     

JPA 2.0规范没有   提供一种定义Id的方法   Embeddable然而,要删除或   更新一个元素   ElementCollection映射,有些独特   通常需要密钥。除此以外,   在每次更新JPA提供程序   需要从中删除所有内容   CollectionTable的{​​{1}}和   然后插回值。那么,   JPA提供商很可能会假设   所有的组合   Entity中的字段是唯一的,   与外键结合使用   (Embeddable(S))。然而,这可能是   效率低下,或者只是不可行   JoinColunm很大或很复杂。

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

但是,如果您定义了Embeddable(指定用于维护列表持久顺序的列 - 这是有意义的,因为您使用的是{{1} }),Hibernate将创建一个主键(由订单列和连接列组成),并且能够更新收集表而不删除整个内容。< / p>

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

@OrderColumn

参考

答案 1 :(得分:7)

除了Pascal的回答,您还必须set at least one column as 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。为了删除   逐行,这将需要SQL像&#34; WHERE(COL =?OR(COL为空,?是空))&#34;而不是   当前&#34; WHERE COL =?&#34; (对于大多数DB而言,对于null失败)。注意   该参数必须被绑定两次。直到我们最终添加&#34;参数绑定点&#34;概念到了   ORM 5+中的AST,处理这种情况要么极其困难,要么不可能。强制   娱乐并不是理想的,但在ORM 4中并不是真正的其他选择。

答案 2 :(得分:1)

我们发现,我们定义为ElementCollection类型的实体没有定义equalshashcode方法,并且具有可空字段。我们通过实体类型提供了这些值(通过@lombok进行了说明),它使休眠状态(v。5.2.14)能够识别出集合是否脏了。

此外,此错误对我们而言也很明显,因为我们处于使用注释@Transaction(readonly = true)标记的服务方法中。由于休眠将尝试清除相关的元素集合并再次将其重新插入,因此在刷新时事务将失败,并且由于很难跟踪消息而使事情中断:

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

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

答案 3 :(得分:0)

我遇到了同样的问题,但想映射一个枚举列表: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替换,因此尽管枚举没有更改,休眠状态将其视为完全“新的”对象。