Подтвердить что ты не робот

Lombok @Builder и JPA конструктор по умолчанию

Я использую проект Lombok вместе с Spring Data JPA. Есть ли способ подключить Lombok @Builder с конструктором по умолчанию JPA?

код:

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

Насколько я знаю, JPA нуждается в конструкторе по умолчанию, который переопределяется аннотацией @Builder. Есть ли обходной путь для этого?

Этот код дает мне ошибку: org.hibernate.InstantiationException: No default constructor for entity: : app.domain.model.Person

4b9b3361

Ответ 1

обновленный

На основе обратной связи и ответа Джона я обновил ответ, чтобы больше не использовать @Tolerate или @Data и вместо этого мы создаем @Getter и @Setter через @Getter и @Setter, создаем конструктор по умолчанию через @NoArgsConstructor и, наконец, мы создаем все аргументы конструктор, который требуется разработчику через @AllArgsConstructor.

Поскольку вы хотите использовать шаблон построителя, я думаю, вы хотите ограничить видимость методов конструктора и мутаторов. Для достижения этой цели мы устанавливаем видимость package private с помощью access к атрибуту на @NoArgsConstructor и @AllArgsConstructor аннотаций и value атрибута на @Setter аннотации.

Важный

Не забудьте правильно переопределить toString, equals и hashCode. Подробности смотрите в следующих постах Влада Михалча:

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

Старая версия с использованием @Tolerate и @Data :

Использование @Tolerate позволило добавить конструктор noarg.

Поскольку вы хотите использовать шаблон компоновщика, я думаю, вы хотите контролировать видимость методов сеттера.

Аннотация @Data делает сгенерированные сеттеры public, а применение @Setter(value = AccessLevel.PROTECTED) к полям делает их protected.

Не забудьте правильно переопределить toString, equals и hashCode. Подробности смотрите в следующих постах Влада Михалча:

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

Вы также можете решить это явным образом с помощью @Data @Builder @NoArgsConstructor @AllArgsConstructor объединенных в определение класса.

Ответ 3

Кажется, что здесь важен порядок аннотаций, с использованием одних и тех же аннотаций, но разных порядков, вы можете иметь работающий код или нет.

Вот нерабочий пример:

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

И это рабочий пример:

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

Поэтому убедитесь, что аннотация @Builder находится в самой верхней позиции, в моем случае я столкнулся с этой ошибкой, потому что хотел отсортировать аннотации по алфавиту.

Ответ 4

Если аннотации lombok.Tolerate на конструкторе и javax.validation.constraints.NotNull по некоторому свойству используются одновременно, сонарква будет отмечать его как критическую ошибку: СОБСТВЕННОСТЬ отмечена "javax.validation.constraints.NotNull", но не инициализирован в этом конструкторе.

Если проект использует SpringData с JPA, его можно решить, используя org.springframework.data.annotation.PersistenceConstructor(Spring аннотация, а не JPA!)

Затем, в сочетании с Lombok, аннотации будут такими:

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

Для Lombok builder вам также необходимо добавить:

@Builder
@AllArgsConstructor

Ответ 6

Использование @NoArgsConstructor и @AllArgsContructor поможет решить проблему наличия конструктора по умолчанию с @Builder.

Это связано с тем, что @Builder требует использования конструктора всех аргументов, а указание только конструктора по умолчанию вызовет проблему.

Вот объяснение Нор: https://github.com/rzwitserloot/lombok/issues/1389#issuecomment-369404719