web-dev-qa-db-de.com

Lombok @Builder und JPA Standardkonstruktor

Ich benutze das Projekt Lombok zusammen mit Spring Data JPA. Gibt es eine Möglichkeit, Lombok @Builder Mit dem JPA-Standardkonstruktor zu verbinden?

Code:

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

Soweit ich weiß, benötigt JPA einen Standardkonstruktor, der durch die Annotation @Builder Überschrieben wird. Gibt es eine Problemumgehung dafür?

Dieser Code gibt mir Fehler: org.hibernate.InstantiationException: No default constructor for entity: : app.domain.model.Person

57
krzakov

Aktualisiert

Basierend auf dem Feedback und Johns Antwort Ich habe die Antwort so aktualisiert, dass @Tolerate oder @Data nicht mehr verwendet wird. Stattdessen erstellen wir Accessoren und Mutatoren über @Getter und @Setter. Erstellen Sie den Standardkonstruktor über @NoArgsConstructor und Schließlich erstellen wir den Konstruktor all args, den der Builder über @AllArgsConstructor benötigt.

Da Sie das Builder-Muster verwenden möchten, möchten Sie die Sichtbarkeit der Konstruktor- und Mutator-Methoden einschränken. Um dies zu erreichen, setzen wir die Sichtbarkeit auf package private über das Attribut access in den Anmerkungen @NoArgsConstructor und @AllArgsConstructor und das Attribut value in der Anmerkung @Setter.

Wichtig

Denken Sie daran, toString, equals und hashCode ordnungsgemäß zu überschreiben. Siehe die folgenden Beiträge von Vlad Mihalcea für 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());
    }
}

Alte Version mit @Tolerate und @Data:

Die Verwendung von @Tolerate ermöglichte das Hinzufügen eines Noarg-Konstruktors.

Da Sie das Builder-Muster verwenden möchten, stellen Sie sich vor, Sie möchten die Sichtbarkeit der Setter-Methoden steuern.

Mit der Annotation @Data werden die generierten Setter public und durch Anwenden von @Setter(value = AccessLevel.PROTECTED) auf die Felder protected.

Denken Sie daran, toString, equals und hashCode ordnungsgemäß zu überschreiben. Siehe die folgenden Beiträge von Vlad Mihalcea für 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());
    }
}
62
Jeff

Sie können es auch explizit lösen, indem Sie @Data @Builder @NoArgsConstructor @AllArgsConstructor In der Klassendefinition kombinieren.

59

Es scheint, dass die Annotationsreihenfolge hier wichtig ist, wobei dieselben Annotationen, aber unterschiedliche Reihenfolgen verwendet werden. Sie können den Code verwenden oder nicht.

Hier ist ein nicht funktionierendes Beispiel:

@AllArgsConstructor
@Builder
@Data
@Entity
@EqualsAndHashCode
@NoArgsConstructor
@RequiredArgsConstructor
@Table
@ToString
public class Person implements Serializable {
  private String name;
}

Und dies ist ein funktionierendes Beispiel:

@Builder
@Data
@Entity
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
@Table
@ToString
public class Person implements Serializable {
  private String name;
}

Stellen Sie also sicher, dass sich die @ Builder-Anmerkung ganz oben befindet. In meinem Fall ist dieser Fehler aufgetreten, weil ich Anmerkungen alphabetisch sortieren wollte.

4
Karl.S

Wenn die Annotationen lombok.Tolerate on constructor und javax.validation.constraints.NotNull Bei einigen Eigenschaften, die gleichzeitig verwendet werden, wird sonarqube als kritischer Fehler markiert: PROPERTY ist als "javax.validation.constraints.NotNull" markiert, wird jedoch in diesem Konstruktor nicht initialisiert.

Wenn das Projekt SpringData mit JPA verwendet, kann es mit org.springframework.data.annotation.PersistenceConstructor gelöst werden (Spring-Annotation, nicht JPA!)

In Kombination mit Lombok sehen die Anmerkungen dann so aus:

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

Für Lombok Builder müssen Sie außerdem Folgendes hinzufügen:

@Builder
@AllArgsConstructor
1
Pavel V

Mit @NoArgsConstructor Und @AllArgsContructor Können Sie das Problem lösen, dass ein Standardkonstruktor mit @Builder Verwendet wird.

z.B

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

Dies liegt daran, dass für @Builder Der gesamte Argumentkonstruktor erforderlich ist und die Angabe nur eines Standardkonstruktors ein Problem verursacht.

Hier ist keine Erklärung: https://github.com/rzwitserloot/lombok/issues/1389#issuecomment-369404719

0
Amrut Prabhu