简体   繁体   English

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

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

I have an entity that has a Boolean value that gets persisted to the DB as a 'Y' or 'N' character: 我有一个具有布尔值的实体,该布尔值作为“ 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;
}

However when I tried to run a test to save this to the database, I would get a NULL Constraint Violation error for activeIndicator , despite the fact that I was initializing it to Boolean.TRUE , as you can see in the code above. 但是,当我尝试运行测试以将其保存到数据库时,尽管我将其初始化为Boolean.TRUE ,但仍收到activeIndicatorNULL约束违规错误 ,如您在上面的代码中看到的那样。 When I turned on debug logs for Hibernate, I was able to verify that it was sending NULL in the insert statement . 当我打开Hibernate的调试日志时,我能够验证它在insert语句中是否发送NULL Below is my test code: 下面是我的测试代码:

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

Turns out that the fault didn't lie with Hibernate, but with Lombok. 事实证明,问题不在于Hibernate,而在于Lombok。 If you're using annotations such as @Data , @EqualsAndHashCodeOf , @NoArgsConstructor , @AllArgsConstructor , or @Builder , your domain object may be using Lombok. 如果您使用注释,比如@Data,@EqualsAndHashCodeOf,@NoArgsConstructor,@AllArgsConstructor,@Builder,你的域对象可通过龙目岛。 (Groovy has some similar annotations, just check if your imports are using groovy.transform or lombok as the package prefix) (Groovy中有一些类似的注解,只是检查,如果你进口使用groovy.transform龙目岛作为包前缀)

The bit of code causing my problems was in my test class: 导致我出现问题的代码是在我的测试类中:

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

After much trial and error, it became apparent that while my JPA/Hibernate setup was correct,the initialization default: 经过多次试验和错误,很明显,尽管我的JPA / Hibernate设置正确,但初始化默认设置为:

private Boolean activeIndicator = Boolean.TRUE;

was not being executed. 没有被执行。

It took some digging, but apparently Lombok's Builder does not use Java's initialization , but its own means to create the object. 进行了一些挖掘,但是显然Lombok的Builder不是使用Java的初始化 ,而是使用自己的方法来创建对象。 This means that initialization defaults are overlooked when you call build() , hence the field will remain NULL if you don't explicitly set it in your builder chain. 这意味着在调用build()时会忽略初始化默认值 ,因此,如果未在构建器链中显式设置该字段,则该字段将保持NULL。 Note that normal Java initialization and constructors are unaffected by this; 注意,普通的Java初始化和构造函数不受此影响。 the problem ONLY manifests itself when creating objects with Lombok's builder. 仅当使用Lombok的生成器创建对象时,该问题才会显现出来。

The issue detailing this problem and Lombok's explanation on why they won't implement a fix is detailed here: https://github.com/rzwitserloot/lombok/issues/663#issuecomment-121397262 此处详细介绍了详细说明此问题的问题以及龙目岛为何不实施修复程序的解释: https : //github.com/rzwitserloot/lombok/issues/663#issuecomment-121397262

There are many workarounds to this, and the one you may want to use depends on your needs. 有很多解决方法,您可能要使用哪种取决于您的需求。

  • Custom Constructor(s) : One can create custom constructors either in place of, or alongside the builder. 自定义构造函数 :可以创建自定义构造函数,以代替构建器或与构建器一起创建。 When you need defaults to be set, use your constructors instead of the builder. 当需要设置默认值时,请使用构造函数而不是构造函数。 - This solution makes it seem like you shouldn't even bother with a builder. -这种解决方案使您似乎不应该为构建器而烦恼。 The only reason you may want to keep the builder is if the builder is capable of delegating creation to your constructors, therefore getting the default values initialized when it builds. 您可能希望保留该构建器的唯一原因是,该构建器是否能够将创建委托给您的构造器,从而在构建时将其默认值初始化。 I have no idea if that's possible as I haven't tested it myself and I decided to go a different route. 我不知道这是否可行,因为我自己还没有测试过,所以我决定走另一条路。 Do respond if you've looked into this option further. 如果您进一步研究了此选项,请做出响应。
  • Overriding the Build Method: If you're going to be using the Builder extensively and don't want to be writing a bunch of constructors, this solution is for you. 覆盖Build方法:如果您打算广泛使用Builder,并且不想编写一堆构造函数,那么此解决方案适合您。 You can set defaults in your own implementation of the build method and delegate to the super to do the actual build. 您可以在自己的build方法实现中设置默认值,然后委托super进行实际构建。 Just be sure to document your code well so future maintainers know to set defaults on both the object and in the build method so they're set no matter how the object is initialized. 只需确保很好地记录您的代码,以便将来的维护者知道在对象和build方法上都设置默认值,因此无论如何初始化对象,都可以对其进行设置。
  • JPA PrePersist Annotation: If your requirements for defaults are for database constraints and you're using JPA or Hibernate, this option is available to you. JPA PrePersist批注:如果默认要求是数据库约束,并且您正在使用JPA或Hibernate,则可以使用此选项。 This annotation will run the method it's attached to before every insert and update of the entity, regardless of how it was initialized. 该注释将在每次实体插入和更新之前运行它所附加的方法,而不管其初始化方式如何。 This is nice so that you don't have to set defaults in two places like you'd have to with the build override strategy. 很好,这样您就不必像在构建覆盖策略中那样必须在两个地方设置默认值。 I went with this option as my business requirements have a strong defaulting requirement on this field. 我选择了此选项,因为我的业务要求对此字段具有很强的默认要求。 I added the code below to my entity class to fix the problem. 我将以下代码添加到我的实体类中,以解决此问题。 I also removed the "= Boolean.TRUE" part of the isActive field declaration as it was no longer necessary. 我也删除了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