Автор оригинала: 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 Listcomments = 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.
Теперь давайте выберем наши сущности, чтобы пользователь мог изменять их, пока они находятся в отключенном состоянии:
Listposts = 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.