Автор оригинала: Vlad Mihalcea.
Вступление
Массовое обновление API критериев JPA удаление-отличная функция, которая позволяет создавать массовые обновления и удалять запросы, используя поддержку API критериев JPA 2.1 через CriteriaUpdate
и CriteriaDelete
.
Поскольку один из членов нашего сообщества спросил меня на форуме Hibernate об этой теме , я решил, что это хорошая возможность написать об этой менее известной функции API критериев JPA.
Массовое обновление и удаление API критериев JPA #java https://t.co/OYHHYgklPQ pic.twitter.com/3PCeoEsArz
Модель предметной области
Предполагая, что в нашей системе есть следующие сущности:
Статус Post
представляет собой Java Перечисление
, в котором указано, должен ли данный Пост
или Комментарий к сообщению
отображаться в нашем приложении. Поскольку все записи Сообщение
и Комментарий к сообщению
проходят модерацию, начальный статус ОЖИДАЕТ
. Если система решит, что данная публикация действительна, статус становится ОДОБРЕНО
и публикация становится видимой. В противном случае публикация помечается как СПАМ
.
Вот причина, по которой и Сообщение
и Комментарий к сообщению
расширяют класс Post Moderate
, который выглядит следующим образом:
@MappedSuperclass public abstract class PostModerate { @Enumerated(EnumType.ORDINAL) @Column(columnDefinition = "tinyint") private PostStatus status = PostStatus.PENDING; @Column(name = "updated_on") private Date updatedOn = new Date(); //Getters and setters omitted for brevity }
Если вы хотите сохранить Перечисление
свойств, то наиболее компактным типом столбца является самый короткий доступный целочисленный тип столбца.
Для получения более подробной информации о преимуществах и недостатках различных Перечисляющих
сохраняющихся стратегий ознакомьтесь с этой статьей .
Объект Post
выглядит следующим образом:
@Entity(name = "Post") @Table(name = "post") public class Post extends PostModerate { @Id @GeneratedValue private Long id; private String title; private String message; //Getters and setters omitted for brevity }
и Комментарий к сообщению
сущность выглядит так:
@Entity(name = "PostComment") @Table(name = "post_comment") public class PostComment extends PostModerate { @Id @GeneratedValue private Long id; @ManyToOne(fetch = FetchType.LAZY) private Post post; private String message; //Getters and setters omitted for brevity }
По умолчанию @ManyToOne
и @OneToOne
ассоциации используют Тип выборки.НЕТЕРПЕЛИВАЯ
стратегия выборки, которая очень плохо влияет на производительность и может привести к N+1 проблемам с запросами .
Для получения более подробной информации ознакомьтесь с этой статьей .
Данные тестирования
Учитывая, что мы добавили в нашу систему следующие объекты:
Post _post = doInJPA(entityManager -> { Post post = new Post(); post.setTitle("High-Performance Java Persistence"); post.setStatus(PostStatus.APPROVED); entityManager.persist(post); return post; }); doInJPA(entityManager -> { Post spamPost1 = new Post(); spamPost1.setTitle("Spam title"); entityManager.persist(spamPost1); Post spamPost2 = new Post(); spamPost2.setMessage("Spam message"); entityManager.persist(spamPost2); PostComment spamComment = new PostComment(); spamComment.setPost(_post); spamComment.setMessage("Spam comment"); entityManager.persist(spamComment); });
Поэтому у нас есть:
- одна
Должность
сущность, котораяУТВЕРЖДЕНА
, но имеет несколькоотправлений
дочерних сущностей, которые имеютОЖИДАЮЩИЙ
статус - другая
Должность
организация, имеющаяОЖИДАЮЩИЙ
статус
Обновление критериев
Для динамического построения запросов всегда следует использовать профессиональный конструктор запросов, такой как API критериев JPA или jOOQ .
Вам никогда не следует прибегать к использованию конкатенации строк для динамического построения запросов, поскольку эта плохая практика подвержена атакам с использованием SQL-инъекций.
Для получения более подробной информации ознакомьтесь с этой статьей .
Для динамического построения операторов обновления SQL JPA предлагает класс Обновление критериев
.
Чтобы узнать, как работает Обновление критериев
, ознакомьтесь со следующим примером, который мы будем использовать для маркировки сообщений со спамом:
publicint flagSpam( EntityManager entityManager, Class postModerateClass) { CriteriaBuilder builder = entityManager .getCriteriaBuilder(); CriteriaUpdate update = builder .createCriteriaUpdate(postModerateClass); Root root = update.from(postModerateClass); Expression filterPredicate = builder .like( builder.lower(root.get("message")), "%spam%" ); if(Post.class.isAssignableFrom(postModerateClass)) { filterPredicate = builder.or( filterPredicate, builder .like( builder.lower(root.get("title")), "%spam%" ) ); } update .set(root.get("status"), PostStatus.SPAM) .set(root.get("updatedOn"), new Date()) .where(filterPredicate); return entityManager .createQuery(update) .executeUpdate(); }
Написание запросов API критериев JPA не очень просто. Плагин Codota IDE может помочь вам в написании таких запросов, что повысит вашу производительность.
Для получения более подробной информации о том, как вы можете использовать Codota для ускорения процесса написания запросов API критериев, ознакомьтесь с этой статьей .
Метод флаг спама
работает следующим образом:
- Во-первых, нам нужно получить
Конструктор критериев
, чтобы мы могли создавать динамические операторы API критериев. - Во-вторых, мы создадим оператор
Обновление критериев
для предоставленногокласса post Moderate
, который может быть любым классом, расширяющимPostModerate
. - Затем мы создадим предикат фильтрации для свойства
message
, который является общим для всех сообщений, которые необходимо модерировать. - Только для
Должности
сущности мы также проверяемназвание
свойство.
Этот пример показывает истинную мощь построителя динамических запросов, поскольку оператор может быть построен таким образом, чтобы он изменялся в зависимости от предоставленных аргументов. Без API критериев вы, вероятно, прибегнете к использованию String
конкатенации и рискуете атаками с использованием SQL – инъекций.
Теперь мы можем протестировать метод помечать спам
следующим образом:
assertEquals(2, flagSpam(entityManager, Post.class)); assertEquals(1, flagSpam(entityManager, PostComment.class));
И Hibernate выполнит следующие инструкции SQL:
UPDATE post SET status = 2, updated_on = '2018-01-09 10:50:42.861' WHERE lower(message) LIKE '%spam%' OR lower(title) LIKE '%spam%' UPDATE post_comment SET status = 2, updated_on = '2018-01-09 10:50:43.07' WHERE lower(message) LIKE '%spam%'
Обратите внимание, как инструкция UPDATE изменяется в зависимости от базового типа сущности. Вот почему Обновление критериев
стоит использовать для динамических операторов массового обновления.
Критерии Удалить
Мало того , что JPA предлагает обновление критериев
, но он также поставляется с утилитой Удаления критериев
для создания динамических операторов массового удаления.
Чтобы увидеть, как работает CriteriaDelete
, ознакомьтесь со следующим примером, который мы будем использовать для удаления старых спам-сообщений:
publicint deleteSpam( EntityManager entityManager, Class postModerateClass) { CriteriaBuilder builder = entityManager .getCriteriaBuilder(); CriteriaDelete delete = builder .createCriteriaDelete(postModerateClass); Root root = delete.from(postModerateClass); int daysValidityThreshold = (Post.class.isAssignableFrom(postModerateClass)) ? 7 : 3; delete .where( builder.and( builder.equal( root.get("status"), PostStatus.SPAM ), builder.lessThanOrEqualTo( root.get("updatedOn"), Timestamp.valueOf( LocalDateTime .now() .minusDays(daysValidityThreshold) ) ) ) ); return entityManager .createQuery(delete) .executeUpdate(); }
На этот раз мы изменяем только параметр, передаваемый предикату фильтрации. Однако вы можете изменить все предложение WHERE при использовании утилиты Удаление критериев
.
Чтобы проверить, как это работает, давайте убедимся, что наши спам-сообщения достаточно старые, чтобы их можно было удалить:
assertEquals(2, entityManager.createQuery( "update Post " + "set updatedOn = :timestamp " + "where status = :status") .setParameter( "timestamp", Timestamp.valueOf(LocalDateTime.now().minusDays(7)) ) .setParameter("status", PostStatus.SPAM) .executeUpdate() ); assertEquals(1, entityManager.createQuery( "update PostComment " + "set updatedOn = :timestamp " + "where status = :status") .setParameter( "timestamp", Timestamp.valueOf(LocalDateTime.now().minusDays(3)) ) .setParameter("status", PostStatus.SPAM) .executeUpdate() );
Хорошо, теперь мы можем запустить метод удалить спам
:
assertEquals(2, deleteSpam(entityManager, Post.class)); assertEquals(1, deleteSpam(entityManager, PostComment.class));
и Hibernate выполнит следующие инструкции по удалению:
DELETE FROM post WHERE status = 2 AND updated_on <= '2018-01-02 10:50:43.109' DELETE FROM post_comment WHERE status = 2 AND updated_on <= '2018-01-06 10:50:43.115'
Вот и все! Вы можете легко создавать динамические инструкции массового обновления и удаления с помощью API критериев.
Вывод
В то время как запрос Критериев
был доступен с JPA 2.0, Обновление критериев
и Критерий
были включены в спецификацию JPA с JPA 2.1.
По этой причине они не очень хорошо известны или признаны. Эта статья доказывает, что они очень полезны, и вам обязательно следует их использовать.