11.2.2 编写基于 JPA 的 Repository

正如 Spring 对其他持久化方案的集成一样,Spring 对 JPA 集成也提供了 JpaTemplate 模板以及对应的支持类 JpaDaoSupport。但是,为了实现更纯粹的 JPA 方式,基于模板的 JPA 已经被弃用了。这与我们在 11.1.2 小节使用的 Hibernate 上下文 Session 是很类似的。

鉴于纯粹的 JPA 方式远胜于基于模板的 JPA,所以在本节中我们将会重点关注如何构建不依赖 Spring 的 JPA Repository。如下程序清单中的 JpaSpitterRepository 展现了如何开发不使用 Spring JpaTemplate 的 JPA Repository。

程序清单11.2 不使用Spring模板的纯JPA Repository

程序清单 11.2 不使用 Spring 模板的纯 JPA Repository
package com.habuma.spittr.persistence;

import java.util.List;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.habuma.spittr.domain.Spitter;
import com.habuma.spittr.domain.Spittle;

@Repository
@Transactional
public class JpaSpitterRepository implements SpitterRepository {
  @PersistenceUnit
  private EntityManagerFactory emf;
  
  public void addSpitter(Spitter spitter) {
    emf.createEntityManager().persist(spitter);
  }
  
  public Spitter getSpitterById(long id) {
    return emf.createEntityManager().find(Spitter.class, id);
  }
  
  public void saveSpitter(Spitter spitter) {
    emf.createEntityManager().merge(spitter);
  }
  
  ...
}

程序清单 11.2 中,需要注意的是 EntityManagerFactory 属性,它使用了 @PersistenceUnit 注解,因此,Spring 会将 EntityManagerFactory 注入到 Repository 之中。有了 EntityManagerFactory 之后,JpaSpitterRepository 的方法 就能使用它来创建 EntityManager 了,然后 EntityManager 可以针对数据库执行操作。

在 JpaSpitterRepository 中,唯一的问题在于每个方法都会调用createEntity-Manager()。除了引入易出错的重复代码以外,这还意味着每次调用 Repository 的方法时,都会创建一个新的 EntityManager。这种复杂性源于事务。如果我们能够预先准备好 EntityManager,那会不会更加方便呢?

这里的问题在于 EntityManager 并不是线程安全的,一般来讲并不适合注入到像 Repository 这样共享的单例 bean 中。但是,这并不意味着我们没有办法要求注入 EntityManager。如下的程序清单展现了如何借助 @PersistentContext 注解为 JpaSpitterRepository 设置 EntityManager。

程序清单 11.3 将 EntityManager 的代理注入到 Repository 之中
package com.habuma.spittr.persistence;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.habuma.spittr.domain.Spitter;
import com.habuma.spittr.domain.Spittle;

@Repository
@Transactional
public class JpaSpitterRepository implements SpitterRepository {
  @PersistenceContext
  private EntityManager em;
  
  public void addSpitter(Spitter spitter) {
    em.persist(spitter);
  }
  
  public Spitter getSpitterById(long id) {
    return em.find(Spitter.class, id);
  }
  
  public void saveSpitter(Spitter spitter) {
    em.merge(spitter);
  }
  
  ...
}

在这个新版本的 JpaSpitterRepository 中,直接为其设置了 EntityManager,这样的话,在每个方法中就没有必要再通过 EntityManagerFactory 创建 EntityManager 了。尽管这种方式非常便利,但是你可能会担心注入的 EntityManager 会有线程安全性的问题。

这里的真相是 @PersistenceContext 并不会真正注入 EntityManager —— 至少,精确来讲不是这样的。它没有将真正的 EntityManager 设置给 Repository,而是给了它一个 EntityManager 的代理。真正的 EntityManager 是与当前事务相关联的那一个,如果不存在这样的 EntityManager 的话,就会创建一个新的。这样的话,我们就能始终以线程安全的方式使用实体管理器。

另外,还需要了解 @PersistenceUnit 和 @PersistenceContext 并不是 Spring 的注解,它们是由 JPA 规范提供的。为了让 Spring 理解这些注解,并注入 EntityManagerFactory 或 EntityManager,我们必须要配置 Spring 的 Persistence- AnnotationBeanPostProcessor。如果你已经使用了 <context:annotation-config> 或 <context:component-scan>,那么你就不必再担心了,因为这些配置元素会自动注册 PersistenceAnnotationBeanPostProcessor bean。否则的话,我们需要显式地注册这个bean:

@Bean
public PersistenceAnnotationBeanPostProcessor paPostProcessor() {
  return new PersistenceAnnotationBeanPostProcessor();
}

你可能也注意到了 JpaSpitterRepository 使用了@Repository 和 @Transactional 注解。@Transactional 表明这个 Repository 中的持久化方法是在事务上下文中执行的。

对于 @Repository 注解,它的作用与开发 Hibernate 上下文 Session 版本的 Repository 时是一致的。由于没有使用模板类来处理异常,所以我们需要为 Repository 添加 @Repository 注解,这样 PersistenceExceptionTranslation-PostProcessor 就会知道要将这个 bean 产生的异常转换成 Spring 的统一数据访问异常。

既然提到了 PersistenceExceptionTranslationPostProcessor,要记住的是我们需要将其作为一个 bean 装配到 Spring 中,就像我们在 Hibernate 样例中所做的那样:

@Bean
public BeanPostProcessor persistenceTranslation() {
  return new PersistenceExceptionTranslationPostProcessor();
}

提醒一下,不管对于 JPA 还是 Hibernate,异常转换都不是强制要求的。如果你希望在 Repository 中抛出特定的 JPA 或 Hibernate 异常,只需将PersistenceExceptionTranslationPostProcessor 省略掉即可,这样原来的异常就会正常地处理。但是,如果使用了 Spring 的异常转换,你会将所有的数据访问异常置于 Spring 的体系之下,这样以后切换持久化机制的话会更容易。

Last updated