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
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());
}
}
Sie können es auch explizit lösen, indem Sie @Data @Builder @NoArgsConstructor @AllArgsConstructor
In der Klassendefinition kombinieren.
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.
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
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