Lombok @Builder and JPA Default constructor
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 @Setter
annotation.
Important
Remember to properly override toString
, equals
, and hashCode
.See the following posts by Vlad Mihalcea for details:
- 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.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:
- 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.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.
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@ToStringpublic class Person implements Serializable { private String name;}
And this is a working example:
@Builder@Data@Entity@EqualsAndHashCode@AllArgsConstructor@NoArgsConstructor@RequiredArgsConstructor@Table@ToStringpublic 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.