Что такое лучшая практика для массового удаления в jpa

Я пытаюсь сделать массовое удаление в моих объектах, я думал, что лучшее решение будет идти с CriteriaDelete, Но CriteriaDelete не каскад (по крайней мере, не для меня).

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

Кто-нибудь есть лучшая идея, как сделать массовое удаление? Это действительно лучший способ?

Если это поможет, я использую eclipselink 2.5.2.

3 ответа

Решение

Варианты:

  1. используйте настройку cascade.Remove в отображении, загружая объекты и вызывая em.remove для каждого
  2. Используйте массовое удаление в основном объекте и установите параметр базы данных "ON DELETE CASCADE", чтобы база данных каскадно удаляла вас. EclipseLink имеет аннотацию @CascadeOnDelete, которая позволяет ему знать, что "ON DELETE CASCADE" установлен для отношения, или создать его, если используется JPA для генерации DDL: http://eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_cascadeondelete.htm
  3. Используйте несколько массовых удалений, чтобы удалить дочерние элементы, на которые могут ссылаться перед удалением основного объекта. Например: "Удалить 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(),

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