Lombok @Builder 和 JPA 默认构造函数

问题描述 投票:0回答:9

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

java spring spring-data-jpa lombok
9个回答
129
投票

已更新

根据反馈和约翰的answer,我已经更新了答案,不再使用

@Tolerate
@Data
,而是通过
@Getter
@Setter
创建访问器和修改器,通过
@NoArgsConstructor创建默认构造函数
,最后我们通过
@AllArgsConstructor
创建构建器所需的所有参数构造函数。

既然您想使用构建器模式,我想您想限制构造函数和修改器方法的可见性。 为了实现这一点,我们通过

package private
access
注释上的
@NoArgsConstructor
属性以及
@AllArgsConstructor
注释上的
value
属性将可见性设置为
@Setter

重要

请记住正确覆盖

toString
equals
hashCode
。 有关详细信息,请参阅 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
可以添加无参数构造函数。

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

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

请记住正确覆盖

toString
equals
hashCode
。 有关详细信息,请参阅 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());
    }
}

111
投票

您还可以使用

@Data @Builder @NoArgsConstructor @AllArgsConstructor
结合类定义来显式解决它。


15
投票

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

这是一个不起作用的示例:

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


10
投票

如果构造函数上的注释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
@AllArgsConstructor

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


5
投票

使用以下组合

  • 龙目岛
  • JPA
    • 增删改查
    • 正确
      @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;
}

3
投票

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

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

1
投票

Jeff 的答案工作正常,但是 @Builder 还不支持自引用关系。

查看此问题了解更多详细信息:

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


0
投票
如果 Builder 与 @NoArgsConstructor 一起使用,则需要 @AllArgsConstructor - 因为构建器需要 @AllArgsConstructor。 - 简单修复

如果您只使用 @Builder - 它会正常工作,因为私有参数化构造函数将由 @Builder 创建,但如果您使用任何 XArgsConstructor,它将覆盖构建器构造函数。

因此,如果您使用@Builder和@NoArgsConstructor或默认构造函数,那么NoArgsConstructor将覆盖Builder的参数化构造函数。这就是你会收到如下错误的原因: 必需:无参数 找到:java.lang.String,java.lang.String 原因:实际参数列表和形式参数列表的长度不同

因为你的 @NoArgsConstructor 期望没有争论,但构建器试图通过传递参数来使用相同的内容。

© www.soinside.com 2019 - 2024. All rights reserved.