繁体   English   中英

当在字段上设置默认值时,JPA / Hibernate将插入NULL

[英]JPA / Hibernate is Inserting NULL When Default Value is Set on Field

我有一个具有布尔值的实体,该布尔值作为“ Y”或“ N”字符保留在数据库中:

@Data
@Entity
@Table(name = "MYOBJECT", schema = "MYSCHEMA")
@EqualsAndHashCode(of = "id")
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MyObject {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MYOBJECT_SEQ")
    @SequenceGenerator(name = "MYOBJECT_SEQ", sequenceName = "MYSCHEMA.MYOBJECT_SEQ")
    private Long id;

    @NotEmpty(message = "Name may not be empty")
    @SafeHtml(whitelistType = WhiteListType.NONE, message = "Name may not contain html or javascript")
    @Column(name = "NAME", nullable = false, length = 60)
    private String name;

    // Other fields... 

    @Type(type = "yes_no")
    @Column(name = "ACTIVE", length = 1, nullable = false)
    private Boolean isActive = Boolean.TRUE;

    @SafeHtml(whitelistType = WhiteListType.NONE, message = "username may not contain html or javascript")
    @Column(name = "USERNAME", nullable = false, length = 30)
    private String username;
}

但是,当我尝试运行测试以将其保存到数据库时,尽管我将其初始化为Boolean.TRUE ,但仍收到activeIndicatorNULL约束违规错误 ,如您在上面的代码中看到的那样。 当我打开Hibernate的调试日志时,我能够验证它在insert语句中是否发送NULL 下面是我的测试代码:

@Test
public void testSave() {
    // Setup Data
    final String NAME = "Test name";
    final String USERNAME = "Test username";

    MyObject stagedObject = MyObject.builder().name(NAME).username(USERNAME).build();

    // Run test
    MyObject savedObject = repo.save(stagedObject);
    entityManager.flush(); // force JPA to sync with db
    entityManager.clear(); // force JPA to fetch a fresh copy of the objects instead of relying on what's in memory

    // Assert
    MyObject fetchedObject = repo.findOne(savedObject.getId());
    assertThat(fetchedObject.getIsActive(), is(Boolean.TRUE));
    // other assertions
}

事实证明,问题不在于Hibernate,而在于Lombok。 如果您使用注释,比如@Data,@EqualsAndHashCodeOf,@NoArgsConstructor,@AllArgsConstructor,@Builder,你的域对象可通过龙目岛。 (Groovy中有一些类似的注解,只是检查,如果你进口使用groovy.transform龙目岛作为包前缀)

导致我出现问题的代码是在我的测试类中:

MyObject.builder().name(NAME).username(USERNAME).build();

经过多次试验和错误,很明显,尽管我的JPA / Hibernate设置正确,但初始化默认设置为:

private Boolean activeIndicator = Boolean.TRUE;

没有被执行。

进行了一些挖掘,但是显然Lombok的Builder不是使用Java的初始化 ,而是使用自己的方法来创建对象。 这意味着在调用build()时会忽略初始化默认值 ,因此,如果未在构建器链中显式设置该字段,则该字段将保持NULL。 注意,普通的Java初始化和构造函数不受此影响。 仅当使用Lombok的生成器创建对象时,该问题才会显现出来。

此处详细介绍了详细说明此问题的问题以及龙目岛为何不实施修复程序的解释: https : //github.com/rzwitserloot/lombok/issues/663#issuecomment-121397262

有很多解决方法,您可能要使用哪种取决于您的需求。

  • 自定义构造函数 :可以创建自定义构造函数,以代替构建器或与构建器一起创建。 当需要设置默认值时,请使用构造函数而不是构造函数。 -这种解决方案使您似乎不应该为构建器而烦恼。 您可能希望保留该构建器的唯一原因是,该构建器是否能够将创建委托给您的构造器,从而在构建时将其默认值初始化。 我不知道这是否可行,因为我自己还没有测试过,所以我决定走另一条路。 如果您进一步研究了此选项,请做出响应。
  • 覆盖Build方法:如果您打算广泛使用Builder,并且不想编写一堆构造函数,那么此解决方案适合您。 您可以在自己的build方法实现中设置默认值,然后委托super进行实际构建。 只需确保很好地记录您的代码,以便将来的维护者知道在对象和build方法上都设置默认值,因此无论如何初始化对象,都可以对其进行设置。
  • JPA PrePersist批注:如果默认要求是数据库约束,并且您正在使用JPA或Hibernate,则可以使用此选项。 该注释将在每次实体插入和更新之前运行它所附加的方法,而不管其初始化方式如何。 很好,这样您就不必像在构建覆盖策略中那样必须在两个地方设置默认值。 我选择了此选项,因为我的业务要求对此字段具有很强的默认要求。 我将以下代码添加到我的实体类中,以解决此问题。 我也删除了isActive字段声明的“ = Boolean.TRUE”部分,因为不再需要它。

     @PrePersist public void defaultIsActive() { if(isActive == null) { isActive = Boolean.TRUE; } } 

在具有默认值的字段上使用@ Builder.Deafult批注。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM