J'utilise le projet Lombok avec Spring Data JPA. Est-il possible de connecter Lombok @Builder
avec le constructeur par défaut JPA?
Code:
@Entity
@Builder
class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
Autant que je sache, JPA a besoin du constructeur par défaut, qui est remplacé par @Builder
annotation. Y at-il une solution de contournement pour cela?
Ce code me donne une erreur: org.hibernate.InstantiationException: No default constructor for entity: : app.domain.model.Person
Mis à jour
Sur la base des commentaires et de la réponse de John , j'ai mis à jour la réponse afin de ne plus utiliser @Tolerate
Ou @Data
Et de créer des accesseurs. et des mutateurs via @Getter
et @Setter
, créez le constructeur par défaut via @NoArgsConstructor
, et finalement nous créons le constructeur tous arguments que le constructeur requiert via @AllArgsConstructor
.!
Puisque vous souhaitez utiliser le modèle de générateur, j'imagine que vous souhaitez limiter la visibilité des méthodes constructeur et mutators. Pour ce faire, nous avons défini la visibilité sur package private
Via l'attribut access
des annotations @NoArgsConstructor
Et @AllArgsConstructor
Et l'attribut value
du @Setter
Annotation.
Important
N'oubliez pas de remplacer correctement toString
, equals
et hashCode
. Voir les articles suivants de Vlad Mihalcea pour plus de détails:
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());
}
}
Ancienne version utilisant @Tolerate
Et @Data
:
L'utilisation de @Tolerate
A permis d'ajouter un constructeur noarg.
Puisque vous souhaitez utiliser le modèle de générateur, j'imagine que vous souhaitez contrôler la visibilité des méthodes de définition.
L'annotation @Data
Rend les paramètres générés public
, appliquer @Setter(value = AccessLevel.PROTECTED)
aux champs les rend protected
.
N'oubliez pas de remplacer correctement toString
, equals
et hashCode
. Voir les articles suivants de Vlad Mihalcea pour plus de détails:
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());
}
}
Vous pouvez aussi le résoudre explicitement avec @Data @Builder @NoArgsConstructor @AllArgsConstructor
combiné sur la définition de la classe.
Il semble que l'ordre des annotations soit important ici, en utilisant les mêmes annotations, mais des ordres différents, le code peut fonctionner ou non.
Voici un exemple qui ne fonctionne pas:
@AllArgsConstructor
@Builder
@Data
@Entity
@EqualsAndHashCode
@NoArgsConstructor
@RequiredArgsConstructor
@Table
@ToString
public class Person implements Serializable {
private String name;
}
Et ceci est un exemple de travail:
@Builder
@Data
@Entity
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
@Table
@ToString
public class Person implements Serializable {
private String name;
}
Assurez-vous donc que l'annotation @Builder se trouve tout en haut de la hiérarchie. Dans mon cas, j'ai rencontré cette erreur car je souhaitais trier les annotations par ordre alphabétique.
Si les annotations lombok.Tolerate sur le constructeur et javax.validation.constraints.NotNull sur certaines propriétés sont utilisées en même temps, sonarqube la marquera comme une erreur critique: PROPERTY est marqué "javax.validation.constraints.NotNull" mais n'est pas initialisé dans ce constructeur.
Si le projet utilise SpringData avec JPA, vous pouvez le résoudre en utilisant org.springframework.data.annotation.PersistenceConstructor (annotation Spring, pas JPA!)
Ensuite, en combinaison avec Lombok, les annotations seront comme ceci:
@RequiredArgsConstructor(onConstructor = @__(@PersistenceConstructor))
Pour le constructeur Lombok, vous devez également ajouter:
@Builder
@AllArgsConstructor
En utilisant @NoArgsConstructor
et @AllArgsContructor
aidera à résoudre le problème de la présence d’un constructeur par défaut avec @Builder
.
par exemple
@Entity
@Builder
@NoArgsConstructor
@AllArgsContructor
class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
Ceci est dû au fait @Builder
requiert tous les constructeurs d'arguments et la spécification d'un constructeur par défaut entraînera un problème.
Voici plus d'explications: https://github.com/rzwitserloot/lombok/issues/1389#issuecomment-369404719