【问题标题】:Why does Hibernate perform INSERT instead of UPDATE when existing value is null?当现有值为空时,为什么 Hibernate 执行 INSERT 而不是 UPDATE?
【发布时间】:2017-07-12 11:45:53
【问题描述】:

问题

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

在表 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

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

所以我的问题是:

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

我的数据库

表 A( id、版本、lastUpdate

表 B( id、key、value

我的实体

@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...
}

【问题讨论】:

  • 能否提供实际的select和merge操作以及map修改语句?
  • 基本上,我所做的只是A a = em.find(A.class, 23); a.myMapping.put("errorKey", null); em.merge(a); 虽然,在我的应用程序的上下文中它有点复杂。

标签: java hibernate jpa


【解决方案1】:

答案 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();

这是在初始持久化期间发生的情况:

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', '')

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

select * from b;
ID  KEY VALUE
1   b   (null)

现在是查找:

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

请注意,输出清楚地表明 Hibarnate 没有拾取 NULL 值元素:

found no. 1: id: 1, version: 1, mapping: {}

现在是与“更新”值的合并——假设更新的值是“任何东西”。请注意,它实际上是一个加法:

found.myMapping.put("b", "anything");
em.getTransaction().begin();
em.merge(found);
em.getTransaction().commit();
em.clear();

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

insert into B (ID, KEY, VALUE) values (1, 'b', 'anything')

插入失败并出现 ORA-00001:违反唯一约束 (XXX.SYS_C0045198):B 中确实有一条记录使用此主键 (1, 'b')。

关于这个的另一篇不错的文章:

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

【讨论】:

  • 感谢您的调查。我设法通过完全禁止 null 值和空 String 对象来解决问题。在编写数据库操作之前,我使用 @PrePersist@PreUpdate 注释从地图中删除任何此类元素。
猜你喜欢
  • 2020-12-21
  • 1970-01-01
  • 1970-01-01
  • 2013-10-11
  • 1970-01-01
  • 1970-01-01
  • 2012-03-22
  • 2023-03-30
  • 1970-01-01
相关资源
最近更新 更多