Lombok @Builder 和 JPA 默认构造函数
Lombok @Builder and JPA Default constructor
我将 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
已更新
根据反馈和 John 的 ,我更新了不再使用 @Tolerate
或 @Data
的答案,而是通过 @Getter
和 @Setter
,通过 @NoArgsConstructor
创建默认构造函数,最后我们通过 @AllArgsConstructor
. [=44= 创建构建器所需的所有参数构造函数]
既然您想使用构建器模式,我想您想限制构造函数和修改器方法的可见性。
为此,我们通过 @NoArgsConstructor
和 @AllArgsConstructor
注释上的 access
属性以及 @Setter
上的 value
属性将可见性设置为 package private
注释。
重要
记得正确覆盖 toString
、equals
和 hashCode
。
有关详细信息,请参阅 Vlad Mihalcea 的以下帖子:
- the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate
- how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier
- hibernate-facts-equals-and-hashcode
package com.Whosebug.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
注释使生成的 setters public
,将 @Setter(value = AccessLevel.PROTECTED)
应用于字段使它们成为 protected
.
记得正确覆盖 toString
、equals
和 hashCode
。
有关详细信息,请参阅 Vlad Mihalcea 的以下帖子:
- the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate
- how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier
- hibernate-facts-equals-and-hashcode
package lombok.javac.handlers.Whosebug;
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());
}
}
您也可以结合 class 定义明确地解决它 @Data @Builder @NoArgsConstructor @AllArgsConstructor
。
如果使用构造函数上的注释 lombok.Tolerate 和某些 属性 上的 javax.validation.constraints.NotNull同时sonarqube会标记为严重错误:属性 is marked "javax.validation.constraints.NotNull" but is not initialized in this constructor.
如果项目使用SpringData with JPA,可以使用org.springframework.data.annotation.PersistenceConstructor解决(Spring注解,不是JPA!)
然后结合Lombok,注解会变成这样:
@RequiredArgsConstructor(onConstructor = @__(@PersistenceConstructor))
对于 Lombok 构建器,您还需要添加:
@Builder
@AllArgsConstructor
看来注解顺序在这里很重要,使用相同的注解,但顺序不同,你可以让代码工作,也可以不工作。
这是一个无效的例子:
@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 注释放在最顶部,在我的例子中,我遇到此错误是因为我想按字母顺序对注释进行排序。
使用 @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
我使用所有这些注释解决了这个问题:
@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@NoArgsConstructor(access = AccessLevel.PACKAGE)
要使用以下组合
- 龙目岛
- JPA
- 增删改查
- 适当
@EqualsAndHashCode
- 不变性 - public 最终字段
- 没有吸气剂
- 没有二传手
- 通过
@Builder
和 @With
更改
我用过:
//Lombok & JPA
//
//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 -
//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;
}
Jeff 的回答很好,但是@Builder 还不支持自引用关系。
查看此问题了解更多详情:
我将 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
已更新
根据反馈和 John 的 @Tolerate
或 @Data
的答案,而是通过 @Getter
和 @Setter
,通过 @NoArgsConstructor
创建默认构造函数,最后我们通过 @AllArgsConstructor
.
既然您想使用构建器模式,我想您想限制构造函数和修改器方法的可见性。
为此,我们通过 @NoArgsConstructor
和 @AllArgsConstructor
注释上的 access
属性以及 @Setter
上的 value
属性将可见性设置为 package private
注释。
重要
记得正确覆盖 toString
、equals
和 hashCode
。
有关详细信息,请参阅 Vlad Mihalcea 的以下帖子:
- the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate
- how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier
- hibernate-facts-equals-and-hashcode
package com.Whosebug.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
注释使生成的 setters public
,将 @Setter(value = AccessLevel.PROTECTED)
应用于字段使它们成为 protected
.
记得正确覆盖 toString
、equals
和 hashCode
。
有关详细信息,请参阅 Vlad Mihalcea 的以下帖子:
- the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate
- how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier
- hibernate-facts-equals-and-hashcode
package lombok.javac.handlers.Whosebug;
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());
}
}
您也可以结合 class 定义明确地解决它 @Data @Builder @NoArgsConstructor @AllArgsConstructor
。
如果使用构造函数上的注释 lombok.Tolerate 和某些 属性 上的 javax.validation.constraints.NotNull同时sonarqube会标记为严重错误:属性 is marked "javax.validation.constraints.NotNull" but is not initialized in this constructor.
如果项目使用SpringData with JPA,可以使用org.springframework.data.annotation.PersistenceConstructor解决(Spring注解,不是JPA!)
然后结合Lombok,注解会变成这样:
@RequiredArgsConstructor(onConstructor = @__(@PersistenceConstructor))
对于 Lombok 构建器,您还需要添加:
@Builder
@AllArgsConstructor
看来注解顺序在这里很重要,使用相同的注解,但顺序不同,你可以让代码工作,也可以不工作。
这是一个无效的例子:
@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 注释放在最顶部,在我的例子中,我遇到此错误是因为我想按字母顺序对注释进行排序。
使用 @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
我使用所有这些注释解决了这个问题:
@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@NoArgsConstructor(access = AccessLevel.PACKAGE)
要使用以下组合
- 龙目岛
- JPA
- 增删改查
- 适当
@EqualsAndHashCode
- 不变性 - public 最终字段
- 没有吸气剂
- 没有二传手
- 通过
@Builder
和@With
更改
我用过:
//Lombok & JPA
//
//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 -
//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;
}
Jeff 的回答很好,但是@Builder 还不支持自引用关系。
查看此问题了解更多详情: