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

时间:2017-07-12 11:45:53

标签: java hibernate jpa

问题

在合并我的一个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
  2. 时,为什么Hibernate会执行INSERT语句
  3. 如何解决此问题?
  4. 我的数据库

    表A( id,版本,lastUpdate

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

1 个答案:

答案 0 :(得分: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