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

Как работают методы сохранения, слияния и гибернации JPA, сохранения, обновления, сохранения и обновления

Узнайте, как JPA сохраняется и объединяется, а также как работают методы сохранения, обновления и обновления в режиме гибернации и связанные с ними переходы состояний сущностей.

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

В этой статье я собираюсь показать вам, как работают JPA persist и merge и как они сравниваются с методами Hibernate save , update и saveOrUpdate .

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

Как я объяснил в этой статье , объект JPA или Hibernate может находиться в одном из следующих четырех состояний:

  • Переходный период (Новый)
  • Управляемый (Постоянный)
  • Отдельный
  • Удалено (Удалено)

Переход из одного состояния в другое осуществляется с помощью методов EntityManager или Сеанса .

Например, JPA EntityManager предоставляет следующие методы перехода состояния сущности.

Hibernate Сеанс реализует все методы JPA EntityManager и предоставляет некоторые дополнительные методы перехода состояния сущности, такие как сохранить , Сохранить обновление и обновление .

Давайте рассмотрим, что у нас есть следующая Книга сущность, которая использует API свободного стиля :

@Entity(name = "Book")
@Table(name = "book")
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    private String isbn;

    private String title;

    private String author;

    public Long getId() {
        return id;
    }

    public Book setId(Long id) {
        this.id = id;
        return this;
    }

    public String getIsbn() {
        return isbn;
    }

    public Book setIsbn(String isbn) {
        this.isbn = isbn;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Book setTitle(String title) {
        this.title = title;
        return this;
    }

    public String getAuthor() {
        return author;
    }

    public Book setAuthor(String author) {
        this.author = author;
        return this;
    }
}

Теперь давайте посмотрим, как мы можем сохранять и обновлять объект с помощью JPA и гибернации.

Чтобы изменить состояние сущности с переходного (Нового) на Управляемое (Сохраняемое), мы можем использовать метод persist , предлагаемый JPA EntityManager , который также наследуется сеансом Hibernate /.

Метод persist запускает Постоянное событие , которое обрабатывается DefaultPersistEventListener Прослушивателем событий гибернации.

Поэтому при выполнении следующего тестового случая:

doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);
    
    LOGGER.info(
        "Persisting the Book entity with the id: {}", 
        book.getId()
    );
});

Hibernate создает следующие инструкции SQL:

CALL NEXT VALUE FOR hibernate_sequence

-- Persisting the Book entity with the id: 1

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

Обратите внимание, что идентификатор присваивается до присоединения Книги сущности к текущему контексту сохранения. Это необходимо, поскольку управляемые сущности хранятся в структуре Map , где ключ формируется типом сущности и ее идентификатором, а значение является ссылкой на сущность. По этой причине JPA EntityManager и Hibernate Сеанс известны как кэш первого уровня.

При вызове persist сущность присоединяется только к текущему контексту сохранения , и ВСТАВКА может быть отложена до тех пор, пока не будет вызван сброс .

Единственным исключением является генератор идентификаторов, который сразу же запускает ВСТАВКУ, поскольку это единственный способ получить идентификатор сущности. По этой причине Hibernate не может выполнять пакетные вставки для сущностей, использующих генератор идентификаторов. Для получения более подробной информации об этой теме ознакомьтесь с этой статьей .

Специфичный для гибернации метод save предшествует JPA и доступен с начала проекта Hibernate.

Метод save запускает SaveOrUpdateEvent , который обрабатывается DefaultSaveOrUpdateEventListener Прослушиватель событий гибернации. Следовательно, метод save эквивалентен методам update и saveOrUpdate .

Чтобы увидеть, как работает метод save , рассмотрим следующий тестовый случай:

doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);

    Long id = (Long) session.save(book);

    LOGGER.info(
        "Saving the Book entity with the id: {}", 
        id
    );
});

При запуске приведенного выше тестового случая Hibernate генерирует следующие инструкции SQL:

CALL NEXT VALUE FOR hibernate_sequence

-- Saving the Book entity with the id: 1

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

Как вы можете видеть, результат идентичен вызову метода persist . Однако, в отличие от persist , метод save возвращает идентификатор сущности.

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

Метод update запускает SaveOrUpdateEvent , который обрабатывается DefaultSaveOrUpdateEventListener Прослушиватель событий гибернации. Следовательно, метод update эквивалентен методам save и saveOrUpdate .

Чтобы увидеть, как работает метод update , рассмотрим следующий пример, в котором сущность Book сохраняется в одной транзакции, затем она изменяет ее, пока сущность находится в отключенном состоянии, и запускает обновление SQL с помощью вызова метода update .

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

LOGGER.info("Modifying the Book entity");

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);

    session.update(_book);

    LOGGER.info("Updating the Book entity");
});

При выполнении приведенного выше тестового случая Hibernate генерирует следующие инструкции SQL:

CALL NEXT VALUE FOR hibernate_sequence

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

-- Modifying the Book entity
-- Updating the Book entity

UPDATE 
    book 
SET 
    author = 'Vlad Mihalcea', 
    isbn = '978-9730228236', 
    title = 'High-Performance Java Persistence, 2nd edition'
WHERE 
    id = 1

Обратите внимание, что ОБНОВЛЕНИЕ выполняется во время сброса контекста сохранения, непосредственно перед фиксацией, и поэтому сообщение Обновление сущности книги регистрируется первым.

Используйте @SelectBeforeUpdate, чтобы избежать ненужных обновлений

Теперь ОБНОВЛЕНИЕ всегда будет выполняться, даже если объект не был изменен, находясь в отключенном состоянии. Чтобы предотвратить это, вы можете использовать @SelectBeforeUpdate Аннотация спящего режима, которая вызовет SELECT оператор, который извлек загруженное состояние , которое затем используется механизмом проверки на загрязнение.

Итак, если мы аннотируем сущность Book с помощью @SelectBeforeUpdate аннотации:

@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {

    //Code omitted for brevity
}

И выполните следующий тестовый случай:

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);

    session.update(_book);
});

Hibernate выполняет следующие инструкции SQL:

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

SELECT 
    b.id,
    b.author AS author2_0_,
    b.isbn AS isbn3_0_,
    b.title AS title4_0_
FROM 
    book b
WHERE 
    b.id = 1

Обратите внимание, что на этот раз ОБНОВЛЕНИЕ не выполняется, так как механизм проверки на наличие ошибок в спящем режиме обнаружил, что объект не был изменен.

Специфичный для гибернации метод saveOrUpdate является просто псевдонимом для сохранения и обновления .

Метод saveOrUpdate запускает SaveOrUpdateEvent , который обрабатывается DefaultSaveOrUpdateEventListener Прослушиватель событий гибернации. Следовательно, метод update эквивалентен методам save и saveOrUpdate .

Теперь вы можете использовать saveOrUpdate , когда хотите сохранить сущность или принудительно ОБНОВИТЬ , как показано в следующем примере.

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(book);

    return book;
});

_book.setTitle("High-Performance Java Persistence, 2nd edition");

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(_book);
});

Одна из проблем, которая может возникнуть с save , update и saveOrUpdate , заключается в том, что контекст сохранения уже содержит ссылку на сущность с тем же идентификатором и того же типа, что и в следующем примере:

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(book);

    return book;
});

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

try {
    doInJPA(entityManager -> {
        Book book = entityManager.find(
            Book.class, 
            _book.getId()
        );

        Session session = entityManager.unwrap(Session.class);
        session.saveOrUpdate(_book);
    });
} catch (NonUniqueObjectException e) {
    LOGGER.error(
        "The Persistence Context cannot hold " +
        "two representations of the same entity", 
        e
    );
}

Теперь, при выполнении приведенного выше тестового примера, Hibernate создаст исключение uniqueobjectexception , поскольку второй EntityManager уже содержит Книгу сущность с тем же идентификатором, что и тот, который мы передаем обновление , и контекст сохранения не может содержать два представления одной и той же сущности.

org.hibernate.NonUniqueObjectException: 
	A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1]
	at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
	at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
	at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682)
	at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)

Чтобы избежать исключения uniqueobjectexception , вам необходимо использовать метод merge , предлагаемый JPA EntityManager и унаследованный Hibernate Сеансом .

Как объяснено в этой статье , merge извлекает новый снимок сущности из базы данных, если в контексте сохранения не найдена ссылка на сущность, и копирует состояние отсоединенного объекта, переданного методу merge .

Метод merge запускает событие Merge , которое обрабатывается прослушивателем событий DefaultMergeEventListener Hibernate.

Чтобы увидеть, как работает метод merge , рассмотрим следующий пример, в котором сущность Book сохраняется в одной транзакции, затем она изменяет ее, пока сущность находится в отделенном состоянии, и передает отделенную сущность в merge в контексте сохранения подпоследовательности.

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

LOGGER.info("Modifying the Book entity");

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

doInJPA(entityManager -> {
    Book book = entityManager.merge(_book);

    LOGGER.info("Merging the Book entity");

    assertFalse(book == _book);
});

При выполнении приведенного выше тестового случая Hibernate выполнил следующие инструкции SQL:

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

-- Modifying the Book entity

SELECT 
    b.id,
    b.author AS author2_0_,
    b.isbn AS isbn3_0_,
    b.title AS title4_0_
FROM 
    book b
WHERE 
    b.id = 1

-- Merging the Book entity

UPDATE 
    book 
SET 
    author = 'Vlad Mihalcea', 
    isbn = '978-9730228236', 
    title = 'High-Performance Java Persistence, 2nd edition'
WHERE 
    id = 1

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

Теперь, хотя вы должны предпочесть использовать JPA слияние при копировании состояния отделенной сущности, дополнительный ВЫБОР может быть проблематичным при выполнении задачи пакетной обработки.

По этой причине вам следует предпочесть использовать update , когда вы уверены, что к текущему контексту сохранения уже не прикреплена ссылка на сущность и что отсоединенный объект был изменен. Для получения более подробной информации об этой теме ознакомьтесь с этой статьей .

Чтобы сохранить сущность, вы должны использовать метод JPA persist . Чтобы скопировать состояние обособленной сущности, следует предпочесть объединить . Метод update полезен только для задач пакетной обработки. save и saveOrUpdate являются просто псевдонимами update , и вам, вероятно, вообще не следует их использовать.

Некоторые разработчики вызывают сохранить , даже если объект уже управляется, но это ошибка и вызывает избыточное событие, поскольку для управляемых объектов ОБНОВЛЕНИЕ автоматически обрабатывается во время сброса контекста сохранения.