> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v4/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://potoyang.gitbook.io/spring-in-action-v4/untitled-5/untitled-2/untitled.md).

# 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 的体系之下，这样以后切换持久化机制的话会更容易。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://potoyang.gitbook.io/spring-in-action-v4/untitled-5/untitled-2/untitled.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
