7. Spring Data JPA EclipseLink
7.1. 简介
我们将沿用之前的架构,并在此基础上实现 JPA/EclipseLink 层。
![]() |
7.2. 设置开发环境
使用 STS 下载 [myql-config-jpa-hibernate] 项目 [1-4]:
![]() |
然后导入位于 [<examples>/spring-database-config/mysql/eclipse] 文件夹 [6] 中的 [mysl-config-jpa-eclipselink] 项目 [5]:
![]() |
完成此操作后,请为 [Package Explorer] 中的所有项目刷新 Maven 环境(Alt-F5):
![]() |
然后,为验证工作环境,请运行名为 [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink] 的构建配置:
![]() |
此配置将运行 [JUnitTestDao] 测试。该测试应通过:
![]() |
7.3. JPA 层配置项目
![]() |
本项目的目的是配置下图所示架构的 JPA 层:
![]() |
7.3.1. Maven 配置
该项目是一个由以下 [pom.xml] 文件配置的 Maven 项目:
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>dvp.spring.database</groupId>
<artifactId>generic-config-jpa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>configuration mysql openjpa</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- dépendances variables ********************************************** -->
<!-- JPA provider -->
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.6.0</version>
</dependency>
<!-- dépendances constantes ********************************************** -->
<!-- Spring Data -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<!-- configuration JDBC inherited -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>generic-config-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<build>
<plugins>
<!-- [https://flexguse.wordpress.com/2013/08/10/maven-spring-data-jpa-eclipselink-and-static-weaving/] -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
<!-- This plugin ensures the EclipseLink static weaving -->
<plugin>
<artifactId>staticweave-maven-plugin</artifactId>
<groupId>de.empulse.eclipselink</groupId>
<version>1.0.0</version>
<executions>
<execution>
<goals>
<goal>weave</goal>
</goals>
<phase>process-classes</phase>
<configuration>
<logLevel>ALL</logLevel>
<!-- <includeProjectClasspath>true</includeProjectClasspath> -->
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.6.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
de.empulse.eclipselink
</groupId>
<artifactId>
staticweave-maven-plugin
</artifactId>
<versionRange>
[1.0.0,)
</versionRange>
<goals>
<goal>weave</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute>
<runOnIncremental>true</runOnIncremental>
</execute>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
- 第 5–7 行:该项目生成的 Maven 工件。它与 [mysql-config-jpa-hibernate] 项目生成的工件相同。这意味着在任何给定时间,这两个项目中只能有一个处于活动状态;
- 第 10–14 行:父级 Maven 项目,用于指定该项目所需的大部分依赖项的版本;
- 第 19–22 行:EclipseLink 库;
- 第 26–29 行:Spring Data 库;
- 第 32–34 行:JPA 层配置项目基于 JDBC 层配置项目构建,后者定义了所用 DBMS 的 JDBC 驱动程序以及数据库的连接详细信息等内容;
- 第 35–40 行:JDBC 层配置项目包含 [Spring JDBC] 库,此处已被 [Spring Data JPA] 库取代。因此,我们指定不将其包含在项目依赖项中。不过,即使保留该库,也不会引发任何错误;
- 第 58–81 行中的插件实现了 JPA 实体编织。所谓编织,是指对 JPA 实体进行转换(增强),使其支持延迟加载。我们无需配置 Hibernate 即可实现此编织。对于 EclipseLink,则需要一个 Maven 插件。 我花了很多时间试图弄清楚如何强制 EclipseLink 遵守下面 [@ManyToOne] 注解中的 [fetch = FetchType.LAZY] 属性:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = ConfigJdbc.TAB_PRODUITS_CATEGORIE_ID)
private Categorie categorie;
JPA规范指出,[@ManyToOne]注解的[fetch = FetchType.LAZY]属性仅为“提示”,JPA实现无需遵循。事实上,EclipseLink默认并不遵循该提示。若要使其生效,需要进行特殊配置。 经过一番徒劳的搜索后,我在第 51 行引用的 URL 中找到了解决方案。当将第 58–81 行添加到 [pom.xml] 文件中时,Eclipse 会报告该文件存在错误。这是 [m2e] 插件的配置问题,该插件负责处理 Eclipse 中的 Maven 项目。您需要添加第 83–119 行来解决此错误。
最终,依赖项如下:
![]() |
7.3.2. Spring 配置
![]() |
[ConfigJpa] 类用于配置 Spring 项目:
package generic.jpa.config;
import generic.jdbc.config.ConfigJdbc;
import javax.persistence.EntityManagerFactory;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
@Import({ ConfigJdbc.class })
public class ConfigJpa {
// the provider JPA
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
// Note: JPA entities and Eclipselink configuration are in the META-INF/persistence.xml file
EclipseLinkJpaVendorAdapter eclipseLinkJpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
eclipseLinkJpaVendorAdapter.setShowSql(false);
eclipseLinkJpaVendorAdapter.setDatabase(Database.MYSQL);
eclipseLinkJpaVendorAdapter.setGenerateDdl(true);
return eclipseLinkJpaVendorAdapter;
}
// data source
@Bean
public DataSource dataSource() {
// data source TomcatJdbc
DataSource dataSource = new DataSource();
// configuration access JDBC
dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
dataSource.setUsername(ConfigJdbc.USER_DBPRODUITSCATEGORIES);
dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITSCATEGORIES);
dataSource.setUrl(ConfigJdbc.URL_DBPRODUITSCATEGORIES);
// initially open connections
dataSource.setInitialSize(5);
// result
return dataSource;
}
// EntityManagerFactory
@Bean
public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(jpaVendorAdapter);
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
EntityManagerFactory entityManagerFactory = factory.getObject();
return entityManagerFactory;
}
// Transaction manager
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
此配置与第6.3.2节中描述的Hibernate JPA实现配置类似。我们仅详细说明其中的差异:
- 第 23–31 行:[jpaVendorAdapter] Bean 现由 EclipseLink 实现;
- 第 50–58 行:在 Hibernate JPA 版本中,代码如下:
该代码用于指定搜索 JPA 实体的位置。在此,我们依赖 [persistence.xml] 文件(第 25 行注释)(参见第 6.3.4 节)来同时:
- 定义 JPA 实体;
- 配置 EclipseLink 以对这些实体进行编织;
7.4. [persistence.xml] 文件
![]() |
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="generic-jpa-entities-dbproduitscategories" transaction-type="RESOURCE_LOCAL">
<!-- entities JPA -->
<class>generic.jpa.entities.dbproduitscategories.Categorie</class>
<class>generic.jpa.entities.dbproduitscategories.Produit</class>
<class>generic.jpa.entities.dbproduitscategories.User</class>
<class>generic.jpa.entities.dbproduitscategories.Role</class>
<class>generic.jpa.entities.dbproduitscategories.UserRole</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<!-- properties required for [@ManyToOne] to be searched in LAZY mode -->
<properties>
<property name="eclipselink.weaving" value="static" />
<property name="eclipselink.weaving.lazy" value="true" />
<property name="eclipselink.weaving.internal" value="true" />
</properties>
</persistence-unit>
</persistence>
- 第 4 行:持久化单元。它可以取任意名称(name 属性);
- 第 6–10 行:待管理的五个 JPA 实体;
- 第 11 行非常重要。有时一个项目会定义在不同上下文中使用的实体。第 11 行确保除了第 5–10 行定义的实体之外,不会存在其他实体。当这些实体用于生成数据源表时,这一点尤为重要。如果存在额外的实体,将会生成多余的表;
- 第13–17行:用于静态编织的EclipseLink配置。编织有两种类型:
- [静态]:JPA 实体在 JPA 层实例化时立即被编织;
- [动态]:JPA 实体在首次进入 JPA 层时被增强(编织);
7.5. JPA 实体
![]() |
JPA 实体是指第 6.3.3 节中针对 Hibernate 实现所描述的实体,但有两点不同:
- 所有 JPA 实体都带有 [@Cache(alwaysRefresh = true)] 注解,这会禁用 EclipseLink 缓存。在本文档中,未使用所采用的 JPA 实现的缓存。EclipseLink 缓存似乎默认处于启用状态,并在测试中引发了错误。
@Entity
@Table(name = ConfigJdbc.TAB_CATEGORIES)
@JsonFilter("jsonFilterCategorie")
@Cache(alwaysRefresh = true)
public class Categorie implements AbstractCoreEntity {
- 所有 [@OneToMany] 注解都伴随着 [@CascadeOnDelete] 注解:
@OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL })
@CascadeOnDelete
private List<Produit> produits;
此注解在从 JPA 实体生成表时发挥作用。 它会在外键(此处为 PRODUCTS[CATEGORY_ID] ---> CATEGORIES[ID])上添加 SQL 属性 [ON DELETE CASCADE],该属性确保每当从 [CATEGORIES] 表中删除一个类别时,[PRODUCTS] 表中对应的产品也会被删除;
注意:需要特别注意的是,正如我们刚才所见,该注解既在创建表时使用,也在查询表时使用。EclipseLink 会默认存在 SQL 属性 [ON DELETE CASCADE],并在被要求删除类别时使用它。如果缺少该属性,将会导致错误。
7.6. 测试层
![]() |
![]() |
上述测试与 Spring JDBC 和 Spring JPA Hibernate 实现的测试完全相同。如有需要,请参阅以下页面:
- [JUnitTestCheckArguments]:第 4.11.1 节;
- [JUnitTestDao]:第 4.11.2 节;
- [JUnitTestPushTheLimits]: 第4.11.3节;
- [JUnitTestProxies]:第6.4.5节;
所得结果如下:
![]() |
![]() |
- 在 [1] 中,[JUnitTestPushTheLimits-EclipseLink]:70.583 秒
- 在 [2] 中,[JUnitTestPushTheLimits-Hibernate]:78.945 秒
- 在 [3] 中,[JUnitTestPushTheLimits-JDBC]:36.09 秒
[JUnitTestProxies] 测试产生了以下控制台输出:
在此我们可以看到,当访问 PROXY 类型类别的 [Category.products] 字段以及 PROXY 类型产品的 [Product.category] 字段时,两种情况均成功检索到了信息(第 7 行和第 17 行)。在三种 JPA 实现中,这是唯一允许在 PROXY 实体上进行此类操作的实现。















