# 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

{% code title="程序清单 11.2　不使用 Spring 模板的纯 JPA Repository" %}

```java
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);
  }
  
  ...
}
```

{% endcode %}

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

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

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

{% code title="程序清单 11.3　将 EntityManager 的代理注入到 Repository 之中" %}

```java
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);
  }
  
  ...
}
```

{% endcode %}

在这个新版本的 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：

```java
@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 样例中所做的那样：

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

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