Skip to content

8. Spring Data JPA OpenJpa

8.1. 简介

我们沿用之前的架构,现在通过 JPA / OpenJpa 层进行实现。

8.2. 工作环境的搭建

使用 STS 下载项目 [myql-config-jpa-eclipselink] [1-4]:

然后导入位于文件夹 [<exemples>/spring-database-config/mysql/eclipse] [6] 中的项目 [mysl-config-jpa-openjpa] [5]:

完成上述操作后,重置 [Package Explorer] 中所有项目的 Maven 环境(Alt-F5):

 

然后,为验证工作环境,请执行名为 [spring-jpa-generic-JUnitTestDao-openjpa] 的运行配置:

此配置将执行测试 [JUnitTestDao]。该测试必须成功:

 

8.3. JPA 层配置项目

  

该项目的目的是配置以下架构中的 JPA 层:

8.3.1. Maven 配置

该项目是一个 Maven 项目,由以下 [pom.xml] 文件进行配置:


<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>
        <!-- 可变依赖项 ********************************************** -->
        <!-- JPA 提供程序 -->
        <dependency>
            <groupId>org.apache.openjpa</groupId>
            <artifactId>openjpa</artifactId>
            <version>2.3.0</version>
        </dependency>
        <!-- 常量依赖项 ********************************************** -->
        <!-- Spring Data -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <!-- 继承自 JDBC 的配置 -->
        <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>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • 第 5-7 行:该项目生成的 Maven 工件。它与项目 [mysql-config-jpa-hibernate] 的工件相同。这意味着在任何给定时刻,这两个项目中只能有一个处于活动状态;
  • 第 10-14 行:父 Maven 项目,用于确定该项目所需的大部分依赖项的版本;
  • 第 19-23 行:OpenJpa 库;
  • 第 26-29 行:Spring Data 库;
  • 第 32-34 行:JPA 层配置项目基于 JDBC 层配置项目,后者定义的内容包括: 所用 SGBD 的 JDBC 驱动程序以及待用数据库的连接信息;
  • 第 35-40 行:图层 JDBC 的配置项目包含库 [Spring JDBC],此处将其替换为库 [Spring Data JPA]。 因此建议不要将其包含在项目依赖项中。但即使保留该库,也不会导致错误;

最终,依赖项如下:

  

8.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.OpenJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
@Import({ConfigJdbc.class})
public class ConfigJpa {

    // JPA 提供程序
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        OpenJpaVendorAdapter openJpaVendorAdapter = new OpenJpaVendorAdapter();
        openJpaVendorAdapter.setShowSql(false);
        openJpaVendorAdapter.setDatabase(Database.MYSQL);
        openJpaVendorAdapter.setGenerateDdl(true);
        return openJpaVendorAdapter;
    }

    // JPA 实体包
    public final static String[] ENTITIES_PACKAGES = { "generic.jpa.entities.dbproduitscategories" };

    // 数据源
    @Bean
    public DataSource dataSource() {
        // 数据源 TomcatJdbc
        DataSource dataSource = new DataSource();
        // 访问配置JDBC
        dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
        dataSource.setUsername(ConfigJdbc.USER_DBPRODUITSCATEGORIES);
        dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITSCATEGORIES);
        dataSource.setUrl(ConfigJdbc.URL_DBPRODUITSCATEGORIES);
        // 初始打开的连接
        dataSource.setInitialSize(5);
        // 结果
        return dataSource;
    }

    // EntityManagerFactory
    @Bean
    public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(jpaVendorAdapter);
        factory.setPackagesToScan(ENTITIES_PACKAGES);
        factory.setDataSource(dataSource);
        factory.afterPropertiesSet();
        return factory.getObject();
    }

    // 事务管理器
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }
}

此配置与第 6.3.2 节中针对 Hibernate 实现 JPA 的配置类似。我们仅详细说明差异:

  • 第 23-30 行:[jpaVendorAdapter] Bean 现由 OpenJpa 实现;

8.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">
        <!-- 实体 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 />
    </persistence-unit>
    <persistence-unit name="generic-jpa-entities-dbproduits" transaction-type="RESOURCE_LOCAL">
        <!-- 实体 JPA -->
        <class>generic.jpa.entities.dbproduits.Produit</class>
        <exclude-unlisted-classes />
    </persistence-unit>
</persistence>
  • 第 4 行:与数据库关联的持久化单元 [dbproduitscategories]。其名称可自定义(属性 name);
  • 第6-10行:待管理的五个实体JPA;
  • 第13-17行:另一个与数据库[dbproduits]关联的持久化单元。OpenJpa允许一个持久化文件包含多个持久化单元,而EclipseLink不支持此功能。 需要提醒的是,在 Hibernate 中,我们并未使用持久化文件;

8.5. 测试层

  

上述测试与 Spring JDBC、Spring JPA Hibernate 以及 Spring JPA EclipseLink 的实现完全一致。如有需要,请参阅以下页面:

应使用的运行配置如下:

为了使 JPA 实体通过 OpenJpa 进行增强(woven),必须使用 Java 代理启动 Java 应用程序或 JUnit 测试。 该代理将在 JVM 和 [http://openjpa.apache.org/builds/1.2.3/apache-openjpa/docs/ref_guide_pc_enhance.html] 加载 JPA 实体之前对其进行增强。以 [JUnitTestDao] 测试的配置为例:

  • 在 [Arguments] [1] 选项卡中,需按照 [2] 的语法,将 Java 代理的引用提供给 JVM。 该 Java 代理即位于 [<m2-repo>/org/apache/openjpa/<version>] 文件夹中的 [openjpa-<version>.jar] 归档文件。<m2-repo> 文件夹已在 Eclipse 配置中指定(如下所示的 [3]):

在上文的 [2] 中,我们使用了一个由用户定义的变量 [M2_REPO]:

 

将变量 [M2_REPO] 的值设置为 [3] 中可见的值。

针对 [JUnitTestDao] 和 [JUnitTestPushTheLimits] 的测试结果如下:

  • 在 [1]、[JUnitTestPushTheLimits-EclipseLink] 中:70.583 秒;
  • 转换为 [2]、[JUnitTestPushTheLimits-Hibernate]:78.945 秒;
  • 在 [3]、[JUnitTestPushTheLimits-JDBC] 中:36.09 秒;
  • 在 [4]、[JUnitTestPushTheLimits-OpenJpa] 中:80.394 秒;

针对测试 [JUnitTestProxies] 获得的控制台结果如下:

Vidage de la base de données --------------------------------
doNothing
Vidage de la base de données --------------------------------
getShortCategoriesByName1 --------------------------------
Catégorie de type : PROXY
Catégorie :
Exception : java.lang.NullPointerException, Message : null
Vidage de la base de données --------------------------------
getLongCategoriesByName1 --------------------------------
Catégorie de type : POJO
Catégorie :
1
Vidage de la base de données --------------------------------
getShortProduitsByName1 --------------------------------
Produit de type : PROXY
Nom de la catégorie du produit :
Exception : java.lang.NullPointerException, Message : null
Vidage de la base de données --------------------------------
getLongProduitsByName1 --------------------------------
Produit de type : POJO
Nom de la catégorie du produit :
categorie[0]

由此可见,当访问类型为 PROXY 的类中的字段 [Categorie.produits],以及访问类型为 PROXY 的产品中的字段 [Produit.categorie] 时, 这两种情况下(第7行和第17行)都存在一个null指针