Перейти к основному содержимому

Руководство по Hibernate EntityManager

· 6 мин. чтения

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.foreach.movie_catalog">
<description>Hibernate EntityManager Demo</description>
<class>com.foreach.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 , а также будем управлять его жизненным циклом.

Во-первых, давайте создадим EntityManagerFactory:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("com.foreach.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. Спящие операции сущностей

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 для работы с Persistence Context.

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