Skip to content

9. 从 JPA 实体生成数据库

可以基于 JPA 实体创建数据库表。我们将在此演示这一过程。目的是验证由 JPA 实体生成的数据库是否确实是我们想要的。

9.1. 设置工作环境

我们将首先使用 EclipseLink JPA 实现 [1]。

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

然后,我们对剩下的三个表 [4-6] 重复上述操作:

对于 [spring-jdbc-01 至 03] 项目所使用的 [dbproduits] 表,我们也采用同样的方法:

此外,您还必须导入两个数据库生成项目:

  • 在 [1] 中,导入 [generic-create-dbproduits] 项目,该项目位于 [<examples>/spring-database-generic/spring-jpa] [2];
  • 在 [4] 中,导入 [generic-create-dbproduitscategories] 项目,该项目位于 [<examples>/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 行:对一个编织器的依赖,该编织器将用于增强 EclipseLink 和 OpenJpa 实现中的 JPA 实体。虽然 [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 实现):


    // 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;
}

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

9.2.3. 存储库

  

[ProductsRepository] 接口如下:


package console;
 
import generic.jpa.entities.dbproduitscategories.Produit;
 
import org.springframework.data.repository.CrudRepository;
 
public interface ProduitsRepository extends CrudRepository<Produit, Long> {
 
}

实例化此接口将触发 JPA 层的实例化。事实上,在第 7 行,该接口引用了 JPA 实体 [Product],这将强制实例化 JPA 层。我们本可以使用任何引用某个 JPA 实体的 [CrudRepository] 接口。 我们可以看到,尽管 [repository] 仅引用了 JPA 实体 [Product],但所有 JPA 实体的所有表均被生成。

9.2.4. 可执行类

  

[CreateDatabase] 类如下所示:


package console;
 
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class CreateDataBase {
 
    public static void main(String[] args) {
        // simply instantiate the Spring context to create the database tables [dbproduitscategories]
        // you also need at least one Spring Data Repository, otherwise nothing happens
        System.out.println("Travail en cours...");
        new AnnotationConfigApplicationContext(AppConfig.class).close();
        System.out.println("Travail terminé...");
    }
 
}
  • 第 11 行:我们实例化 Spring 上下文并立即将其关闭。在此上下文中,存在一个引用 JPA 实体 [Product] 的 [ProductsRepository] Bean。这足以实例化 JPA 层,从而在 [productcategories] 数据库中生成表。

当前配置如下:

 
  • [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'
;

让我们以生成 [PRODUCTS] 表的 SQL 脚本为例(第 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] 属性。这对应于 [@ManyToOne(fetch = FetchType.LAZY), @JoinColumn(name = ConfigJdbc.TAB_PRODUITS_CATEGORIE_ID)] 注解,以及 [Categorie.produits] 字段的 [@OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL }), @CascadeOnDelete] 注解;
  • 第 19 行:[NAME] 列的定义对应于 [Product.name] 字段的注解 [@Column(name = ConfigJdbc.TAB_PRODUITS_NAME, unique = true, length = 30, nullable = false)];
  • 第 20 行:[PRICE] 列的定义对应于 [Product.price] 字段的注解 [@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],用于测试 Hibernate 或 EclipseLink 的 JPA 实现(在此情况下,将使用 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] 对应的等效注解,该注解会在外键 [PRODUCTS.CATEGORY_ID] 上生成 SQL 属性 [ON DELETE CASCADE](第 25 行)。因此,该属性必须手动生成,因为它是测试所必需的:

 

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

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

 

完成此操作后,请验证以下测试配置是否通过:

  • [spring-jdbc-generic-04.JUnitTestDao],用于测试 JDBC 实现;
  • [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink],用于测试 Hibernate 或 Eclipselink 的 JPA 实现(在此情况下,将使用 Hibernate)

两项测试都必须通过。

9.2.7. 使用 OpenJpa 生成表

我们使用 OpenJpa JPA 实现重复上述步骤:

 

注意:按 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 {
 
    // the provider 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],用于测试 OpenJPA 的 JPA 实现;

这两次运行都必须成功。

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>
        <!-- configuration JPA of SGBD -->
        <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 {
 
    // 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_DBPRODUITS);
        dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITS);
        dataSource.setUrl(ConfigJdbc.URL_DBPRODUITS);
        // 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.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] 类中的 [entityManagerFactory] Bean。在该类中,JPA 实体为 [Product, Category]。此处仅为 [Product],且其定义与配置 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">
        <!-- entities JPA -->
        <class>generic.jpa.entities.dbproduits.Produit</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
    </persistence-unit>
</persistence>
  • 第 6 行:唯一的 JPA 实体;
  • 第 4 行:[entityManagerFactory] Bean 中引用的持久化单元名称 [generic-jpa-entities-dbproduits];

9.3.3. JPA 实体 [Product]

  

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 {
 
    // fields
    @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;
 
    // manufacturers
    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;
    }
 
    // getters and setters
    ...
}

这是一个现已成为标准的 JPA 定义。请注意以下几点:

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

9.3.4. 可执行类

  

[CreateDatabase] 类如下所示:


package console;
 
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class CreateDataBase {
 
    public static void main(String[] args) {
        // simply instantiate the Spring context to create the database tables [dbproduits]
        // you also need at least one Spring Data Repository, otherwise nothing happens
        System.out.println("Travail en cours...");
        new AnnotationConfigApplicationContext(AppConfig.class).close();
        System.out.println("Travail terminé...");
    }
 
}

这是我们之前见过的代码。

[PRODUCTS] 表是使用以下运行时配置创建的:

控制台日志如下:

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'
;