龙目岛@Builder and jPA 缺省构造函数

我正在使用龙目岛项目和 Spring Data jPA。 有没有办法把龙目岛 ABc0和 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

103916 次浏览

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.

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.

Important

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

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 @Tolerate and @Data:

Using @Tolerate worked to allow adding a noarg constructor.

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

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

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

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

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

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.

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

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

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

For Lombok builder you also need to add:

@Builder
@AllArgsConstructor

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.

Using @NoArgsConstructor and @AllArgsContructor will help solve the issue of having a default constructor with @Builder.

e.g

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

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

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

i solved this using all these annotations:

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

To use the following combination

  • lombok
  • JPA
    • CRUD
    • proper @EqualsAndHashCode
  • immutability - public final fields
  • no getters
  • no setters
  • changes via @Builder and @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;
}

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

Check this question for more details:

JPA @OnetoOne self reference relationship with both columns non null