Автор оригинала: Vlad Mihalcea.
Вступление
В этой статье я собираюсь объяснить, как работают операции сохранения и слияния сущностей при использовании JPA и гибернации.
При использовании JPA переходы состояния сущности автоматически преобразуются в операторы SQL. В этом посте будет объяснено, когда использовать сохраняться и когда использовать слияние .
Настаивать
Операция persist должна использоваться только для новых сущностей. С точки зрения JPA, сущность является новой, если она никогда не была связана со строкой базы данных, что означает, что в базе данных нет записи таблицы, соответствующей рассматриваемой сущности.
Например, при выполнении следующего тестового случая:
Post post = new Post();
post.setTitle("High-Performance Java Persistence");
entityManager.persist(post);
LOGGER.info("The post entity identifier is {}", post.getId());
LOGGER.info("Flush Persistence Context");
entityManager.flush();
Hibernate собирается присоединить объект Post к текущему контексту сохранения. Оператор INSERT SQL может быть выполнен непосредственно или отложен до времени сброса .
ИДЕНТИЧНОСТЬ
Если сущность использует генератор идентификаторов:
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
ВСТАВКА выполняется сразу же, и Hibernate генерирует следующий вывод:
INSERT INTO post (id, title) VALUES (DEFAULT, 'High-Performance Java Persistence') -- The Post entity identifier is 1 -- Flush Persistence Context
Всякий раз, когда сущность сохраняется, Hibernate должен присоединять ее к текущему контексту сохранения, который действует как Карта сущностей. Ключ Map формируется из типа сущности (ее Java Класса ) и идентификатора сущности.
Для столбцов IDENTITY единственный способ узнать значение идентификатора-выполнить ВСТАВКУ SQL. Следовательно, ВСТАВКА выполняется при вызове метода persist и не может быть отключена до времени сброса.
По этой причине Hibernate отключает пакетные вставки JDBC для сущностей, использующих стратегию ИДЕНТИФИКАЦИЯ генератор.
ПОСЛЕДОВАТЕЛЬНОСТЬ
При использовании стратегии ПОСЛЕДОВАТЕЛЬНОСТИ идентификатора и повторном запуске того же примера Hibernate генерирует следующий вывод:
CALL NEXT VALUE FOR 'hibernate_sequence'
-- The post entity identifier is 1
-- Flush Persistence Context
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence', 1)
На этот раз оператор INSERT может быть отложен до времени сброса, и Hibernate может применить оптимизацию пакетной вставки, если вы зададите свойство конфигурации размера пакета.
Стратегия ТАБЛИЦА ведет себя как ПОСЛЕДОВАТЕЛЬНОСТЬ , но вам следует избегать этого любой ценой, поскольку для создания идентификатора сущности используется отдельная транзакция, что оказывает давление на базовый пул соединений и журнал транзакций базы данных.
Что еще хуже, блокировки на уровне строк используются для координации нескольких одновременных запросов, и, как говорит нам Закон Амдала , введение сериализуемого выполнения может повлиять на масштабируемость.
Для получения более подробной информации о том, почему вам следует избегать ТАБЛИЦЫ стратегии, ознакомьтесь с этой статьей .
Поглощать
Слияние требуется только для отдельных объектов.
Предполагая, что у нас есть следующая сущность:
Post post = doInJPA(entityManager -> {
Post _post = new Post();
_post.setTitle("High-Performance Java Persistence");
entityManager.persist(_post);
return _post;
});
Поскольку EntityManager , который загрузил объект Post , был закрыт, Post отсоединяется, и режим гибернации больше не может отслеживать какие-либо изменения. Отделенный объект может быть изменен, и для распространения этих изменений объект необходимо повторно подключить к новому контексту сохранения:
post.setTitle("High-Performance Java Persistence Rocks!");
doInJPA(entityManager -> {
LOGGER.info("Merging the Post entity");
Post post_ = entityManager.merge(post);
});
При запуске приведенного выше тестового случая Hibernate выполнит следующие инструкции:
-- Merging the Post entity
SELECT p.id AS id1_0_0_ ,
p.title AS title2_0_0_
FROM post p
WHERE p.id = 1
UPDATE post
SET title='High-Performance Java Persistence Rocks!'
WHERE id=1
Hibernate сначала создает инструкцию SELECT для получения последнего состояния базовой записи базы данных, а затем копирует состояние отсоединенной сущности во вновь извлеченную управляемую сущность. Таким образом, механизм грязной проверки может обнаружить любое изменение состояния и распространить его в базу данных.
В то время как для ИДЕНТИЧНОСТИ и ПОСЛЕДОВАТЕЛЬНОСТЬ стратегии генератора, вы можете практически использовать слияние для сохранения сущности для назначенного генератора это было бы менее эффективно.
Учитывая, что Для записи сущности требуется, чтобы идентификаторы назначались вручную:
@Id private Long id;
При использовании слияние вместо сохранение :
doInJPA(entityManager -> {
Post post = new Post();
post.setId(1L);
post.setTitle("High-Performance Java Persistence");
entityManager.merge(post);
});
Hibernate выдаст инструкцию SELECT , чтобы убедиться, что в базе данных нет записей с одинаковым идентификатором:
SELECT p.id AS id1_0_0_,
p.title AS title2_0_0_
FROM post p
WHERE p.id = 1
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence', 1)
Вы действительно можете устранить эту проблему, добавив свойство версии в свою сущность, что на самом деле полезно, так как вы также можете предотвратить потерю обновлений в транзакциях с несколькими запросами:
@Version private Long version;
Если вы используете генератор назначенных идентификаторов, важно использовать оболочку Java (например, java.lang. Long ), для которого Hibernate может проверять возможность обнуления, вместо примитива (например, long) для свойства @Version .
Причина, по которой я хотел показать вам этот пример, заключается в том, что вы можете случайно использовать метод save , подобный этому, предложенному Spring Data SimpleJpaRepository :
@Transactional publicS save(S entity) { if (entityInformation.isNew(entity)) { em.persist(entity); return entity; } else { return em.merge(entity); } }
Те же правила применяются и к методу Spring Data save . Если вы когда-либо используете генератор назначенных идентификаторов, вы должны не забыть добавить свойство Java-оболочки @Version , в противном случае будет сгенерирован избыточный оператор SELECT .
Избыточный анти-шаблон сохранения
К настоящему времени ясно, что новые сущности должны проходить через сохраняться , в то время как отдельные сущности должны быть повторно присоединены с помощью слияния . Однако, просматривая множество проектов, я пришел к выводу, что следующий анти-шаблон довольно распространен:
@Transactional
public void savePostTitle(Long postId, String title) {
Post post = postRepository.findOne(postId);
post.setTitle(title);
postRepository.save(post);
}
Метод сохранить не служит никакой цели. Даже если мы удалим его, Hibernate все равно выдаст инструкцию UPDATE , поскольку объект управляется и любое изменение состояния распространяется до тех пор, пока в данный момент выполняется EntityManager открыт.
Это анти-шаблон, потому что сохранить вызов запускает Событие слияния , которое обрабатывается DefaultMergeEventListener , который выполняет следующие операции:
protected void entityIsPersistent(MergeEvent event, Map copyCache) {
LOG.trace( "Ignoring persistent instance" );
final Object entity = event.getEntity();
final EventSource source = event.getSession();
final EntityPersister persister = source
.getEntityPersister( event.getEntityName(), entity );
( (MergeContext) copyCache ).put( entity, entity, true );
cascadeOnMerge( source, persister, entity, copyCache );
copyValues( persister, entity, entity, source, copyCache );
event.setResult( entity );
}
В вызове метода копировать значения снова копируется гидратированное состояние , поэтому создается новый массив с избыточностью, что приводит к потере циклов процессора. Если сущность имеет дочерние ассоциации и операция слияния также каскадируется от родительских к дочерним сущностям , накладные расходы еще больше, поскольку каждая дочерняя сущность будет распространять Событие слияния и цикл продолжается.
Вывод
Хотя метод save может быть удобен в некоторых ситуациях, на практике никогда не следует вызывать merge для объектов, которые являются новыми или уже управляемыми. Как правило, вы не должны использовать save с JPA. Для новых сущностей вы всегда должны использовать persist , в то время как для отдельных сущностей вам нужно вызвать merge . Для управляемых сущностей вам не нужен метод save , поскольку Hibernate автоматически синхронизирует состояние сущности с базовой записью базы данных.