Contents

Hibernate 中EntityManager简介

1. 简介

EntityManager是 Java Persistence API 的一部分。它主要实现了 JPA 2.0 规范定义的编程接口和生命周期规则。

此外,我们可以使用EntityManager中的 API 访问持久性上下文。

在本教程中,我们将了解EntityManager的配置、类型和各种 API 。

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>

Maven Central 上提供了hibernate-coremysql-connector-java 依赖项。

3. 配置

现在让我们使用与数据库中的 MOVIE 表对应的Movie实体来演示EntityManager

在本文的整个过程中,我们将使用EntityManager API 来处理数据库中的Movie对象。

3.1. 定义实体

让我们首先使用*@Entity*注解创建与 MOVIE 表对应的实体:

@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时,持久性实现在 classpath 中搜索META-INF/persistence.xml文件。

此文件包含EntityManager的配置:

<persistence-unit name="com.blogdemo.movie_catalog">
    <description>Hibernate EntityManager Demo</description>
    <class>com.blogdemo.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有两种类型:Container-Managed 和 Application-Managed。

让我们仔细看看每种类型。

4.1. 容器管理的EntityManager

在这里,容器将EntityManager注入到我们的企业组件中。

换句话说,容器为我们从EntityManagerFactory创建了EntityManager

@PersistenceContext
EntityManager entityManager;

这也意味着 容器负责开始事务,以及提交或回滚事务。

同样,容器负责关闭 EntityManager,因此无需手动清理即可安全使用。即使我们尝试close 容器管理的EntityManager,它也应该抛出IllegalStateException

4.2. 应用程序管理的EntityManager

相反,EntityManager的生命周期由应用程序管理。

事实上,我们将手动创建EntityManager并管理它的生命周期。

首先,让我们创建EntityManagerFactory

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

为了创建一个EntityManager,我们必须在EntityManagerFactory中显式调用createEntityManager()

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

由于我们负责创建EntityManager实例,因此关闭它们也是我们的责任。因此,我们应该在使用完每个EntityManagerclose 它们。

4.3. 线程安全

EntityManagerFactory实例,因此,Hibernate 的 SessionFactory实例  ,是线程安全的。所以在并发上下文中写是完全安全的:

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,要么创建一个新的。当我们在 View 中启用诸如 Open Session/EntityManager 之类的东西时,通常会发生重用。

无论哪种方式,容器都确保每个 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();
}

*一旦对象被保存在数据库中,它就处于*persistent 状态。

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. 合并实体

在实践中,许多应用程序需要跨多个事务修改实体。例如,我们可能希望在一个事务中检索一个实体以呈现给 UI。然后另一个事务将引入 UI 中所做的更改。

对于这种情况,我们可以使用*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();
}