Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lombok @Builder and JPA Default constructor

I'm using project Lombok together with Spring Data JPA. Is there any way to connect Lombok @Builder with JPA default constructor?

Code:

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

As far as I know JPA needs default constructor which is overriden by @Builder annotation. Is there any workaround for that?

This code gives me error: org.hibernate.InstantiationException: No default constructor for entity: : app.domain.model.Person

like image 886
krzakov Avatar asked Dec 12 '15 15:12

krzakov


People also ask

Does JPA need default constructor?

The short answer is no. It is not required to be empty. However, you need to be aware that when the JPA implementation materializes an instance from persistence, it will do things to the instance after the no-args constructor completes.

What is difference between @builder and SuperBuilder?

The @SuperBuilder annotation produces complex builder APIs for your classes. In contrast to @Builder , @SuperBuilder also works with fields from superclasses. However, it only works for types. Most importantly, it requires that all superclasses also have the @SuperBuilder annotation.

What does @builder do in Lombok?

When we annotate a class with @Builder, Lombok creates a builder for all instance fields in that class. We've put the @Builder annotation on the class without any customization. Lombok creates an inner static builder class named as StudentBuilder. This builder class handles the initialization of all fields in Student.

What is @builder annotation in Lombok?

Lombok's @Builder annotation is a useful technique to implement the builder pattern that aims to reduce the boilerplate code. In this tutorial, we will learn to apply @Builder to a class and other useful features. Ensure you have included Lombok in the project and installed Lombok support in the IDE.


3 Answers

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:

  • 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());
    }
}
like image 180
Jeff Avatar answered Oct 17 '22 14:10

Jeff


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

like image 92
John John Pichler Avatar answered Oct 17 '22 14:10

John John Pichler


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.

like image 12
Karl.S Avatar answered Oct 17 '22 15:10

Karl.S