web-dev-qa-db-fra.com

Lombok @Builder et le constructeur par défaut JPA

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

57
krzakov

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());
    }
}
62
Jeff

Vous pouvez aussi le résoudre explicitement avec @Data @Builder @NoArgsConstructor @AllArgsConstructor combiné sur la définition de la classe.

59
John John Pichler

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.

4
Karl.S

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
1
Pavel V

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

0
Amrut Prabhu