Lombok @ Builder und JPA Standardkonstruktor


75

Ich verwende das Projekt Lombok zusammen mit Spring Data JPA. Gibt es eine Möglichkeit, Lombok @Buildermit 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 @BuilderAnmerkungen überschrieben wird . Gibt es dafür eine Problemumgehung?

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



1
Versuchen Sie, einen Konstruktor ohne Argumente hinzuzufügen .AFAIK, @Builderüberschreibt Ihren Konstruktor ohne Argumente nicht
Ken Chan

2
Ja, aber @Id ist ein Pflichtfeld. NoArgs wird es nicht schneiden
krzakov

2
Ich verstehe nicht was du willst. Wie können Sie einen Noargs-Konstruktor haben, der Werte bildet? @Id ist entweder erforderlich oder nicht. Wenn dies der Fall ist, benötigen Sie einen Konstruktorparameter. Wenn nicht, können Sie NoArgs verwenden. Was fehlt mir hier?
Roel Spilker

Antworten:


81

Aktualisiert

Basierend auf dem Feedback und John Antwort habe ich die Antwort auf nicht mehr zu verwenden aktualisiert @Tolerateoder @Datastattdessen wir schaffen Zugriffs- und Mutatoren über @Getterund @Settererstellen Sie den Standard - Konstruktor über @NoArgsConstructor, und schließlich schaffen wir die alle args Konstruktor , dass der Builder erfordert über @AllArgsConstructor.

Da Sie das Builder-Muster verwenden möchten, stellen Sie sich vor, Sie möchten die Sichtbarkeit der Konstruktor- und Mutator-Methoden einschränken. Um dies zu erreichen setzen wir die Sichtbarkeit package privateüber das accessAttribut auf die @NoArgsConstructorund @AllArgsConstructorAnmerkungen und das valueAttribut auf der @SetterAnmerkung.

Wichtig

Denken Sie daran, richtig außer Kraft setzen toString, equalsund hashCode. 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 @Tolerateund @Data:

Verwenden von @Toleratefunktioniert, um das Hinzufügen eines Noarg-Konstruktors zu ermöglichen.

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

Die @DataAnnotation macht die generierten Setter public, die Anwendung @Setter(value = AccessLevel.PROTECTED)auf die Felder macht sie protected.

Denken Sie daran, richtig außer Kraft setzen toString, equalsund hashCode. 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());
    }
}

2
Ich hatte die gleiche Frage wie krzakov und habe sie mit Ihrem Hinweis gelöst @Tolerate. Danke dafür, Jeff. Aber gibt es einen Grund, warum Sie die @DataAnmerkung hinzufügen ? Setter sind in diesem Fall nicht erforderlich und @Dataüberschreiben gleich / hash / toString mit dem Standardverhalten, was zu Problemen führen kann.
Wollodev

Nicht @Datamit Entitäten verwenden.
wst

Wenn dies Bedenken in Bezug auf String, Equals und HashCode hat, habe ich Links zu Dokumenten hinzugefügt, die die ordnungsgemäße Implementierung betreffen.
Jeff

@wst, warum wäre @Dataeine schlechte Idee mit Entitäten?
Srnjak

2
@srnjak @Dataverwendet standardmäßig alle Felder zum Generieren equalsund hashCodeMethoden, einschließlich id. Einfaches Beispiel: Möglicherweise haben Sie vor und nach dem Speichern dieselbe Entitätsdarstellung, bei der es sich aus Java-Sicht um unterschiedliche Instanzen handelt (mit und ohne ID). Dies kann zu Verwirrung und Konsistenzproblemen führen. Sie können verwenden, @Datawenn Sie diese Methoden überschreiben. Es gibt ein Kapitel von Hibernate-Dokumenten darüber: docs.jboss.org/hibernate/orm/5.3/userguide/html_single/…
wst

71

Sie können es auch explizit mit @Data @Builder @NoArgsConstructor @AllArgsConstructorkombiniert in der Klassendefinition lösen .


Beachten Sie, dass dadurch nicht automatisch Zugriffsmethoden (Getter) erstellt werden.
Jeff

@ Jeff dann einfach hinzufügen@Data
Deniss M.

1
Sie möchten @Data nicht verwenden, da dies die Methoden equals, hashCode und toString generiert, die im Fall von jpa-Entitäten von Hand generiert werden sollten. Siehe die Details in meiner Antwort oben.
Jeff

9

Es scheint, dass die Reihenfolge der Anmerkungen hier wichtig ist, wenn dieselben Anmerkungen verwendet werden, aber unterschiedliche Reihenfolge. 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 das 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-Annotation ganz oben befindet. In meinem Fall ist dieser Fehler aufgetreten, weil ich Annotationen alphabetisch sortieren wollte.


7

Wenn die Anmerkungen lombok.Tolerate on constructor und javax.validation.constraints.NotNull für einige Eigenschaften gleichzeitig verwendet werden, markiert sonarqube dies als kritischen Fehler: PROPERTY ist mit "javax.validation.constraints.NotNull" gekennzeichnet, jedoch nicht in diesem Konstruktor initialisiert.

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

In Kombination mit Lombok sehen die Anmerkungen dann folgendermaßen aus:

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

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

@Builder
@AllArgsConstructor

2

Um die folgende Kombination zu verwenden

  • Lombok
  • JPA
    • CRUD
    • richtig @EqualsAndHashCode
  • Unveränderlichkeit - öffentliche Endfelder
  • keine Getter
  • keine Setter
  • Änderungen über @Builderund@With

Ich benutzte:

//Lombok & JPA
///programming/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;
}

1

Die Verwendung von @NoArgsConstructorund @AllArgsContructorhilft bei der Lösung des Problems, einen Standardkonstruktor mit zu haben @Builder.

z.B

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

Dies liegt daran, @Builderdass alle Argumentkonstruktoren erforderlich sind und die Angabe nur eines Standardkonstruktors ein Problem verursacht.

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


1

Ich habe dies mit all diesen Anmerkungen gelöst:

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.