Java Lombok @Builder 和 JPA 默认构造函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34241718/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 15:27:51  来源:igfitidea点击:

Lombok @Builder and JPA Default constructor

javaspringspring-data-jpalombok

提问by krzakov

I'm using project Lombok together with Spring Data JPA. Is there any way to connect Lombok @Builderwith JPA default constructor?

我正在将 Lombok 项目与 Spring Data JPA 一起使用。有没有办法将 Lombok@Builder与 JPA 默认构造函数连接起来?

Code:

代码:

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

As far as I know JPA needs default constructor which is overriden by @Builderannotation. Is there any workaround for that?

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

This code gives me error: org.hibernate.InstantiationException: No default constructor for entity: : app.domain.model.Person

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

采纳答案by Jeff

Updated

更新

Based on the feedback and John's answerI have updated the answer to no longer use @Tolerateor @Dataand instead we create accessors and mutators via @Getterand @Setter, create the default constructor via @NoArgsConstructor, and finally we create the all args constructor that the builder requires via @AllArgsConstructor.

根据反馈和 John 的回答,我更新了不再使用@Tolerateor的答案@Data,而是通过@Getterand创建访问器和修改器,通过@Setter创建默认构造函数@NoArgsConstructor,最后我们通过创建构建器需要的所有 args 构造函数@AllArgsConstructor

Since you want to use the builder pattern I imagine you want to restrict visibility of the constructor and mutators methods. To achieve this we set the visibility to package privatevia the accessattribute on the @NoArgsConstructorand @AllArgsConstructorannotations and the valueattribute on the @Setterannotation.

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

Important

重要的

Remember to properly override toString, equals, and hashCode. See the following posts by Vlad Mihalcea for details:

记住要正确重写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());
    }
}

Old Version using @Tolerateand @Data:

旧版本使用@Tolerate@Data

Using @Tolerateworked to allow adding a noarg constructor.

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

Since you want to use the builder pattern I imagine you want to control visibility of the setter methods.

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

The @Dataannotation makes the generated setters public, applying @Setter(value = AccessLevel.PROTECTED)to the fields makes them protected.

@Data注解使得生成的制定者public,应用@Setter(value = AccessLevel.PROTECTED)的领域,因此他们protected

Remember to properly override toString, equals, and hashCode. See the following posts by Vlad Mihalcea for details:

记住要正确重写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());
    }
}

回答by John John Pichler

You can also solve it explicit with @Data @Builder @NoArgsConstructor @AllArgsConstructorcombined on the class definition.

您还可以通过@Data @Builder @NoArgsConstructor @AllArgsConstructor结合类定义来显式解决它。

回答by Pavel V

If the annotations lombok.Tolerateon constructor and javax.validation.constraints.NotNullon some property are used at the same time, sonarqube will mark it as a critical error: PROPERTY is marked "javax.validation.constraints.NotNull" but is not initialized in this constructor.

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

If the project uses SpringData with JPA, it can be solved using org.springframework.data.annotation.PersistenceConstructor(Spring annotation, not JPA!)

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

Then, in combination with Lombok, annotations will be like this:

然后,结合 Lombok,注解会是这样的:

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

For Lombok builder you also need to add:

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

@Builder
@AllArgsConstructor

回答by Karl.S

It seems that the annotations order is important here, using the same annotations, but different orders, you can have the code working, or not.

似乎注释顺序在这里很重要,使用相同的注释,但顺序不同,您可以让代码工作,也可以不工作。

Here is a non working example:

这是一个非工作示例:

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

And this is a working example:

这是一个工作示例:

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

So be sure to have the @Builder annotation at the very top position, in my case I encountered this error because I wanted to sort annotations alphabetically.

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

回答by Amrut Prabhu

Using @NoArgsConstructorand @AllArgsContructorwill help solve the issue of having a default constructor with @Builder.

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

e.g

例如

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

This is because @Builderrequires all argument constructor and specifying only a default constructor will cause an issue.

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

Here is nore explaination: https://github.com/rzwitserloot/lombok/issues/1389#issuecomment-369404719

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

回答by Akash5288

i solved this using all these annotations:

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

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

回答by raisercostin

To use the following combination

要使用以下组合

  • lombok
  • JPA
    • CRUD
    • proper @EqualsAndHashCode
  • immutability - public final fields
  • no getters
  • no setters
  • changes via @Builderand @With
  • 龙目岛
  • 日本特许经营协会
    • CRUD
    • 恰当的 @EqualsAndHashCode
  • 不变性 - 公共最终字段
  • 没有吸气剂
  • 没有二传手
  • 通过@Builder和改变@With

I used:

我用了:

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

回答by bluelurker

Jeff's answer works fine however, @Builder does not support self-reference relationships yet.

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

Check this question for more details:

检查此问题以获取更多详细信息:

JPA @OnetoOne self reference relationship with both columns non null

JPA @OnetoOne 自引用关系与两列非空