Автор оригинала: Vlad Mihalcea.
Вступление
JPA и Hibernate позволяют нам выполнять массовые запросы на обновление и удаление, чтобы мы могли обрабатывать несколько строк, соответствующих критериям фильтрации бизнес-вариантов использования.
При изменении нескольких записей у вас есть два варианта. Вы можете использовать либо пакетную обработку, либо массовую обработку.
Хотя пакетная обработка полезна, когда сущности уже управляются текущим контекстом сохранения, поскольку она может уменьшить количество выполняемых инструкций INSERT, UPDATE или DELETE, массовая обработка позволяет нам изменять базовые записи базы данных с помощью одной инструкции SQL.
Модель предметной области
Давайте предположим, что наше приложение использует следующие сущности:
Сообщение
или Комментарий к сообщению
Видимость сущности контролируется с помощью Статус сообщения
Java Перечисление
. При первом создании объекты Сообщение
и Комментарий к сообщению
имеют статус ОЖИДАНИЕ
, поэтому они скрыты от пользователя. Для отображения объектов Сообщение
или Комментарий к сообщению
необходимо модерировать. Если модераторы решат, что данная публикация действительна, статус изменится на ОДОБРЕНО
, и запись станет видимой. В противном случае публикация помечается как СПАМ
.
Если вы хотите сохранить Перечисление
свойств, то наиболее компактным типом столбца является самый короткий доступный целочисленный тип столбца.
Для получения более подробной информации о преимуществах и недостатках различных Перечисляющих
сохраняющихся стратегий ознакомьтесь с этой статьей .
Чтобы поделиться свойством статус
, сущности Сообщение
и Комментарий к сообщению
расширяют базовый класс | @MappedSuperclass
, который выглядит следующим образом:
@MappedSuperclass public abstract class PostModerate{ @Enumerated(EnumType.ORDINAL) @Column(columnDefinition = "smallint") private PostStatus status = PostStatus.PENDING; @Column(name = "updated_on") private Date updatedOn = new Date(); public PostStatus getStatus() { return status; } public T setStatus(PostStatus status) { this.status = status; return (T) this; } public Date getUpdatedOn() { return updatedOn; } public T setUpdatedOn(Date updatedOn) { this.updatedOn = updatedOn; return (T) this; } }
Таким образом, объект Post
будет выглядеть следующим образом:
@Entity(name = "Post") @Table(name = "post") public class Post extends PostModerate{ @Id private Long id; private String title; private String message; public Long getId() { return id; } public Post setId(Long id) { this.id = id; return this; } public String getTitle() { return title; } public Post setTitle(String title) { this.title = title; return this; } public String getMessage() { return message; } public Post setMessage(String message) { this.message = message; return this; } }
И, Комментарий к сообщению
дочерняя сущность будет выглядеть следующим образом:
@Entity(name = "PostComment") @Table(name = "post_comment") public class PostComment extends PostModerate{ @Id private Long id; @ManyToOne(fetch = FetchType.LAZY) private Post post; private String message; public Long getId() { return id; } public PostComment setId(Long id) { this.id = id; return this; } public Post getPost() { return post; } public PostComment setPost(Post post) { this.post = post; return this; } public String getMessage() { return message; } public PostComment setMessage(String message) { this.message = message; return this; } }
По умолчанию @ManyToOne
и @OneToOne
ассоциации используют Тип выборки.НЕТЕРПЕЛИВАЯ
стратегия выборки, которая очень плохо влияет на производительность и может привести к N+1 проблемам с запросами .
Для получения более подробной информации ознакомьтесь с этой статьей .
Теперь давайте добавим некоторые сущности в нашу систему:
entityManager.persist( new Post() .setId(1L) .setTitle("High-Performance Java Persistence") .setStatus(PostStatus.APPROVED) ); entityManager.persist( new Post() .setId(2L) .setTitle("Spam title") ); entityManager.persist( new Post() .setId(3L) .setMessage("Spam message") ); entityManager.persist( new PostComment() .setId(1L) .setPost(entityManager.getReference(Post.class, 1L)) .setMessage("Spam comment") );
Итак, теперь у нас есть Сообщение
объект со статусом ОДОБРЕНО
, два Сообщения
объекта и один Комментарий к сообщению
дочерний объект со статусом ОЖИДАЮЩИЙ
и содержащий информацию о спаме.
Массовое обновление с помощью JPA и гибернации
Чтобы пометить как спам все записи Post
, содержащие информацию о спаме, мы можем использовать следующую инструкцию массового обновления JPQL:
int updateCount = entityManager.createQuery(""" update Post set updatedOn = CURRENT_TIMESTAMP, status = :newStatus where status = :oldStatus and ( lower(title) like :spamToken or lower(message) like :spamToken ) """) .setParameter("newStatus", PostStatus.SPAM) .setParameter("oldStatus", PostStatus.PENDING) .setParameter("spamToken", "%spam%") .executeUpdate(); assertEquals(2, updateCount);
При выполнении вышеупомянутого JPQL-запроса Hibernate генерирует следующую инструкцию SQL:
UPDATE post SET updated_on = CURRENT_TIMESTAMP, status = 2 WHERE status = 0 AND ( lower(title) LIKE '%spam%' OR lower(message) LIKE '%spam%' )
Чтобы модерировать Комментарий к сообщению
сущности, мы можем использовать следующую инструкцию массового обновления JPQL:
int updateCount = entityManager.createQuery(""" update PostComment set updatedOn = CURRENT_TIMESTAMP, status = :newStatus where status = :oldStatus and lower(message) like :spamToken """) .setParameter("newStatus", PostStatus.SPAM) .setParameter("oldStatus", PostStatus.PENDING) .setParameter("spamToken", "%spam%") .executeUpdate(); assertEquals(1, updateCount);
И Hibernate сгенерирует ожидаемый SQL-запрос на массовое обновление:
UPDATE post_comment SET updated_on = CURRENT_TIMESTAMP, status = 2 WHERE status = 0 AND lower(message) LIKE '%spam%'
Массовое удаление с помощью JPA и гибернации
Чтобы удалить все объекты Post
, которые были помечены как спам и которые старше 7 дней, мы можем использовать следующую инструкцию массового удаления JPQL:
int deleteCount = entityManager.createQuery(""" delete from Post where status = :status and updatedOn <= :validityThreshold """) .setParameter("status", PostStatus.SPAM) .setParameter( "validityThreshold", Timestamp.valueOf( LocalDateTime.now().minusDays(7) ) ) .executeUpdate(); assertEquals(2, deleteCount);
И, чтобы удалить все Комментарии к публикации
сущности, которые были помечены как спам и которые старше 3 дней, мы можем использовать следующую инструкцию массового удаления JPQL:
int deleteCount = entityManager.createQuery(""" delete from PostComment where status = :status and updatedOn <= :validityThreshold """) .setParameter("status", PostStatus.SPAM) .setParameter( "validityThreshold", Timestamp.valueOf( LocalDateTime.now().minusDays(3) ) ) .executeUpdate(); assertEquals(1, deleteCount);
Вот и все!
Вывод
Операторы массового обновления и удаления очень полезны всякий раз, когда мы хотим обработать некоторые записи, которые могут быть отфильтрованы с использованием одного и того же предиката.
Инструкции JPQL bulk и update очень похожи на инструкции SQL, и производительность может быть лучше, чем если бы вы вместо этого использовали пакетирование.