Рубрики
Без рубрики

Массовое обновление и удаление API критериев JPA

Узнайте, как создавать массовые запросы на обновление и удаление с помощью API-запросов JPA 2.1 для обновления критериев и определения критериев.

Автор оригинала: 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 предлагает класс Обновление критериев .

Чтобы узнать, как работает Обновление критериев , ознакомьтесь со следующим примером, который мы будем использовать для маркировки сообщений со спамом:

public  int 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 , ознакомьтесь со следующим примером, который мы будем использовать для удаления старых спам-сообщений:

public  int 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.

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