У меня есть родительский объект с некоторыми дочерними объектами. При сохранении нового родительского объекта и его дочерних объектов 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 да
Затем предоставьте полное сопоставление между сущностями




Вы используете 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! Итак, каковы плюсы и минусы этого метода по сравнению с моим исходным методом (т.е. сохранение дочернего элемента напрямую по сравнению с сохранением родителя, а затем каскадное сохранение дочернего элемента)?
Минусов у этого подхода нет — то, как вы сопоставили свои сущности, предполагает, что вы используете именно эти шаги. Добавление в коллекцию необходимо, только если вы не используете mappedBy.
Ладно, понял. Тогда есть ли способ избежать SELECT, когда нет mappedBy (т.е. одно направление)?
Нет, как упомянул @Andriy Slobodyanyk в другом ответе, Set должен проверить, существует ли уже добавленный элемент в коллекции. Для этого ему необходимо загрузить другие элементы. Единственное решение — переключиться на другой тип коллекции — список (который использует PersistedBag, если элементы не упорядочены в БД). А также, как упоминал @Andriy Slobodyanyk, будьте осторожны с наборами, поскольку hashCode(), который зависит от идентификаторов, может привести к неприятным ошибкам, когда эти идентификаторы генерируются после добавления элементов в набор.
@Stanislav_Bashkyrtsev Я попробовал ваш метод, он работает для существующего MyParent, когда я пытаюсь сохранить новый MyChild, но если родитель также новый, я получаю сообщение об ошибке при сохранении дочернего элемента, говорящего о нарушении ключа внешней ссылки из базы данных, потому что родитель не еще не спасли. Как бы вы решили это?
Упомянутая вами ошибка подразумевает, что вы не установили поле MyChild.parent. Вам нужно установить его, но также вам нужно либо persist(parent) перед сохранением дочернего элемента, либо настроить каскад.
Есть ли связь от ребенка к родителю?