【0】README
1)本文部分文字描述转自:“Spring In Action(中/英文版)”,旨在review “spring(11)使用对象-关系映射持久化数据” 的相关知识;
【2】spring 与 java 持久化API
1)intro:JPA全称Java Persistence API.JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
2)在spring中使用 JPA 第一步是要在 spring应用上下文中将 实体管理器工厂 按照bean 的形式来进行配置;(干货——引入了实体管理器工厂)
【2.1】配置实体管理器工厂
1)intro:基于JPA的应用程序需要使用 EntityManagerFactory的实现类来获取 EntityManager实例。JPA 定义了 两种类型的实体管理器:
type1)应用程序管理类型:当应用程序向实体管理器工厂直接请求实体管理器时,工厂会创建一个实体管理器。在这种模式下,程序要负责打开或关闭实体管理器并在事务中对其进行控制。这种方式的实体管理器适合于不运行在 java ee 容器中的独立应用程序;
type2)容器管理类型:实体管理器由java ee 创建和管理。应用程序根本不与实体管理器工程打交道。相反,实体管理器直接通过注入或JNDI 来获取。容器负责配置实体管理器工厂。这种类型的实体管理器最适用于 java ee 容器,在这种case下 会希望在 persistence.xml 指定的 JPA 配置之外保持一些自己对 JPA的控制;(干货——容器管理类型的实体管理器最适用于 java ee 容器)
2)以上两种实体管理器实现了同一个接口 EntityManager。关键的区别在于 EntityManager 的创建和管理方式。 应用程序管理类型的 EntityManager由 EntityManagerFactory 创建的,EntityManagerFactory 是由 PersistenceProvider.createEntityManagerFactory() 方法得到的;而 容器管理类型的 EntityManagerFactorys 是通过 PersistenceProvider.createContainerEntityManagerFactory()方法得到的;(干货——应用程序管理类型和容器管理类型的实体管理器的关键区别在于 EntityManager 的创建和管理方式)
3)这两种实体管理器工厂分别由对应的 spring 工厂 bean 创建:
type1)LocalEntityManagerFactoryBean: 生成应用程序管理类型的 EntityManagerFactory;
type2)LocalContainerEntityManagerFactoryBean: 生成容器管理类型的 EntityManagerFactory;
Attention)应用程序管理类型和容器管理类型的实体管理器工厂间唯一值得关注的区别是:在 spring 应用上下文中如何进行配置;
【2.1.1】配置应用程序管理类型的JPA
1)intro:对于应用程序管理类型的实体管理器工厂来说,它绝大部分配置信息来源于一个 名为 persistence.xml 的配置文件,这个文件必须位于 类路径下的 META-INF 目录下;
2)下面是一个典型的 persistence.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
version="1.0">
<persistence-unit name="spitterPU">
<class>com.habuma.spittr.domain.Spitter</class>
<class>com.habuma.spittr.domain.Spittle</class>
<properties>
<property name="toplink.jdbc.driver" value="org.hsqldb.jdbcDriver" />
<property name="toplink.jdbc.url"
value="jdbc:hsqldb:hsql://localhost/spitter/spitter" />
<property name="toplink.jdbc.user" value="sa" />
<property name="toplink.jdbc.password" value="" />
</properties>
</persistence-unit>
</persistence>
3)可以通过如下的 @Bean注解方法在spring中声明 LocalEntityManagerFactoryBean:
【2.1.2】使用容器管理类型的JPA
1)intro:容器管理的JPA 采取了一个不同的方式。当运行在容器中时,可以使用容器提供的信息来生成 EntityManagerFactory;
2)你可以将 数据源信息配置在 spring应用上下文中,而不是在 persistence.xml 中了。如下的@Bean 注解方法声明了在 spring中如何使用 LocalContainerEntityManagerFactoryBean 来配置容器管理类型的 JPA:
对以上代码的分析(Analysis):
A1)这里,我们使用了 spring配置的数据源来设置 datasource属性。任何 javax.sql.DataSource 的实现都是可以的;
A2)jpaVendorApapter属性:用于指明所使用的是哪一个厂商的JPA 实现,spring提供了多个 JPA 厂商适配器:
adapter1)EclipseLinkJpaVendorAdapter
adapter2)HibernateJpaVendorAdapter
adapter3)OpenJpaVendorAdapter
adapter4)TopLinkJpaVendorAdapter (deprecated in Spring 3.1)
【2.1.3】从JNDI 获取实体管理器工厂
1)intro:如果将spring应用程序部署在应用server中, 则 EntityManagerFactory 可能已经被创建好了,并且位于 JNDI 中等待查询使用;
2)在这种case下,可以使用 spring jee 命名空间下的 <jee: jndi-lookup>元素来获取对 EntityManagerFactory 的引用:
<jee:jndi-lookup id="emf" jndi-name="persistence/spitterPU" />
3)可以使用 java Config 来获取 EntityManagerFactory :
@Bean
public JndiObjectFactoryBean entityManagerFactory() {}
JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
jndiObjectFB.setJndiName("jdbc/SpittrDS");
return jndiObjectFB;
}
对以上代码的分析(Analysis): 因为上述方法返回的 JndiObjectFactoryBean 是 FactoryBean 接口的实现,它能够创建 EntityManagerFactory;
Attention)自此,我们就得到了 EntityManagerFactory 对象了;
【2.2】编写基于 JPA 的 Repository
1)intro:spring 对 JPA 集成也提供了JpaTemplate 模板以及对应的支持类 JpaDaoSupport;
2)鉴于纯粹的JPA 方式远远胜于基于模板的 JPA,所以在本节中我们将会重点关注如何构建不依赖spring 的 JPA Repository。如下程序中的 JpaSpitterRepository 展现了 如何开发不使用 spring JpaTemplate 的 JPA Repository;
@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);
}
...
}
对以上代码的分析(Analysis):
A1)@PersistenceUnit注解:spring会将 EntityManagerFactory 注入到 Repository中;(干货——注解@PersistenceUnit的作用)
A2)有了 EntityManagerFactory之后,JpaSpitterRepository 的方法就能够使用它来创建 EntityManager了,然后 EntityManager 可以针对数据库执行操作;
A3)JpaSpitterRepository 唯一的问题:在于每个方法都会调用createEntityManager()方法;
3)下面的程序展现了 如何借助 @PersistenceContext注解为 JpaSpitterRepository 设置 EntityManager;
@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);
}
...
}
对以上代码的分析(Analysis):
A0)@Transactional注解的作用: 表明这个 Repository中的持久化方法是在事务上下文中执行的;(干货——@Transactional注解的作用)
A1)由于没有使用模板类来处理异常,所以我们需要为 Repository添加 @Repository 注解,这样PersistenceAnnotationBeanPostProcessor 就会知道要将这个bean 产生 的异常转换为 spring 的统一数据访问异常;
A2)在上面的JpaSpitterRepository 中,直接为其设置了 EntityManager ;这样的话,每个方法中就没有必须再通过 EntityManagerFactory 创建 EntityManager了;(尽管这种方式非常便利,但你可能会担心注入的EntityManager 会有 线程安全问题)
A3)这里的真相是:@PersistenceContext 并不会真正注入 EntityManager;它没有将真正的 EntityManager 设置给 Repository,而是给了它一个 EntityManager 的代理;真正的EntityManager 是与当前事务相关联的那一个,如果不存在这样的 EntityManager 的话,就会创建一个新的。这样的话,我们就能始终以 线程安全的方式提供实体管理器了;
4)需要了解 @PersistenceUnit and @PersistenceContext 并不是spring 的注解,而是 JPA规范提供的。
4.1)为了让spring理解 这些注解,并注入 EntityManagerFactory or EntityManager,我们必须要配置 spring 的 PersistenceAnnotationBeanPostProcessor ;
4.2)如果你已经使用了 <context:annotation-config> or <context:component-scan>,那么你不必担心了,因为这些配置元素会自动注册 PersistenceAnnotationBeanPostProcessor bean;否则的话,我们就要显示地注入这个 bean;
@Bean
public PersistenceAnnotationBeanPostProcessor paPostProcessor() {
return new PersistenceAnnotationBeanPostProcessor();
}