Skip to content

9. 基于 JPA 实体生成数据库

可以基于 JPA 实体创建数据库表。下面我们将演示这一过程。这样做的目的是验证基于 JPA 实体生成的数据库是否正是我们想要的。

9.1. 工作环境的搭建

我们将首先使用 JPA、EclipseLink 和 [1] 这三个实体进行操作。

然后,我们使用客户端 [MyManager] 删除数据库中的表 MySQL 和 [dbproduitscategories](参见第 23.5 节)。 首先删除包含外键的表 [1-3]:

然后,我们从剩下的三个表开始:

对于项目 [spring-jdbc-01 à 03] 所使用的表 [dbproduits],我们也采取同样的操作:

此外,还需导入这两个数据库的生成项目:

  • 在 [1] 中,需导入项目 [generic-create-dbproduits],该项目位于 [<exemples>/spring-database-generic/spring-jpa] 和 [2] 中;
  • 在 [4] 中,导入项目 [generic-create-dbproduitscategories],该项目位于 [<exemples>/spring-database-generic/spring-jpa] 和 [5] 中;

注意:按 Alt-F5 并重新生成所有 Maven 项目;

9.2. 生成 [dbproduitscategories] 数据库

 

9.2.1. Maven配置

该项目的 [pom.xml] 文件如下:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>dvp.spring.database</groupId>
    <artifactId>generic-create-dbproduitscategories</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>generic-create-dbproduitscategories</name>
    <description>création de la bases de données [dbproduitscategories] à l'aide des annotations JPA</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- spring-jpa-generic -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-jpa-generic</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- Weaver Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-instrument</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>spring.data.console.Main</start-class>
        <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>
  • 第 22-26 行:对第 6.4 节中讨论的 [spring-jpa-generic] 项目的依赖;
  • 第28-32行:对一个Weaver的依赖,该Weaver将用于增强JPA中的EclipseLink和OpenJpa实现。 该依赖在文件 [pom.xml] 中并非必需,但其 JAR 文件将作为所使用的 Java 代理。将该依赖添加到文件 [pom.xml] 中可确保该 JAR 文件确实可用;

最终,依赖关系如下:

  

9.2.2. Spring 配置

  

类 [AppConfig] 用于配置 Spring 项目:


package console;

import generic.jpa.config.ConfigJpa;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration
@Import({ ConfigJpa.class })
@EnableJpaRepositories(basePackages = { "console" })
public class AppConfig {

}
  • 第 10 行:该类从 [ConfigJpa] 类中获取 Bean。需要说明的是,该类处理的是数据库 [dbproduitscategories] 中的实体 JPA(参见第 6.3 节);
  • 第 11 行:声明需扫描 [console] 包以查找 [CrudRepository] 实例;

在类 [ConfigJpa] 中,包含以下 Bean(具体内容因所使用的 JPA 实现而异):


    // 提供程序 JPA
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        // 注:JPA 实体和 Eclipselink 配置位于文件 META-INF/persistence.xml 中
        EclipseLinkJpaVendorAdapter eclipseLinkJpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
        eclipseLinkJpaVendorAdapter.setShowSql(false);
        eclipseLinkJpaVendorAdapter.setDatabase(Database.MYSQL);
        eclipseLinkJpaVendorAdapter.setGenerateDdl(true);
        return eclipseLinkJpaVendorAdapter;
}

此处关键的是第 8 行。该行存在于所有使用的 JPA 实现中。它规定,如果与 JPA 实体关联的表不存在,则必须创建这些表。我们将基于此属性来生成这些表。

9.2.3. 存储库

  

[ProduitsRepository] 接口如下:


package console;

import generic.jpa.entities.dbproduitscategories.Produit;

import org.springframework.data.repository.CrudRepository;

public interface ProduitsRepository extends CrudRepository<Produit, Long> {

}

正是它的实例化将触发 JPA 层的实例化。 事实上,在第7行,该接口引用了实体 JPA [Produit],这将强制实例化 JPA 层。 其实,只要将任何引用了 JPA 实体的 [CrudRepository] 接口放入其中即可。 事实上,尽管 [repository] 仅引用了 JPA 和 [Produit] 这两个实体,但生成的却是所有 JPA 实体的所有表。

9.2.4. 可执行类

  

类 [CreateDatabase] 如下所示:


package console;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class CreateDataBase {

    public static void main(String[] args) {
        // 只需实例化 Spring 上下文即可创建数据库表 [dbproduitscategories]
        // 还必须至少有一个 Spring Data Repository,否则什么也不会发生
        System.out.println("Travail en cours...");
        new AnnotationConfigApplicationContext(AppConfig.class).close();
        System.out.println("Travail terminé...");
    }

}
  • 第 11 行:实例化 Spring 上下文并立即关闭。在此上下文中,存在 Bean [ProduitsRepository],它引用了实体 JPA 和 [Produit]。 这足以实例化 JPA 层,从而生成 [dbproduitscategories] 数据库中的表。

当前配置如下:

 
  • [JDBC] 层配置为 MySQL 的 [dbproduitscategories] 数据库;
  • 层 [JPA] 通过 EclipseLink 实现;
  • 数据库 [dbproduitscategories] 没有表;

:按 Alt-F5 并重新生成所有 Maven 项目;

使用以下运行配置:

  • 在 [1-2] 中,该运行配置需要一个 Java 代理才能使测试通过。 根据具体情况,EclipseLink 并不总是需要该代理,但在此处若缺失该代理,执行将失败。该代理并非 EclipseLink 代理,而是 Spring 代理。它由以下依赖项提供:

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-instrument</artifactId>
            <scope>runtime</scope>
</dependency>

该项目中的 [pom.xml] 文件中。该代理位于 [<m2-repo>/org/springframework/spring-instrument/4.1.6.RELEASE/spring-instrument-4.1.6.RELEASE.jar] 中,其中 <m2-repo> 是 Maven 的本地仓库;

执行后得到以下结果:

在 [3] 中,可以看到表已生成。现在检查数据库的 DDL(领域定义语言):

用于生成表的脚本 SQL 也可以保存为文件 [1]。

 

生成的脚本 SQL 如下:


SET FOREIGN_KEY_CHECKS=0;

USE `dbproduitscategories`;

CREATE TABLE `categories` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `NOM` (`NOM`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `produits` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `DESCRIPTION` VARCHAR(100) COLLATE utf8_general_ci DEFAULT NULL,
  `CATEGORIE_ID` BIGINT(20) NOT NULL,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PRIX` DOUBLE NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  `CATEGORIE` INTEGER(11) NOT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `NOM` (`NOM`) USING BTREE,
  KEY `FK_PRODUITS_CATEGORIE_ID` (`CATEGORIE_ID`) USING BTREE,
  CONSTRAINT `FK_PRODUITS_CATEGORIE_ID` FOREIGN KEY (`CATEGORIE_ID`) REFERENCES `categories` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `roles` (
...
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `users` (
 ...
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `users_roles` (
...
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

以脚本 SQL 为例,该脚本生成表 [PRODUITS](第 15-29 行):

  • 第 16 行:[ID] 是主键(第 23 行),其属性为 [AUTO_INCREMENT](第 5 行)。 这对应于实体 JPA 中字段 [id] 的注释 [@Id, @GeneratedValue(strategy = GenerationType.IDENTITY), @Column(name = ConfigJdbc.TAB_JPA_ID)];
  • 第 17 行:列 [DESCRIPTION] 的定义对应于实体 JPA 中字段 [description] 的注释 [@Column(name = ConfigJdbc.TAB_PRODUITS_DESCRIPTION, length = 100)];
  • 第 18 行:列 [CATEGORIE_ID] 是表 [PRODUITS] 中列 [CATEGORIES.ID](第 26 行)的外键。 此外,该外键具有属性 [ON DELETE CASCADE]。 这对应于字段 [Produit.categorie] 的注释 [@ManyToOne(fetch = FetchType.LAZY), @JoinColumn(name = ConfigJdbc.TAB_PRODUITS_CATEGORIE_ID)] 以及字段 [Categorie.produits] 的注释 [@OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL }), @CascadeOnDelete];
  • 第 19 行:列 [NOM] 的定义对应于字段 [Produit.nom] 的注释 [@Column(name = ConfigJdbc.TAB_PRODUITS_NOM, unique = true, length = 30, nullable = false)];
  • 第 20 行:列 [PRIX] 的定义对应于字段 [Produit.prix] 的注释 [@Column(name = ConfigJdbc.TAB_PRODUITS_PRIX, nullable = false)];
  • 第 24-25 行:脚本为表中的每个唯一列创建三个索引;

生成的表中,VERSIONING字段没有默认值,而Java代码却期望该字段有默认值。如果缺少该默认值,某些测试将无法通过。我们按以下方式添加该属性:

 
 
 

对于拥有 [VERSIONING] 列的五个表,均需进行此操作。默认值本身并不重要,关键在于其必须存在。随后,每当修改其所属的行时,该值就会递增 1。

完成上述操作后,请验证以下执行配置是否通过:

  • [spring-jdbc-generic-04.JUnitTestDao],用于测试 JDBC 的实现;
  • [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink],用于测试 JPA 的 Hibernate 或 Eclipselink 实现(此处为 EclipseLink)

这两次执行都必须成功。

9.2.6. 使用 Hibernate 生成表

我们使用以下 Eclipse 环境创建 Hibernate 表:

 

表的生成由名为 [generic-create-dbproduitscategories-hibernate] 的运行配置完成,且不使用 Java 代理;

Hibernate 生成的数据库脚本 SQL 如下:


SET FOREIGN_KEY_CHECKS=0;

USE `dbproduitscategories`;

CREATE TABLE `categories` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `UK_7ajcg7japnxw846ru01damg8s` (`NOM`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `produits` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `DESCRIPTION` VARCHAR(100) COLLATE utf8_general_ci DEFAULT NULL,
  `CATEGORIE_ID` BIGINT(20) NOT NULL,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PRIX` DOUBLE NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `UK_hfvjn9lp7qoo5x79uu0ump3rf` (`NOM`) USING BTREE,
  KEY `FK_p3foj9yrqnmi7856n9s8mbpue` (`CATEGORIE_ID`) USING BTREE,
  CONSTRAINT `FK_p3foj9yrqnmi7856n9s8mbpue` FOREIGN KEY (`CATEGORIE_ID`) REFERENCES `categories` (`ID`)
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `roles` (
...
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `users` (
...
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `users_roles` (
 ...
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

生成的表是相同的,因为 Hibernate 也使用了注解 JPA。对于 Hibernate,我没有找到与注解 EclipseLink [@OnCascadeDelete] 对应的注解,该注解生成了SQL [ON DELTE CASCADE] 属性(第 25 行)。因此必须手动生成该属性,因为测试需要它:

 

对于表 [USERS_ROLES] 中的两个外键,也需进行同样的操作:

最后,与EclipseLink的实现方式相同,这五个表中的[VERSIONING]列必须设置默认值:

 

完成上述操作后,请验证以下执行配置是否通过:

  • [spring-jdbc-generic-04.JUnitTestDao](用于测试 JDBC 实现);
  • [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink],用于测试 JPA 的 Hibernate 或 Eclipselink 实现(此处为 Hibernate)

这两次执行都必须成功。

9.2.7. 使用 OpenJpa 生成表

我们使用 JPA 和 OpenJpa 实现重新执行上述步骤:

 

注意:按 Alt-F5 并重新生成所有 Maven 项目;

我们将配置项目 [mysql-config-jpa-openjpa] 的类 [ConfigJpa] 修改如下:


package generic.jpa.config;

import generic.jdbc.config.ConfigJdbc;

import java.util.Map;

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;
    }
..
    // EntityManagerFactory
    @Bean
    public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(jpaVendorAdapter);
        factory.setPackagesToScan(ENTITIES_PACKAGES);
        Map<String, Object> mapJpaProperties = factory.getJpaPropertyMap();
        mapJpaProperties.put("openjpa.jdbc.MappingDefaults",
                "ForeignKeyDeleteAction=cascade,JoinForeignKeyDeleteAction=restrict");
        factory.setDataSource(dataSource);
        factory.afterPropertiesSet();
        return factory.getObject();
    }
}
  • 第 40-41 行:为 OpenJPA 创建一个属性,用于指定生成表时外键的生成方式。若无此属性,则不会生成外键。 属性 [ForeignKeyDeleteAction=cascade] 用于在这些外键上生成属性 [ON DELETE CASCADE];

表的生成由名为 [generic-create-dbproduitscategories-openjpa] 的运行配置负责,该配置包含两个 Java 代理;

Image

  • 第一个 Java 代理是已用于 EclipseLink 的 Spring 代理;
  • 第二个 Java 代理由 OpenJpa 提供;

生成的数据库中的脚本 SQL 如下:


SET FOREIGN_KEY_CHECKS=0;

USE `dbproduitscategories`;

CREATE TABLE `categories` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `U_CTGORIS_NOM` (`NOM`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `produits` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `DESCRIPTION` VARCHAR(100) COLLATE utf8_general_ci DEFAULT NULL,
  `CATEGORIE_ID` BIGINT(20) DEFAULT NULL,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PRIX` DOUBLE NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `U_PRODUTS_NOM` (`NOM`) USING BTREE,
  KEY `CATEGORIE_ID` (`CATEGORIE_ID`) USING BTREE,
  CONSTRAINT `produits_ibfk_1` FOREIGN KEY (`CATEGORIE_ID`) REFERENCES `categories` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `roles` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `NAME` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `U_ROLES_NAME` (`NAME`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `users` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `LOGIN` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `NAME` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PASSWORD` VARCHAR(60) COLLATE utf8_general_ci NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `U_USERS_LOGIN` (`LOGIN`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `users_roles` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  `ROLE_ID` BIGINT(20) NOT NULL,
  `USER_ID` BIGINT(20) NOT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  KEY `ROLE_ID` (`ROLE_ID`) USING BTREE,
  KEY `USER_ID` (`USER_ID`) USING BTREE,
  CONSTRAINT `users_roles_ibfk_2` FOREIGN KEY (`USER_ID`) REFERENCES `users` (`ID`) ON DELETE CASCADE,
  CONSTRAINT `users_roles_ibfk_1` FOREIGN KEY (`ROLE_ID`) REFERENCES `roles` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

这与 EclipseLink 中的脚本相同。因此,我们将对表进行相同的修正。完成上述操作后,请验证以下执行配置是否通过:

  • [spring-jdbc-generic-04.JUnitTestDao],用于测试 JDBC 的实现;
  • [spring-jpa-generic-JUnitTestDao-openjpa],用于测试 JPA 和 OpenJpa 的实现;

这两次执行都必须成功。

9.3. 生成基础项目 [dbproduits]

[dbproduits] 数据库由 [spring-jdbc-01 à 03] 项目使用。也可以从 JPA 实体生成该数据库。

  • 在 [1] 中,Eclipse 项目将处于 MySQL / EclipseLink 的配置中。 数据库生成项目 [dbproduits] 对应的是 [generic-create-dbproduits];
  • 在 [2] 中,待生成的表为 [PRODUITS];

:按 Alt-F5 并重新生成所有 Maven 项目;

9.3.1. Maven 配置

项目 [generic-create-dbproduits] 的 Maven 配置如下:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>dvp.spring.database</groupId>
    <artifactId>generic-create-dbproduits</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>generic-create-dbproduits</name>
    <description>création de la bases de données [dbproduits] à l'aide des annotations JPA</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- SGBD 的配置 JPA -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>generic-config-jpa</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>spring.data.console.Main</start-class>
        <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>

该项目仅有一个依赖项(第22-26行),用于配置JPA层。最终,依赖项如下:

  

9.3.2. Spring 配置

  

Spring配置类如下:


package console;

import generic.jdbc.config.ConfigJdbc;
import generic.jpa.config.ConfigJpa;

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.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;

@EnableJpaRepositories(basePackages = { "console" })
@Configuration
@Import({ ConfigJpa.class })
public class AppConfig {

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

    // EntityManagerFactory
    @Bean
    public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(jpaVendorAdapter);
        factory.setPersistenceUnitName("generic-jpa-entities-dbproduits");
        factory.setDataSource(dataSource);
        factory.afterPropertiesSet();
        return factory.getObject();
    }

}
  • 第 18 行:导入类 [ConfigJpa](第 7.3 节)中的 Bean;
  • 第 22-35 行:重新定义数据源 [dataSource]。 在 [ConfigJpa] 中,数据源是数据库 [dbproduitscategories]。此处将使用数据库 [dbproduits];
  • 第38-46行:重新定义类[ConfigJpa]中的Bean [entityManagerFactory]。 在该类中,实体 JPA 原本是 [Produit, Categorie]。此处仅为 [Produit],且其定义与配置 JPA 层的项目中的定义不一致;
  • 第 42 行:为了定义这个新的实体 JPA,引用了在文件 [META-INF/persistence.xml] 中定义的实体 JPA:
  

文件 [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-dbproduits" transaction-type="RESOURCE_LOCAL">
        <!-- 实体 JPA -->
        <class>generic.jpa.entities.dbproduits.Produit</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
    </persistence-unit>
</persistence>
  • 第 6 行:唯一的实体 JPA;
  • 第 4 行:持久化单元名称 [generic-jpa-entities-dbproduits],该单元在 Bean [entityManagerFactory] 中被引用;

9.3.3. 实体 JPA [Produit]

  

实体 JPA 在项目 [mysql-config-jpa-eclipselink] 中定义如下:


package generic.jpa.entities.dbproduits;

import generic.jdbc.config.ConfigJdbc;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity(name="Produit1")
@Table(name = ConfigJdbc.TAB_PRODUITS)
public class Produit {

    // 字段
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = ConfigJdbc.TAB_PRODUITS_ID)
    private int id;
    @Column(name = ConfigJdbc.TAB_PRODUITS_NOM, unique = true, length = 30, nullable = false)
    private String nom;
    @Column(name = ConfigJdbc.TAB_PRODUITS_CATEGORIE, nullable = false)
    private int categorie;
    @Column(name = ConfigJdbc.TAB_PRODUITS_PRIX, nullable = false)
    private double prix;
    @Column(name = ConfigJdbc.TAB_PRODUITS_DESCRIPTION, length = 100, nullable = false)
    private String description;

    // 构造函数
    public Produit() {

    }

    public Produit(int id, String nom, int categorie, double prix, String description) {
        this.id = id;
        this.nom = nom;
        this.categorie = categorie;
        this.prix = prix;
        this.description = description;
    }

    // getter 和 setter
    ...
}

这是一个如今已成经典的 JPA 定义。只需注意以下几点:

  • 第 12 行:该实体被命名为 [Produit1]。默认情况下,实体的名称即为类名,此处为 [Produit]。 但由于同一项目中还存在另一个实体 JPA [Produit],因此在执行之前就报错了。我们通过以下方式解决了这个问题;
  • 第24行:此处的类别仅为一个数字;
  • 实体之间不存在关联。因此情况非常简单;

9.3.4. 可执行类

  

类 [CreateDatabase] 如下所示:


package console;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class CreateDataBase {

    public static void main(String[] args) {
        // 只需实例化 Spring 上下文即可创建数据库表[dbproduits]
        // 还必须至少有一个 Spring Data Repository,否则什么也不会发生
        System.out.println("Travail en cours...");
        new AnnotationConfigApplicationContext(AppConfig.class).close();
        System.out.println("Travail terminé...");
    }

}

这是我们之前已经遇到过的代码。

表 [PRODUITS] 已根据以下执行配置创建:

控制台日志如下:

Travail en cours...
16:31:17.668 [main] INFO  o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6fadae5d: startup date [Tue Jun 02 16:31:17 CEST 2015]; root of context hierarchy
16:31:17.758 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'entityManagerFactory': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jpa.config.ConfigJpa; factoryMethodName=entityManagerFactory; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jpa.config.ConfigJpa] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=entityManagerFactory; initMethodName=null; destroyMethodName=(inferred); defined in console.AppConfig]
16:31:17.759 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'dataSource': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jpa.config.ConfigJpa; factoryMethodName=dataSource; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jpa.config.ConfigJpa] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=dataSource; initMethodName=null; destroyMethodName=(inferred); defined in console.AppConfig]
16:31:18.462 [main] INFO  o.s.o.j.LocalContainerEntityManagerFactoryBean - Building JPA container EntityManagerFactory for persistence unit 'generic-jpa-entities-dbproduits'
[EL Info]: server: 2015-06-02 16:31:18.624--ServerSession(817299424)--Detected server platform: org.eclipse.persistence.platform.server.NoServerPlatform.
[EL Info]: server: 2015-06-02 16:31:19.015--ServerSession(817299424)--Detected server platform: org.eclipse.persistence.platform.server.NoServerPlatform.
[EL Info]: 2015-06-02 16:31:19.021--ServerSession(817299424)--EclipseLink, version: Eclipse Persistence Services - 2.6.0.v20150309-bf26070
[EL Info]: connection: 2015-06-02 16:31:20.122--ServerSession(817299424)--/file:/D:/data/istia-1415/spring data/dvp/dvp-spring-database-04/spring-database-generic/spring-jpa/generic-create-dbproduits/target/classes_generic-jpa-entities-dbproduits login successful
16:31:20.654 [main] INFO  o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6fadae5d: startup date [Tue Jun 02 16:31:17 CEST 2015]; root of context hierarchy
[EL Info]: connection: 2015-06-02 16:31:20.656--ServerSession(817299424)--/file:/D:/data/istia-1415/spring data/dvp/dvp-spring-database-04/spring-database-generic/spring-jpa/generic-create-dbproduits/target/classes_generic-jpa-entities-dbproduits logout successful
[EL Warning]: 2015-06-02 16:31:20.657--session_manager_no_partition
Travail terminé...

现在,让我们回到客户端 [MyManager],并刷新 [1-2] 的显示:

在 [3] 中,可以看到已生成了一张表。现在检查数据库的 DDL(域定义语言):


SET FOREIGN_KEY_CHECKS=0;

USE `dbproduits`;

CREATE TABLE `produits` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `CATEGORIE` INTEGER(11) NOT NULL,
  `DESCRIPTION` VARCHAR(100) COLLATE utf8_general_ci NOT NULL,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PRIX` DOUBLE NOT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `NOM` (`NOM`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

确实得到了预期的表。为了验证这一点,我们将执行以下配置:

该配置应能成功执行。

9.3.6. Hibernate 生成

 

:按 Alt-F5 并重新生成所有 Maven 项目;

运行配置如下:

Hibernate生成的脚本SQL如下:


USE `dbproduits`;

CREATE TABLE `produits` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `CATEGORIE` INTEGER(11) NOT NULL,
  `DESCRIPTION` VARCHAR(100) COLLATE utf8_general_ci NOT NULL,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PRIX` DOUBLE NOT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `UK_hfvjn9lp7qoo5x79uu0ump3rf` (`NOM`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

9.3.7. 生成 OpenJpa

 

注意:按 Alt-F5 并重新生成所有 Maven 项目;

运行配置如下:

由 OpenJpa 生成的脚本 SQL 如下:


USE `dbproduits`;

CREATE TABLE `produits` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `CATEGORIE` INTEGER(11) NOT NULL,
  `DESCRIPTION` VARCHAR(100) COLLATE utf8_general_ci NOT NULL,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PRIX` DOUBLE NOT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `U_PRODUTS_NOM` (`NOM`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;