Lombok @Builder和JPA默认构造函数


78

我正在将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



1
尝试添加no args构造函数.AFAIK,@Builder不会覆盖您的no args构造函数
Ken Chan

2
是的,但@Id是必填字段。NoArgs不会削减它
krzakov 2015年

2
我不明白你想要什么 您如何拥有组成值的noargs构造函数?@Id是必需的还是不需要的。如果是,则需要一个构造函数参数,如果不需要,则可以使用NoArgs。我在这里想念什么?
Roel Spilker,2015年

Answers:


83

更新

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

因为您想使用构建器模式,所以我想您想限制构造函数和mutators方法的可见性。为此,我们package private通过和注释上的属性以及注释上的access属性将可见性设置为。@NoArgsConstructor@AllArgsConstructorvalue@Setter

重要

记住要正确重写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

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

因为您想使用构建器模式,所以我想您想控制setter方法的可见性。

@Data注解使得生成的制定者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());
    }
}

2
我有和krzakov相同的问题,并且使用提示得到了解决@Tolerate。谢谢杰夫。但是,添加@Data注释有什么理由吗?在这种情况下,不需要使用setter,而是@Data使用默认行为覆盖equal / hash / toString,这可能会造成问题。
wollodev

不要@Data与实体一起使用。
WST

如果这与toString,equals和hashCode有关,那么我已添加了有关正确实现的文档链接。
杰夫

@wst,为什么@Data对实体不是个好主意?
srnjak '18 -10-30

2
@Data默认情况下,@ srnjak使用所有字段进行生成equals和使用hashCode方法,包括id。简单的示例-保存前后您可能具有相同的实体表示形式,从Java的角度来看,它们将是不同的实例(带有和不带有id的实例)。这可能会导致混乱和一致性问题。@Data如果您要覆盖这些方法,则可以使用。有一个关于它的Hibernate文档的章节:docs.jboss.org/hibernate/orm/5.3/userguide/html_single/...
WST

71

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


请注意,这不会自动创建访问器方法(获取器)。
杰夫

然后@Jeff只需添加@Data
Deniss M.

1
您不希望使用@Data,因为它会生成equals,hashCode和toString方法,在jpa实体的情况下,应手动生成这些方法。请参阅上面我的答案中的详细信息。
杰夫,

9

注释的顺序在这里似乎很重要,使用相同的注释,但是使用不同的顺序,则可以使代码正常工作或不工作。

这是一个无效的示例:

@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

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

如果项目将SpringData与JPA一起使用,则可以使用org.springframework.data.annotation.PersistenceConstructor(Spring注释,而不是JPA!)来解决。

然后,结合龙目岛,注释将如下所示:

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

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

@Builder
@AllArgsConstructor

2

使用以下组合

  • 龙目岛
  • JPA
    • 欺诈
    • 正确 @EqualsAndHashCode
  • 不变性-公共最终领域
  • 没有吸气剂
  • 没有二传手
  • 通过@Builder和进行更改@With

我用了:

//Lombok & JPA
///programming/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;
}

1

使用@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


1

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.