Что такое лучшая практика для массового удаления в jpa
Я пытаюсь сделать массовое удаление в моих объектах, я думал, что лучшее решение будет идти с CriteriaDelete
, Но CriteriaDelete
не каскад (по крайней мере, не для меня).
Так что, похоже, единственное решение, которое у меня есть, это сначала выбрать и удалить каждый элемент отдельно. Что не кажется мне неправильным.
Кто-нибудь есть лучшая идея, как сделать массовое удаление? Это действительно лучший способ?
Если это поможет, я использую eclipselink 2.5.2.
3 ответа
Варианты:
- используйте настройку cascade.Remove в отображении, загружая объекты и вызывая em.remove для каждого
- Используйте массовое удаление в основном объекте и установите параметр базы данных "ON DELETE CASCADE", чтобы база данных каскадно удаляла вас. EclipseLink имеет аннотацию @CascadeOnDelete, которая позволяет ему знать, что "ON DELETE CASCADE" установлен для отношения, или создать его, если используется JPA для генерации DDL: http://eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_cascadeondelete.htm
- Используйте несколько массовых удалений, чтобы удалить дочерние элементы, на которые могут ссылаться перед удалением основного объекта. Например: "Удалить FROM Child c, где c.parent = (выберите p из Parent P, где [условия удаления])" и "Удалить FROM Parent p, где [условия удаления]". См. Раздел 10.2.4 http://docs.oracle.com/middleware/1212/toplink/OTLCG/queries.htm для получения подробной информации.
Как работает JPA CriteriaDelete
JPA CriteriaDelete
Оператор генерирует оператор массового удаления JPQL, который анализируется на оператор массового удаления SQL.
Итак, следующий JPA CriteriaDelete
заявление:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaDelete<PostComment> delete = builder.createCriteriaDelete(PostComment.class);
Root<T> root = delete.from(PostComment.class);
int daysValidityThreshold = 3;
delete.where(
builder.and(
builder.equal(
root.get("status"),
PostStatus.SPAM
),
builder.lessThanOrEqualTo(
root.get("updatedOn"),
Timestamp.valueOf(
LocalDateTime
.now()
.minusDays(daysValidityThreshold)
)
)
)
);
int deleteCount = entityManager.createQuery(delete).executeUpdate();
генерирует этот запрос на удаление SQL:
DELETE FROM
post_comment
WHERE
status = 2 AND
updated_on <= '2020-08-06 10:50:43.115'
Таким образом, каскада на уровне сущности нет, поскольку удаление выполняется с помощью оператора SQL, а не с помощью EntityManager
.
Массовое каскадное удаление
Чтобы включить каскадирование при выполнении массового удаления, вам необходимо использовать каскад уровня DDL при объявлении ограничений FK.
ALTER TABLE post_comment
ADD CONSTRAINT FK_POST_COMMENT_POST_ID
FOREIGN KEY (post_id) REFERENCES post
ON DELETE CASCADE
Теперь при выполнении следующего оператора массового удаления:
DELETE FROM
post
WHERE
status = 2 AND
updated_on <= '2020-08-02 10:50:43.109'
БД удалит post_comment
записи со ссылкой на post
строки, которые были удалены.
Лучший способ выполнить DDL - использовать инструмент автоматической миграции схемы, такой как Flyway, поэтому определение внешнего ключа должно находиться в сценарии миграции.
Если вы создаете сценарии миграции с помощью инструмента HBM2DLL, то в PostComment
class, вы можете использовать следующее сопоставление для генерации вышеупомянутого оператора DDL:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(foreignKey = @ForeignKey(name = "FK_POST_COMMENT_POST_ID"))
@OnDelete(action = OnDeleteAction.CASCADE)
private Post post;
Дополнительные сведения о запросах массового обновления и удаления с помощью Criteria API см. В этой статье.
Если вы действительно заботитесь о времени, которое требуется для выполнения массового удаления, я предлагаю вам использовать JPQL для удаления ваших сущностей. Когда вы выпускаете DELETE
JPQL-запрос, он напрямую выдаст удаление этих объектов, не извлекая их в первую очередь.
int deletedCount = entityManager.createQuery("DELETE FROM Country").executeUpdate();
Вы даже можете сделать условное удаление на основе некоторых параметров этих объектов, используя API запросов, как показано ниже
Query query = entityManager.createQuery("DELETE FROM Country c
WHERE c.population < :p");
int deletedCount = query.setParameter(p, 100000).executeUpdate();
executeUpdate
вернет количество удаленных строк после завершения операции.
Если у вас есть правильный каскадный тип в ваших сущностях, как CascadeType.ALL
(или же) CascadeType.REMOVE
, тогда приведенный выше запрос поможет вам.
@Entity
class Employee {
@OneToOne(cascade=CascadeType.REMOVE)
private Address address;
}
JPQL BULK DELETE
(будь то использование строкового JPQL или Criteria JPQL) не предназначено для каскадирования (т. е. следуйте настройкам каскадного типа для полей). Если вы хотите использовать каскадирование, вы должны настроить хранилище данных на использование реального FOREIGN KEY
или вы вытаскиваете объекты для удаления и вызываете EntityManager.remove()
,