@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 方法可以解决问题,但它没有改变任何东西。
任何提示表示赞赏!
JPA wikibook 的关于
ElementCollection
的页面以某种方式解释了这个问题:
CollectionTable 中的主键
JPA 2.0 规范没有 提供一种方法来定义
Id
。 但是,要删除或 更新一个元素Embeddable
映射,一些独特的 通常需要密钥。否则, 每次更新时,JPA 提供商都会 需要删除所有内容ElementCollection
为CollectionTable
,以及 然后将值插入回去。 因此, JPA 提供商很可能会假设 所有的组合Entity
中的字段是唯一的, 与外键结合 (Embeddable
(s))。然而这可能是 效率低下,或者根本不可行,如果JoinColunm
很大,或者很复杂。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;
...
}
除了 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 中确实没有其他选择。
我们发现我们定义为 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;
}
解决了我们的问题。祝你好运。
我有同样的问题,但想映射枚举列表:
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 将其视为完全“新”的对象,尽管枚举没有改变。
如上所述,覆盖 equals 和 hashcode 对我有用