Спящий режим генерирует SELECT перед INSERT для новых дочерних объектов

У меня есть родительский объект с некоторыми дочерними объектами. При сохранении нового родительского объекта и его дочерних объектов Spring может вызывать persist, и я мог видеть, что сгенерированы только некоторые операторы INSERT.

Проблема заключается в том, что когда я добавляю несколько новых дочерних объектов к существующему родительскому объекту и сохраняю его, вместо этого Spring вызывает merge, и я обнаружил, что для этих новых дочерних объектов были созданы некоторые операторы SELECT перед их операторами INSERT.

Как избежать этих дополнительных операторов SELECT?

например

@Entity
public class MyParent {
    ...
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade = CascadeType.ALL)
    private Set<MyChild> children;
}

@Entity
public class MyChild {
    ...        
}

и

MyParent myParent = buildNewParentAndChildren();
myParentRepository.save(myParent); // deep down calls persist(), generates INSERT only

но

MyParent myParent = myParentRepository.findById(1);
MyChild myChild = buildNewChild();
myParent.getMyChildren().add(myChild);
myParentRepository.save(myParent); // deep down calls merge(), generates SELECT and INSERT

Есть ли связь от ребенка к родителю?

XtremeBaumer 27.10.2022 12:35

@XtremeBaumer да

user1589188 27.10.2022 13:22

Затем предоставьте полное сопоставление между сущностями

XtremeBaumer 27.10.2022 15:16
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
3
136
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы используете Set детей. Это нормально, но по своему определению он должен проверять, что вновь добавленный дочерний элемент ранее не присутствовал в коллекции. Попробуйте вместо этого использовать List.

Кроме того, я бы посоветовал проверить как правильно реализовать equals() и hashCode() для сущности.

Ответ принят как подходящий

Вы добавляете элемент в коллекцию. Это должно инициализировать ленивую коллекцию, поэтому вы получаете свой SELECT.

Обратите внимание, что вам вообще не нужно работать с коллекцией, чтобы добавить элемент. Вы используете «обратное» отображение, объявляя mappedBy = "myParent". Это означает, что именно дочерний элемент отвечает за сохранение ассоциации со своим родителем.

Итак, что вам нужно:

MyParent myParent = myParentRepository.findById(1);
MyChild myChild = buildNewChild();
myChild.setParent(myParent);
someRepository.persist(myChild)

В следующий раз, когда вы выберете MyParent из другого сеанса Hibernate, вы получите новый элемент в коллекции.

Добавлять в коллекцию имеет смысл только в том случае, если вы собираетесь работать с этим списком в том же запросе. Но это означает, что вам нужен SELECT.

PS: если вы избавитесь от Spring Data в пользу Hibernate Session или JPA EntityManager, вы также сможете избавиться от SELECTing MyParent, используя session.load(1) или entityManager.getRerence(1).

Да, использование репозитория MyChild для сохранения нового myChild действительно пропускает SELECT и генерируется только INSERT! Итак, каковы плюсы и минусы этого метода по сравнению с моим исходным методом (т.е. сохранение дочернего элемента напрямую по сравнению с сохранением родителя, а затем каскадное сохранение дочернего элемента)?

user1589188 27.10.2022 17:27

Минусов у этого подхода нет — то, как вы сопоставили свои сущности, предполагает, что вы используете именно эти шаги. Добавление в коллекцию необходимо, только если вы не используете mappedBy.

Stanislav Bashkyrtsev 27.10.2022 18:29

Ладно, понял. Тогда есть ли способ избежать SELECT, когда нет mappedBy (т.е. одно направление)?

user1589188 28.10.2022 02:40

Нет, как упомянул @Andriy Slobodyanyk в другом ответе, Set должен проверить, существует ли уже добавленный элемент в коллекции. Для этого ему необходимо загрузить другие элементы. Единственное решение — переключиться на другой тип коллекции — список (который использует PersistedBag, если элементы не упорядочены в БД). А также, как упоминал @Andriy Slobodyanyk, будьте осторожны с наборами, поскольку hashCode(), который зависит от идентификаторов, может привести к неприятным ошибкам, когда эти идентификаторы генерируются после добавления элементов в набор.

Stanislav Bashkyrtsev 28.10.2022 10:15

@Stanislav_Bashkyrtsev Я попробовал ваш метод, он работает для существующего MyParent, когда я пытаюсь сохранить новый MyChild, но если родитель также новый, я получаю сообщение об ошибке при сохранении дочернего элемента, говорящего о нарушении ключа внешней ссылки из базы данных, потому что родитель не еще не спасли. Как бы вы решили это?

user1589188 03.11.2022 03:50

Упомянутая вами ошибка подразумевает, что вы не установили поле MyChild.parent. Вам нужно установить его, но также вам нужно либо persist(parent) перед сохранением дочернего элемента, либо настроить каскад.

Stanislav Bashkyrtsev 03.11.2022 04:10

Другие вопросы по теме