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

Как оптимизировать операцию слияния с помощью обновления во время пакетной обработки с помощью JPA и гибернации

Автор оригинала: Vlad Mihalcea.

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

В этой статье вы увидите недостаток перехода состояния слияния сущности и то, как вы можете справиться с ним с помощью Hibernate.

Для предстоящих тестовых случаев мы будем использовать следующие объекты:

Сущность Сообщение имеет двунаправленную связь @OneToMany с сущностью комментария к сообщению .

Сущность Комментарий к сообщению является владельцем двунаправленной ассоциации, а связь @ManyToOne извлекается лениво, потому что НЕТЕРПЕЛИВАЯ выборка-это запах кода .

@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Post post;

    private String review;

    //Constructors, getters and setters omitted for brevity
}

Итак, Сообщение сущность имеет сопоставленную |/@OneToMany ассоциацию:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @OneToMany(
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List comments = new ArrayList<>();

    //Constructors, getters, and setters omitted for brevity

    public void addComment(PostComment comment) {
        comments.add(comment);
        comment.setPost(this);
    }
}

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

Давайте предположим, что мы сохраняем в нашей базе данных следующие объекты:

for (int i = 0; i < 3; i++) {
    Post post = new Post(
        String.format(
            "High-Performance Java Persistence, Part no. %d", 
            i
        )
    );
    post.addComment(
        new PostComment("Excellent")
    );
    entityManager.persist(post);
}

Если мы включим пакетное обновление на уровне конфигурации гибернации:

properties.put("hibernate.jdbc.batch_size", "5");
properties.put("hibernate.order_inserts", "true");
properties.put("hibernate.order_updates", "true");
properties.put("hibernate.jdbc.batch_versioned_data", "true");

Затем Hibernate выдает следующие инструкции SQL INSERT:

Query:[
    "insert into post (title, id) values (?, ?)"
], 
Params:[
    (High-Performance Java Persistence, Part no. 0, 1), 
    (High-Performance Java Persistence, Part no. 1, 3), 
    (High-Performance Java Persistence, Part no. 2, 5)
]

Query:[
    "insert into post_comment (post_id, review, id) values (?, ?, ?)"
], 
Params:[
    (1, Excellent, 2), 
    (3, Excellent, 4), 
    (5, Excellent, 6)
]

Как вы можете видеть, мы активировали пакетные обновления Hibernate, которые также работают для инструкций INSERT, UPDATE и DELETE.

Теперь давайте выберем наши сущности, чтобы пользователь мог изменять их, пока они находятся в отключенном состоянии:

List posts = doInJPA(entityManager -> {
    return entityManager.createQuery(
        "select distinct p " +
        "from Post p " +
        "join fetch p.comments ", Post.class)
    .setHint( QueryHints.PASS_DISTINCT_THROUGH, false )
    .getResultList();
});

for ( Post post: posts ) {
    post.setTitle( 
        "Vlad Mihalcea's " + post.getTitle() 
    );

    for ( PostComment comment: post.getComments() ) {
        comment.setReview( 
            comment.getReview() + " read!" 
        );
    }
}

Подсказка PASS_DISTINCT_THROUGH запроса указывает Hibernate использовать ключевое слово distinct JPQL для дедупликации ссылок на сущности, избегая при этом передачи его в фактический запрос SQL SELECT:

SELECT  p.id AS id1_0_0_,
        c.id AS id1_1_1_,
        p.title AS title2_0_0_,
        c.post_id AS post_id3_1_1_,
        c.review AS review2_1_1_,
        c.post_id AS post_id3_1_0__,
        c.id AS id1_1_0__
FROM    post p
INNER JOIN 
        post_comment c ON p.id = c.post_id

Даже если Сообщение и Комментарий к сообщению сущности были изменены, инструкция SQL не выдается, если только сущности не присоединены к активному контексту сохранения. Для этого у нас есть варианты:

  • Мы можем вызвать операцию JPA слияние , которая выбирает последний снимок сущности и копирует состояние отделенной сущности во вновь выбранную сущность.
  • Или мы можем вызвать операцию Hibernate update , которая направлена на повторное подключение объекта, не требуя дополнительного запроса SELECT.

При попытке выполнить операцию JPA слияние :

doInJPA(entityManager -> {
    for ( Post post: posts ) {
        entityManager.merge( post );
    }
});

Hibernate генерирует следующие инструкции:

SELECT  p.id AS id1_0_1_,
        p.title AS title2_0_1_,
        c.post_id AS post_id3_1_3_,
        c.id AS id1_1_3_,
        c.id AS id1_1_0_,
        c.post_id AS post_id3_1_0_,
        c.review AS review2_1_0_
FROM    post p
LEFT OUTER JOIN 
        post_comment c ON p.id = c.post_id
WHERE   p.id = 1

SELECT  p.id AS id1_0_1_,
        p.title AS title2_0_1_,
        c.post_id AS post_id3_1_3_,
        c.id AS id1_1_3_,
        c.id AS id1_1_0_,
        c.post_id AS post_id3_1_0_,
        c.review AS review2_1_0_
FROM    post p
LEFT OUTER JOIN 
        post_comment c ON p.id = c.post_id
WHERE   p.id = 3

SELECT  p.id AS id1_0_1_,
        p.title AS title2_0_1_,
        c.post_id AS post_id3_1_3_,
        c.id AS id1_1_3_,
        c.id AS id1_1_0_,
        c.post_id AS post_id3_1_0_,
        c.review AS review2_1_0_
FROM    post p
LEFT OUTER JOIN 
        post_comment c ON p.id = c.post_id
WHERE   p.id = 5

Query:[
    "update post set title=? where id=?"], 
Params:[
    (Vlad Mihalcea's High-Performance Java Persistence, Part no. 0, 1), 
    (Vlad Mihalcea's High-Performance Java Persistence, Part no. 1, 3), 
    (Vlad Mihalcea's High-Performance Java Persistence, Part no. 2, 5)
]

Query:[
    "update post_comment set post_id=?, review=? where id=?"
], 
Params:[
    (1, Excellent read!, 2), 
    (3, Excellent read!, 4), 
    (5, Excellent read!, 6)
]

Помимо ожидаемых операторов UPDATE , которые были правильно сгруппированы, мы видим 3 дополнительных оператора SELECT с ЛЕВЫМ ВНЕШНИМ СОЕДИНЕНИЕМ между строками Post и PostComment таблицы.

Это нежелательно, так как у нас могут быть сотни таких сущностей, и для каждой из них потребуется отдельный SQL-запрос для операции слияние .

При использовании специфичной для гибернации Сессии |/обновления операции:

doInJPA(entityManager -> {
    Session session = entityManager.unwrap( Session.class );
    for ( Post post: posts ) {
        session.update( post );
    }
});

Hibernate генерирует только инструкции UPDATE SQL:

Query:[
    "update post set title=? where id=?"], 
Params:[
    (Vlad Mihalcea's High-Performance Java Persistence, Part no. 0, 1), 
    (Vlad Mihalcea's High-Performance Java Persistence, Part no. 1, 3), 
    (Vlad Mihalcea's High-Performance Java Persistence, Part no. 2, 5)
]

Query:[
    "update post_comment set post_id=?, review=? where id=?"
], 
Params:[
    (1, Excellent read!, 2), 
    (3, Excellent read!, 4), 
    (5, Excellent read!, 6)
]

Намного лучше!

В то время как операция слияния безопаснее, так как она анализирует кэш транзакций с последующей записью 1-го уровня и будет работать, даже если у нас уже есть эта сущность, подключенная в текущем контексте сохранения, операция обновление намного эффективнее для пакетной обработки сущности .

Одно предостережение, при котором обновление не подходит, заключается в использовании менее оптимистичной блокировки версии , поскольку оператор SELECT будет выпущен в любом случае. Это связано с тем, что Hibernate нуждается в разобранном состоянии для предоставления значений свойств времени загрузки, используемых в качестве предикатов предложения WHERE.