«1. Введение

EntityManager является частью Java Persistence API. Главным образом, он реализует программные интерфейсы и правила жизненного цикла, определенные спецификацией JPA 2.0.

Более того, мы можем получить доступ к Persistence Context, используя API в EntityManager.

В этом руководстве мы рассмотрим конфигурацию, типы и различные API-интерфейсы EntityManager.

2. Зависимости Maven

Во-первых, нам нужно включить зависимости Hibernate:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.4.0.Final</version>
</dependency>

Нам также нужно будет включить зависимости драйвера, в зависимости от используемой базы данных:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.13</version>
</dependency>

Зависимости hibernate-core и mysql-connector-java доступны на Maven Central.

3. Конфигурация

Теперь давайте продемонстрируем EntityManager, используя сущность Movie, которая соответствует таблице MOVIE в базе данных.

В этой статье мы будем использовать EntityManager API для работы с объектами Movie в базе данных.

3.1. Определение объекта

Давайте начнем с создания объекта, соответствующего таблице MOVIE, используя аннотацию @Entity:

@Entity
@Table(name = "MOVIE")
public class Movie {
    
    @Id
    private Long id;

    private String movieName;

    private Integer releaseYear;

    private String language;

    // standard constructor, getters, setters
}

3.2. Файл persistence.xml

При создании EntityManagerFactory реализация сохранения ищет файл META-INF/persistence.xml в пути к классам.

Этот файл содержит конфигурацию для EntityManager:

<persistence-unit name="com.baeldung.movie_catalog">
    <description>Hibernate EntityManager Demo</description>
    <class>com.baeldung.hibernate.pojo.Movie</class> 
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <properties>
        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
        <property name="hibernate.hbm2ddl.auto" value="update"/>
        <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
        <property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/moviecatalog"/>
        <property name="javax.persistence.jdbc.user" value="root"/>
        <property name="javax.persistence.jdbc.password" value="root"/>
    </properties>
</persistence-unit>

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

Кроме того, мы определяем диалект и другие свойства JDBC базового хранилища данных. Hibernate не зависит от базы данных. На основе этих свойств Hibernate подключается к базовой базе данных.

4. EntityManager, управляемый контейнером и приложением

В основном существует два типа EntityManager: управляемый контейнером и управляемый приложением.

Давайте подробнее рассмотрим каждый тип.

4.1. EntityManager, управляемый контейнером

Здесь контейнер внедряет EntityManager в наши корпоративные компоненты.

Другими словами, контейнер создает для нас EntityManager из EntityManagerFactory:

@PersistenceContext
EntityManager entityManager;

Это также означает, что контейнер отвечает за начало транзакции, а также за ее фиксацию или откат.

Точно так же контейнер отвечает за закрытие EntityManager, поэтому его безопасно использовать без ручной очистки. Даже если мы попытаемся закрыть EntityManager, управляемый контейнером, он должен вызвать исключение IllegalStateException.

4.2. EntityManager, управляемый приложением

И наоборот, жизненный цикл EntityManager здесь управляется приложением.

На самом деле мы создадим EntityManager вручную. Кроме того, мы также будем управлять жизненным циклом созданного EntityManager.

Сначала создадим EntityManagerFactory:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("com.baeldung.movie_catalog");

Чтобы создать EntityManager, мы должны явно вызвать createEntityManager() в EntityManagerFactory:

public static EntityManager getEntityManager() {
    return emf.createEntityManager();
}

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

4.3. Безопасность потоков

Экземпляры EntityManagerFactory и, следовательно, экземпляры SessionFactory Hibernate являются потокобезопасными. Таким образом, в параллельных контекстах совершенно безопасно писать:

EntityManagerFactory emf = // fetched from somewhere
EntityManager em = emf.createEntityManager();

С другой стороны, экземпляры EntityManager не являются потокобезопасными и предназначены для использования в средах, ограниченных потоками. Это означает, что каждый поток должен получить свой экземпляр, работать с ним и закрыть его в конце.

При использовании EntityManager, управляемого приложением, легко создавать экземпляры, ограниченные потоком:

EntityManagerFactory emf = // fetched from somewhere 
EntityManager em = emf.createEntityManager();
// use it in the current thread

Однако при использовании EntityManager, управляемого контейнером, все становится нелогичным. Например:

@Service
public class MovieService {

    @PersistenceContext // or even @Autowired
    private EntityManager entityManager;
    
    // omitted
}

Кажется, что один экземпляр EntityManager должен быть общим для всех операций. Однако контейнер (JakartaEE или Spring) внедряет здесь специальный прокси вместо простого EntityManager. Spring, например, внедряет прокси типа SharedEntityManagerCreator.

«Каждый раз, когда мы используем внедренный EntityManager, этот прокси будет либо повторно использовать существующий EntityManager, либо создавать новый. Повторное использование обычно происходит, когда мы включаем что-то вроде Open Session/EntityManager в View.

В любом случае контейнер гарантирует, что каждый EntityManager ограничен одним потоком.

5. Hibernate Entity Operations

EntityManager API предоставляет набор методов. Мы можем взаимодействовать с базой данных, используя эти методы.

5.1. Сохранение объектов

Чтобы иметь объект, связанный с EntityManager, мы можем использовать метод persist():

public void saveMovie() {
    EntityManager em = getEntityManager();
    
    em.getTransaction().begin();
    
    Movie movie = new Movie();
    movie.setId(1L);
    movie.setMovieName("The Godfather");
    movie.setReleaseYear(1972);
    movie.setLanguage("English");

    em.persist(movie);
    em.getTransaction().commit();
}

После того, как объект сохранен в базе данных, он находится в постоянном состоянии.

5.2. Загрузка объектов

Для извлечения объекта из базы данных мы можем использовать метод find().

Здесь метод ищет по первичному ключу. На самом деле, метод ожидает тип класса сущности и первичный ключ:

public Movie getMovie(Long movieId) {
    EntityManager em = getEntityManager();
    Movie movie = em.find(Movie.class, new Long(movieId));
    em.detach(movie);
    return movie;
}

Однако, если нам просто нужна ссылка на сущность, мы можем вместо этого использовать метод getReference(). По сути, он возвращает объекту прокси:

Movie movieRef = em.getReference(Movie.class, new Long(movieId));

5.3. Отсоединение объектов

В случае, если нам нужно отсоединить объект от контекста персистентности, мы можем использовать метод detach(). Мы передаем отсоединяемый объект в качестве параметра метода:

em.detach(movie);

Как только объект будет отсоединен от контекста персистентности, он будет находиться в отсоединенном состоянии.

5.4. Слияние сущностей

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

Для таких ситуаций мы можем использовать метод merge(). Метод слияния помогает внести изменения, внесенные в отсоединенный объект, в управляемый объект, если таковые имеются:

public void mergeMovie() {
    EntityManager em = getEntityManager();
    Movie movie = getMovie(1L);
    em.detach(movie);
    movie.setLanguage("Italian");
    em.getTransaction().begin();
    em.merge(movie);
    em.getTransaction().commit();
}

5.5. Запрос сущностей

Кроме того, мы можем использовать JPQL для запроса сущностей. Мы будем вызывать getResultList() для их выполнения.

Конечно, мы можем использовать getSingleResult(), если запрос возвращает только один объект:

public List<?> queryForMovies() {
    EntityManager em = getEntityManager();
    List<?> movies = em.createQuery("SELECT movie from Movie movie where movie.language = ?1")
      .setParameter(1, "English")
      .getResultList();
    return movies;
}

5.6. Удаление объектов

Кроме того, мы можем удалить объект из базы данных с помощью метода remove(). Важно отметить, что объект не отсоединяется, а удаляется.

Здесь состояние объекта меняется с постоянного на новое:

public void removeMovie() {
    EntityManager em = HibernateOperations.getEntityManager();
    em.getTransaction().begin();
    Movie movie = em.find(Movie.class, new Long(1L));
    em.remove(movie);
    em.getTransaction().commit();
}

6. Заключение

В этой статье мы рассмотрели EntityManager в Hibernate. Мы рассмотрели типы и конфигурацию и узнали о различных методах, доступных в API для работы с контекстом персистентности.

Как всегда, код, использованный в статье, доступен на Github.