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

时间:2016-10-11 14:31:50

标签: java hibernate jpa default lombok

我有一个具有布尔值的实体,该值以“Y”或“N”字符的形式持久保存到数据库中:

    union floatInt
    { 
        float f;
        unsigned int l;
    };

    floatInt A, B;

    A.l = 0xFFA70B56;
    B.f = A.f;

    std::cout << std::hex << B.l << std::endl;

但是,当我尝试运行测试以将其保存到数据库时,我会得到一个 NULL for Constraint Violation error for activeIndicator ,尽管事实上我将其初始化为Boolean。正确,正如您在上面的代码中看到的那样。当我打开Hibernate的调试日志时,我能够验证它是否在insert语句中发送NULL 。以下是我的测试代码:

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

2 个答案:

答案 0 :(得分:2)

事实证明,Hibernate不是故障,而是使用Lombok。如果您正在使用注释,例如 @Data @EqualsAndHashCodeOf @NoArgsConstructor @AllArgsConstructor ,或 @Builder ,您的域对象可能正在使用Lombok。 (Groovy有一些类似的注释,只是检查你的导入是否使用 groovy.transform lombok 作为包前缀)

导致我出现问题的代码位于我的测试类中:

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

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

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

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

答案 1 :(得分:0)

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