当现有值为 null 时,为什么 Hibernate 执行 INSERT 而不是 UPDATE?

use*_*038 5 java hibernate jpa

问题

在合并我的 Hibernate 实体之一时,我观察到了约束违规。有两个表 A 和 B,其中一个保存元数据,另一个是字符串值的映射。

在表 B 中,(id, key) 上有一个组合主键。

现在,当我合并 A 的现有实例(该实例也有 B 的多个条目)时,Hibernate 将为 B 执行 UPDATE 和 INSERT 语句。

UPDATE 语句没有问题,但无法执行 INSERT 语句,因为它会导致约束冲突,因为 (id, key) 是唯一组合。

我发现只有当 B 中的现有值为 时,才会出现此问题null

我使用的是 Oracle 11、JPA 2.1 和 EJB 3.2。不过,并不完全确定如何确定 Hibernate 版本。

一个例子

让我们假设这些是表 B 的条目:

23, fooKey, foo
23, barKey, bar
23, errorKey, null
Run Code Online (Sandbox Code Playgroud)

现在,当我合并 A (id = 23) 时,将为fooKey和执行 UPDATE 语句barKey。然而,对于errorKey,Hibernate 将改为发出 INSERT 语句并导致约束违规。

所以我的问题是:

  1. 当现有值为 时,为什么 Hibernate 执行 INSERT 语句null
  2. 我该如何解决这个问题?

我的数据库

表 A( id、版本、最后更新

表 B( id、键、值

我的实体

@Entity
public class A {
  @Id
  public long id;
  public long version = 0;
  public Date lastUpdate = new Date();

  @ElementCollection(fetch = FetchType.EAGER)
  @CollectionTable(name = "B", joinColumns = @JoinColumn(name = "ID"))
  @MapKeyColumn(name = "KEY")
  @Column(name = "VALUE", length = 2000)
  public Map<String, String> myMapping = new HashMap<String, String>();

  // Setters and getters...
}
Run Code Online (Sandbox Code Playgroud)

ris*_*kop 2

答案 1:从 Hibernate 的角度来看,集合中的 NULL 值元素不存在。因此,当您将元素“更新”为非空值时,Hibernate 会执行 INSERT 语句。

答案 2:使用 Hibernate 时应避免集合中出现空值(或包装它们),使用 Oracle 时应避免空格(见下文)。

我用Oracle重现了你的问题。首先,我存储一个“”(空字符串)值元素,然后将其更新为任何其他字符串。我使用 log4jdbc 来检查 Hibernate 发出的实际语句。

这是演示(使用 Oracle 会失败,使用 H2 则可以):

http://peter.risko.hu/java_incubator/jpa_hibernate_collectionBehaviour_null_empty_string_h2_oracle_log4jdbc.zip

最初的坚持:

A a = new A();
a.setId(1);
a.setLastUpdate(new Date());
a.setVersion(1);

Map<String, String> myMap = new HashMap<>();
myMap.put("b", "");
a.setMyMapping(myMap);

em.getTransaction().begin();
em.persist(a);
em.getTransaction().commit();
em.clear();
Run Code Online (Sandbox Code Playgroud)

这是初始持久化过程中发生的情况:

insert into A (lastUpdate, version, id) values (to_timestamp('07/13/2017 12:28:37.112', 'mm/dd/yyyy hh24:mi:ss.ff3'), 1, 1)
insert into B (ID, KEY, VALUE) values (1, 'b', '')
Run Code Online (Sandbox Code Playgroud)

Oracle 是如此“聪明”(愚蠢?)以至于它插入 NULL 而不是空字符串。所以在db中B表中有(1, 'b', NULL)记录:

select * from b;
ID  KEY VALUE
1   b   (null)
Run Code Online (Sandbox Code Playgroud)

现在进行查找:

A found = em.find(A.class, 1l);
System.out.println("found no. 1: " + found);
Run Code Online (Sandbox Code Playgroud)

请注意,输出清楚地表明 Hibarnate 尚未选取 NULL 值元素:

found no. 1: id: 1, version: 1, mapping: {}
Run Code Online (Sandbox Code Playgroud)

现在是与“更新”值的合并——假设更新值是“任何内容”。请注意,这实际上是一个补充:

found.myMapping.put("b", "anything");
em.getTransaction().begin();
em.merge(found);
em.getTransaction().commit();
em.clear();
Run Code Online (Sandbox Code Playgroud)

由于这是一个补充,这就是 Hibernate 将尝试做的事情:

insert into B (ID, KEY, VALUE) values (1, 'b', 'anything')
Run Code Online (Sandbox Code Playgroud)

并且插入失败,并显示 ORA-00001:违反了唯一约束(XXX.SYS_C0045198):B 中确实存在具有此主键(1,'b')的记录。

关于此的另一篇好文章:

http://koenseneels.blogspot.hu/2012/09/hibernates-map-behaviour.html