【问题标题】:Lombok @Builder and JPA Default constructorLombok @Builder 和 JPA 默认构造函数
【发布时间】:2016-03-18 10:32:50
【问题描述】:

我将 Lombok 项目与 Spring Data JPA 一起使用。 有什么方法可以将 Lombok @Builder 与 JPA 默认构造函数连接起来吗?

代码:

@Entity 
@Builder
class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
}

据我所知,JPA 需要被@Builder 注释覆盖的默认构造函数。有什么解决办法吗?

这段代码给了我错误: org.hibernate.InstantiationException: No default constructor for entity: : app.domain.model.Person

【问题讨论】:

  • 尝试添加无参数构造函数 .AFAIK,@Builder 不会覆盖你的无参数构造函数
  • 是的,但@Id 是必填字段。 NoArgs 不会削减它
  • 我不明白你想要什么。你怎么能有一个构成值的 noargs 构造函数? @Id 要么是必需的,要么不是。如果是,则需要构造函数参数,如果不是,则可以使用 NoArgs。我在这里错过了什么?

标签: java spring spring-data-jpa lombok


【解决方案1】:

您也可以在类定义上结合@Data @Builder @NoArgsConstructor @AllArgsConstructor 显式解决它。

【讨论】:

  • 注意,这不会自动创建访问器方法(getter)。
  • @Jeff 然后添加@Data
  • 您不想使用@Data,因为这会生成equals、hashCode 和toString 方法,在jpa 实体的情况下应该手动生成这些方法。请参阅上面我的答案中的详细信息。
【解决方案2】:

Jeff 的回答很好,但是 @Builder 还不支持自引用关系。

查看此问题了解更多详情:

JPA @OnetoOne self reference relationship with both columns non null

【讨论】:

    【解决方案3】:

    使用以下组合

    • 龙目岛
    • JPA
      • CRUD
      • 正确的@EqualsAndHashCode
    • 不变性 - 公共最终字段
    • 没有吸气剂
    • 没有二传手
    • 通过@Builder@With 更改

    我用过:

    //Lombok & JPA
    //https://stackoverflow.com/questions/34241718/lombok-builder-and-jpa-default-constructor
    
    //Mandatory in conjunction with JPA: an equal based on fields is not desired
    @lombok.EqualsAndHashCode(onlyExplicitlyIncluded = true)
    //Mandatory in conjunction with JPA: force is needed to generate default values for final fields, that will be overriden by JPA
    @lombok.NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
    //Hides the constructor to force usage of the Builder.
    @lombok.AllArgsConstructor(access = AccessLevel.PRIVATE)
    @lombok.ToString
    //Good to just modify some values
    @lombok.With
    //Mandatory in conjunction with JPA: Some suggest that the Builder should be above Entity - https://stackoverflow.com/a/52048267/99248
    //Good to be used to modify all values
    @lombok.Builder(toBuilder = true)
    //final fields needed for imutability, the default access to public - since are final is safe 
    @lombok.experimental.FieldDefaults(makeFinal = true, level = AccessLevel.PUBLIC)
    //no getters and setters
    @lombok.Getter(value = AccessLevel.NONE)
    @lombok.Setter(value = AccessLevel.NONE)
    
    //JPA
    @javax.persistence.Entity
    @javax.persistence.Table(name = "PERSON_WITH_MOTTO")
    //jpa should use field access 
    @javax.persistence.Access(AccessType.FIELD)
    public class Person {
      @javax.persistence.Id
      @javax.persistence.GeneratedValue
      //Used also automatically as JPA
      @lombok.EqualsAndHashCode.Include
      Long id;
      String name;
      String motto;
    }
    

    【讨论】:

      【解决方案4】:

      我使用所有这些注释解决了这个问题:

      @Data
      @Builder
      @AllArgsConstructor(access = AccessLevel.PACKAGE)
      @NoArgsConstructor(access = AccessLevel.PACKAGE)
      

      【讨论】:

        【解决方案5】:

        使用@NoArgsConstructor@AllArgsContructor 将有助于解决@Builder 具有默认构造函数的问题。

        例如

        @Entity 
        @Builder
        @NoArgsConstructor
        @AllArgsContructor
        class Person {
            @Id
            @GeneratedValue(strategy = GenerationType.AUTO)
            private Long id;
        }
        

        这是因为@Builder 需要所有参数构造函数,并且仅指定默认构造函数会导致问题。

        这里不再解释:https://github.com/rzwitserloot/lombok/issues/1389#issuecomment-369404719

        【讨论】:

          【解决方案6】:

          看来注解顺序在这里很重要,使用相同的注解,但顺序不同,你可以让代码工作,也可以不工作。

          这是一个无效的例子:

          @AllArgsConstructor
          @Builder
          @Data
          @Entity
          @EqualsAndHashCode
          @NoArgsConstructor
          @RequiredArgsConstructor
          @Table
          @ToString
          public class Person implements Serializable {
            private String name;
          }
          

          这是一个工作示例:

          @Builder
          @Data
          @Entity
          @EqualsAndHashCode
          @AllArgsConstructor
          @NoArgsConstructor
          @RequiredArgsConstructor
          @Table
          @ToString
          public class Person implements Serializable {
            private String name;
          }
          

          所以一定要把@Builder注解放在最上面,在我的例子中,我遇到了这个错误,因为我想按字母顺序对注解进行排序。

          【讨论】:

            【解决方案7】:

            更新

            根据反馈和 John 的 answer,我更新了答案,不再使用 @Tolerate@Data,而是通过 @Getter@Setter 创建访问器和修改器,创建默认值构造函数通过@NoArgsConstructor,最后我们通过@AllArgsConstructor创建构建器需要的全参数构造函数。

            由于您想使用构建器模式,我想您想限制构造函数和修改器方法的可见性。 为了实现这一点,我们通过@NoArgsConstructor@AllArgsConstructor 注释上的access 属性以及value 注释上的value 属性将可见性设置为package private

            重要

            请记住正确覆盖toStringequalshashCode。 有关详细信息,请参阅 Vlad Mihalcea 的以下帖子:

            package com.stackoverflow.SO34299054;
            
            import static org.junit.Assert.*;
            
            import java.util.Random;
            
            import javax.persistence.Entity;
            import javax.persistence.GeneratedValue;
            import javax.persistence.GenerationType;
            import javax.persistence.Id;
            
            import org.junit.Test;
            
            import lombok.AccessLevel;
            import lombok.AllArgsConstructor;
            import lombok.Builder;
            import lombok.Getter;
            import lombok.NoArgsConstructor;
            import lombok.Setter;
            
            @SuppressWarnings("javadoc")
            public class Answer {
            
                @Entity
                @Builder(toBuilder = true)
                @AllArgsConstructor(access = AccessLevel.PACKAGE)
                @NoArgsConstructor(access = AccessLevel.PACKAGE)
                @Setter(value = AccessLevel.PACKAGE)
                @Getter
                public static class Person {
            
                    @Id
                    @GeneratedValue(strategy = GenerationType.AUTO)
                    private Long id;
            
                    /*
                     * IMPORTANT:
                     * Set toString, equals, and hashCode as described in these
                     * documents:
                     * - https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/
                     * - https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
                     * - https://vladmihalcea.com/hibernate-facts-equals-and-hashcode/
                     */
                }
            
                /**
                 * Test person builder.
                 */
                @Test
                public void testPersonBuilder() {
            
                    final Long expectedId = new Random().nextLong();
                    final Person fromBuilder = Person.builder()
                        .id(expectedId)
                        .build();
                    assertEquals(expectedId, fromBuilder.getId());
            
                }
            
                /**
                 * Test person constructor.
                 */
                @Test
                public void testPersonConstructor() {
            
                    final Long expectedId = new Random().nextLong();
                    final Person fromNoArgConstructor = new Person();
                    fromNoArgConstructor.setId(expectedId);
                    assertEquals(expectedId, fromNoArgConstructor.getId());
                }
            }
            

            使用@Tolerate@Data 的旧版本:

            使用 @Tolerate 允许添加 noarg 构造函数。

            由于您想使用构建器模式,我想您想控制 setter 方法的可见性。

            @Data 注释使生成的 setter 成为public,将@Setter(value = AccessLevel.PROTECTED) 应用于字段使它们成为protected

            请记住正确覆盖toStringequalshashCode。 有关详细信息,请参阅 Vlad Mihalcea 的以下帖子:

            package lombok.javac.handlers.stackoverflow;
            
            import static org.junit.Assert.*;
            
            import java.util.Random;
            
            import javax.persistence.GenerationType;
            import javax.persistence.GeneratedValue;
            import javax.persistence.Id;
            
            import lombok.AccessLevel;
            import lombok.Builder;
            import lombok.Data;
            import lombok.Setter;
            import lombok.experimental.Tolerate;
            
            import org.junit.Test;
            
            public class So34241718 {
            
                @Builder
                @Data
                public static class Person {
            
                    @Id
                    @GeneratedValue(strategy = GenerationType.AUTO)
                    @Setter(value = AccessLevel.PROTECTED)
                    Long id;
            
                    @Tolerate
                    Person() {}
            
                   /* IMPORTANT:
                      Override toString, equals, and hashCode as described in these 
                      documents:
                      - https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/
                      - https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
                      - https://vladmihalcea.com/hibernate-facts-equals-and-hashcode/
                      */
                }
            
                @Test
                public void testPersonBuilder() {
            
                    Long expectedId = new Random().nextLong();
                    final Person fromBuilder = Person.builder()
                        .id(expectedId)
                        .build();
                    assertEquals(expectedId, fromBuilder.getId());
            
                }
            
                @Test
                public void testPersonConstructor() {
            
                    Long expectedId = new Random().nextLong();
                    final Person fromNoArgConstructor = new Person();
                    fromNoArgConstructor .setId(expectedId);
                    assertEquals(expectedId, fromNoArgConstructor.getId());
                }
            }
            

            【讨论】:

            • 我和 krzakov 有同样的问题,我用你的提示解决了这个问题,使用@Tolerate。谢谢那个杰夫。但是有什么理由添加@Data 注释吗?在这种情况下不需要设置器,@Data 会使用默认行为覆盖 equal/hash/toString,这可能会产生问题。
            • 不要将@Data 用于实体。
            • 如果这与 toString、equals 和 hashCode 有关,我已经添加了有关正确实现的文档的链接。
            • @wst ,为什么@Data 对实体不好?
            • @srnjak @Data 默认使用所有字段来生成equalshashCode 方法,包括id。简单的例子——你可能在保存之前和之后有相同的实体表示,从 Java 的角度来看,这将是不同的实例(有和没有 id)。这可能会导致混淆和一致性问题。如果您要覆盖这些方法,则可以使用@Data。有关于它的 Hibernate 文档章节:docs.jboss.org/hibernate/orm/5.3/userguide/html_single/…
            【解决方案8】:

            如果同时使用构造函数上的注解lombok.Tolerate和某些属性上的javax.validation.constraints.NotNull,sonarqube会将其标记为关键错误:PROPERTY 标记为“javax.validation.constraints.NotNull”,但未在此构造函数中初始化。

            如果项目使用SpringData和JPA,可以使用org.springframework.data.annotation.PersistenceConstructor解决(Spring注解,不是JPA!)

            那么,结合Lombok,注解会是这样的:

            @RequiredArgsConstructor(onConstructor = @__(@PersistenceConstructor))
            

            对于 Lombok builder,您还需要添加:

            @Builder
            @AllArgsConstructor
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2016-03-24
              • 2020-05-14
              • 1970-01-01
              • 2015-11-09
              • 2019-08-09
              • 2012-11-26
              • 2011-02-20
              相关资源
              最近更新 更多