Сохранение объекта OneToMany со сгенерированными идентификаторами в Spring Boot - PullRequest
0 голосов
/ 03 февраля 2020

У меня есть объект, Вопрос , который я хочу сохранить в базе данных. На каждый вопрос ссылаются некоторые Ответы через поле questionId .

Оба объекта имеют поле идентификатора, которое генерируется автоматически при сохранении. Вот упрощенный код для сущностей:

Вопрос. java

@Data
@Entity
public class Question {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    @Size(max=1000, message="Text too long")
    @NotNull(message="Field text cannot be null")
    private String text;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name="id", referencedColumnName = "questionId")
    private List<Answer> answers;
}

Ответ. java

@Data
@Entity
public class Answer {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    @Size(max=255, message="Text too long")
    @NotNull(message="Field text cannot be null")
    private String text;

    @NotNull(message="Field questionId cannot be null")
    private Integer questionId;
}

Итак, чтобы сохранить объект Вопроса с несколькими ответами, я создаю эти объекты без поля идентификатора, которое будет сгенерировано автоматически. Объект Question сериализуется из JSON в QuestionController :

@RestController
@RequestMapping(value="/questions")
public class QuestionController {
    @Autowired
    private QuestionRepository questionRepository;

    @PostMapping
    public void createQuestion(@RequestBody Question question){
        questionRepository.save(question);
    }
}

Проблема заключается в том, что я также должен оставить поле questionId пустым, так как Я не знаю, пока это не записано в базу данных. Это приводит к тому, что транзакция выдает ошибку, запрашивая значение для этого поля.

К настоящему времени единственное решение, которое я нашел, - это удалить ответы из вопроса и, как только он будет сохранен, заполнить значения questionId и сохранить Ответы отдельно.

Есть ли способ совершить эти транзакции сразу?

Ответы [ 3 ]

0 голосов
/ 03 февраля 2020

Вы должны разделить концепцию сущности и DTO (объект передачи данных).

Например, вы можете создать новый объект QuestionDTO в качестве основного тела вашего метода в вашем контроллере. После анализа и проверки вашей платформой вы конвертируете (см. Org.springframework.core.convert.converter.Converter) его в объект сущности. После этого вы можете безопасно сохранить свою сущность.

@Data
public class QuestionDTO {

    @Size(max=1000, message="Text too long")
    @NotNull(message="Field text cannot be null")
    private String text;

    private List<AnswerDTO> answers;
}
@Data
public class AnswerDTO {

    @Size(max=255, message="Text too long")
    @NotNull(message="Field text cannot be null")
    private String text;

}
@RestController
@RequestMapping(value="/questions")
public class QuestionController {
    @Autowired
    private QuestionRepository questionRepository;

    @PostMapping
    public void createQuestion(@RequestBody QuestionDTO questionDTO){
        Question question = convert(questionDTO);
        questionRepository.save(question);
    }
}
0 голосов
/ 03 февраля 2020

Учитывая мой опыт, вы просто неправильно моделируете свои сущности. Как сказано в другом комментарии, важно знать, какими будут ваши варианты использования. Например, когда вы моделируете Вопрос / Ответ, будет много случаев, когда вы будете использовать свой Вопрос, и только Вопрос, и использовать отношение с Ответом для их отображения. Но при доступе к вопросу из его ответа это вряд ли произойдет ...

Итак, как вы это делаете? Вы слышали понятие Агрегат Root и Объект стоимости? Это довольно легко. Вот как вы можете спроектировать свои отношения Вопрос / Ответ:

  1. Создайте ответ класса и используйте аннотацию @Embeddable. Таким образом, hibernate не будет пытаться создать таблицу для хранения ваших ответов, но вы можете использовать это, чтобы помочь отображению hibernate наилучшим образом.
@Data
@Embeddable
public class Answer {
    private String text;
}

Вот и все, это все, что вам нужно для представления объекта значения Answer.

Создайте свою сущность Question со списком Answers (обратите внимание, я удалил аннотацию к вашим ограничениям, но не стесняйтесь их использовать):
@Data
@Entity
public class Question {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Integer id;

    private String text;

    @ElementCollection(fetch = FetchType.EAGER)
    @AttributeOverride(name = "text", column = @Column(name = "answer_text"))
    private List<Answer> answers;
}
  • @ElementCollection(fetch = FetchType.EAGER) для Hibernate обязательно знать, что ему нужно будет хранить список Answer, таким образом, создается таблица для хранения бесконечного числа ответов на ваш вопрос.
  • @AttributeOverride(name = "text", column = @Column(name = "answer_text")) здесь, чтобы сказать Спящий режим: сохраните значение атрибута text моего Answer класса и сохраните его в столбце с именем answer_text.

Что делать дальше? Создайте Controller и Repository и попробуйте опубликовать вопрос.

@RestController
public class QuestionController {

    @Autowired
    private QuestionRepository repository;

    @PostMapping("/questions")
    public void postQuestion(@RequestBody Question question) {
        repository.save(question);
    }

    @GetMapping("/questions")
    public Iterable<Question> questions() {
        return repository.findAll();
    }
}
public interface QuestionRepository extends CrudRepository<Question, Integer> {}

И, наконец, опубликуйте свой вопрос:

{
    "text": "quetion text", 
    "answers": [{
        "text": "answer1"
    }, {
        "text": "answer2"
    }]
}

Это создаст ваш вопрос с его список ответов.

Даже если с плюсами / минусами я перечислю ниже, я все же думаю, что это лучшее решение / компромисс, который вы можете иметь:

Плюсы

  • Простота настройки
  • Вам не нужно управлять связью между Question и Answer
  • Когда вы получаете вопрос, это также извлечение соответствующего Answer.

Минусы

  • Вы не можете изменить конкретный ответ на конкретный c вопрос (но вы когда-нибудь?)
  • У вас нет таблицы, отражающей все ответы, которые у вас есть в вашей базе данных (но еще раз, вы когда-нибудь)

Учитывая базовое использование c попробуйте это.

Только чтобы вы поняли, вот таблицы, которые Hibernate создает с помощью этого решения:

Hibernate: создать таблицу q uestion (целое число идентификатора, сгенерированное по умолчанию как идентификатор (начинается с 1), текст varchar (255), первичный ключ (id)) Hibernate: создать таблицу question_answers (целое число question_id не нулевое, answer_text varchar (255))

Всего 2 таблицы, одна вопрос и одна ** question_answers * * ...

0 голосов
/ 03 февраля 2020

Подумайте о том, каким будет типичный рабочий процесс:

Сначала вы создаете Вопрос (любой, вероятно, сохраняющий его) с пустым массивом ответов.

Затем вы добавляете ответы по одному один, и вопрос уже был сохранен и, следовательно, имеет идентификатор. Вместо @NotNull для questionId я бы лучше добавил @ManyToOne, который должным образом инструктирует hibernate о том, как использовать поле.

Но даже не сохраняя сначала вопрос, поскольку вы добавляете каскадный тип, hibernate должен разрешиться проблема правильно, как только вы позвоните сохранить на вопрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...