Lombok @Builder和JPA Default构造函数

时间:2023-02-05 17:52:28

I'm using project Lombok together with Spring Data JPA. Is there any way to connect Lombok @Builder with 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 @Builder annotation. 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:没有实体的默认构造函数:: app.domain.model.Person

4 个解决方案

#1


36  

Updated

Based on the feedback and John's answer I have updated the answer to no longer use @Tolerate or @Data and instead we create accessors and mutators via @Getter and @Setter, create the default constructor via @NoArgsConstructor, and finally we create the all args constructor that the builder requires via @AllArgsConstructor.

基于反馈和John的回答,我更新了答案,不再使用@Tolerate或@Data,而是通过@Getter和@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 private via the access attribute on the @NoArgsConstructor and @AllArgsConstructor annotations and the value attribute on the @Setterannotation.

既然你想使用构建器模式,我想你想要限制构造函数和mutators方法的可见性。为实现此目的,我们通过@NoArgsConstructor和@AllArgsConstructor注释上的access属性以及@Setterannotation上的value属性设置包的私有性。

Important

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

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

package com.*.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 @Tolerate and @Data:

使用@Tolerate和@Data的旧版本:

Using @Tolerate worked to allow adding a noarg constructor.

使用@Tolerate可以添加一个noarg构造函数。

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

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

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

@Data注释使生成的setter公开,将@Setter(value = AccessLevel.PROTECTED)应用于字段使其受到保护。

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

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

package lombok.javac.handlers.*;

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());
    }
}

#2


47  

You can also solve it explicit with @Builder @NoArgsConstructor @AllArgsConstructor combined on the class definition.

您也可以使用@Builder @NoArgsConstructor @AllArgsConstructor在类定义上进行显式解决。

#3


1  

If the annotations lombok.Tolerate on constructor and javax.validation.constraints.NotNull on 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!)

如果项目使用SpringData和JPA,可以使用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
@AllArgsConstructor

#4


1  

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注释放在最顶端的位置,在我的情况下我遇到了这个错误,因为我想按字母顺序对注释进行排序。

#1


36  

Updated

Based on the feedback and John's answer I have updated the answer to no longer use @Tolerate or @Data and instead we create accessors and mutators via @Getter and @Setter, create the default constructor via @NoArgsConstructor, and finally we create the all args constructor that the builder requires via @AllArgsConstructor.

基于反馈和John的回答,我更新了答案,不再使用@Tolerate或@Data,而是通过@Getter和@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 private via the access attribute on the @NoArgsConstructor and @AllArgsConstructor annotations and the value attribute on the @Setterannotation.

既然你想使用构建器模式,我想你想要限制构造函数和mutators方法的可见性。为实现此目的,我们通过@NoArgsConstructor和@AllArgsConstructor注释上的access属性以及@Setterannotation上的value属性设置包的私有性。

Important

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

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

package com.*.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 @Tolerate and @Data:

使用@Tolerate和@Data的旧版本:

Using @Tolerate worked to allow adding a noarg constructor.

使用@Tolerate可以添加一个noarg构造函数。

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

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

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

@Data注释使生成的setter公开,将@Setter(value = AccessLevel.PROTECTED)应用于字段使其受到保护。

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

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

package lombok.javac.handlers.*;

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());
    }
}

#2


47  

You can also solve it explicit with @Builder @NoArgsConstructor @AllArgsConstructor combined on the class definition.

您也可以使用@Builder @NoArgsConstructor @AllArgsConstructor在类定义上进行显式解决。

#3


1  

If the annotations lombok.Tolerate on constructor and javax.validation.constraints.NotNull on 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!)

如果项目使用SpringData和JPA,可以使用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
@AllArgsConstructor

#4


1  

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注释放在最顶端的位置,在我的情况下我遇到了这个错误,因为我想按字母顺序对注释进行排序。