Skip to content

9. Generación de bases de datos a partir de las entidades JPA

Es posible crear las tablas de una base de datos a partir de las entidades JPA. Esto es lo que vamos a mostrar ahora. El interés radica en verificar que la base de datos generada a partir de las entidades JPA es efectivamente la que queremos.

9.1. Configuración del entorno de trabajo

En primer lugar, vamos a trabajar con una implementación JPA EclipseLink [1].

A continuación, eliminamos las tablas de la base de datos MySQL [dbproduitscategories] con el cliente [MyManager] (véase el apartado 23.5). Comenzamos eliminando las tablas que contienen las claves externas [1-3]:

A continuación, volvemos a empezar con las tres tablas restantes [4-6]:

Hacemos lo mismo con la tabla [dbproduits] utilizada por los proyectos [spring-jdbc-01 à 03]:

Además, hay que importar los dos proyectos de generación de las dos bases de datos:

  • en [1], se importa el proyecto [generic-create-dbproduits], que se encuentra en [<exemples>/spring-database-generic/spring-jpa] [2];
  • en [4], se importa el proyecto [generic-create-dbproduitscategories], que se encuentra en [<exemples>/spring-database-generic/spring-jpa] [5];

Nota: pulsa Alt-F5 y regenera todos los proyectos Maven;

9.2. Generación de la base [dbproduitscategories]

 

9.2.1. Configuración de Maven

El archivo [pom.xml] del proyecto es el siguiente:


<?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>
  • líneas 22-26: la dependencia del proyecto [spring-jpa-generic] analizado en el apartado 6.4;
  • líneas 28-32: la dependencia de un weaver que se utilizará para enriquecer las entidades JPA de las implementaciones EclipseLink y OpenJpa. Su dependencia no es necesaria en el archivo [pom.xml], pero su jar será el agente Java utilizado. Incluir la dependencia en el archivo [pom.xml] nos garantiza que jar estará disponible;

En definitiva, las dependencias son las siguientes:

  

9.2.2. Configuración de Spring

  

La clase [AppConfig] configura el proyecto 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 {

}
  • línea 10: la clase recupera los beans de la clase [ConfigJpa]. Recordemos que esta clase trabaja con las entidades JPA de la base de datos [dbproduitscategories] (véase el apartado 6.3);
  • línea 11: se declara que el paquete [console] debe ser escaneado para encontrar instancias [CrudRepository];

En la clase [ConfigJpa], se encuentra el siguiente bean (varía según la implementación JPA utilizada):


    // el proveedor JPA
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        // Nota: las entidades JPA y la configuración de Eclipselink se encuentran en el archivo META-INF/persistence.xml
        EclipseLinkJpaVendorAdapter eclipseLinkJpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
        eclipseLinkJpaVendorAdapter.setShowSql(false);
        eclipseLinkJpaVendorAdapter.setDatabase(Database.MYSQL);
        eclipseLinkJpaVendorAdapter.setGenerateDdl(true);
        return eclipseLinkJpaVendorAdapter;
}

Lo importante aquí es la línea 8. Está presente en todas las implementaciones JPA utilizadas. Indica que, si las tablas asociadas a las entidades JPA no existen, deben crearse. Nos basaremos en esta propiedad para generar las tablas.

9.2.3. Los repositorios

  

La interfaz [ProduitsRepository] es la siguiente:


package console;

import generic.jpa.entities.dbproduitscategories.Produit;

import org.springframework.data.repository.CrudRepository;

public interface ProduitsRepository extends CrudRepository<Produit, Long> {

}

Es su instanciación la que provocará la instanciación de la capa JPA. De hecho, en la línea 7, la interfaz hace referencia a la entidad JPA [Produit], lo que forzará la instanciación de la capa JPA. Se podría haber utilizado cualquier interfaz [CrudRepository] que hiciera referencia a una de las entidades JPA. De hecho, se observa que, aunque [repository] solo hace referencia a la entidad JPA [Produit], se generan todas las tablas de todas las entidades JPA.

9.2.4. La clase ejecutable

  

La clase [CreateDatabase] es la siguiente:


package console;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class CreateDataBase {

    public static void main(String[] args) {
        // basta con instanciar el contexto Spring para crear las tablas de la base de datos [dbproduitscategories]
        // también se necesita al menos un repositorio Spring Data; de lo contrario, no ocurre nada
        System.out.println("Travail en cours...");
        new AnnotationConfigApplicationContext(AppConfig.class).close();
        System.out.println("Travail terminé...");
    }

}
  • línea 11: se instancia el contexto Spring para cerrarlo inmediatamente. En este contexto, se encuentra el bean [ProduitsRepository] que hace referencia a la entidad JPA [Produit]. Esto es suficiente para instanciar la capa JPA y, por lo tanto, generar las tablas de la base de datos [dbproduitscategories].

Nos encontramos en la siguiente configuración:

 
  • la capa [JDBC] está configurada para la base de datos [dbproduitscategories] de MySQL;
  • la capa [JPA] se implementa con EclipseLink;
  • la base de datos [dbproduitscategories] no tiene tablas;

Nota: pulsa Alt-F5 y regenera todos los proyectos Maven;

Se utiliza la siguiente configuración de ejecución:

  • en [1-2], esta configuración de ejecución requiere un agente Java para que la prueba se complete con éxito. Dependiendo del caso, EclipseLink no siempre necesita este agente, pero aquí la ejecución falla si no está presente. Este agente no es un agente EclipseLink, sino un agente Spring. Lo proporciona la dependencia:

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

registrada en el archivo [pom.xml] del proyecto. El agente se encuentra en [<m2-repo>/org/springframework/spring-instrument/4.1.6.RELEASE/spring-instrument-4.1.6.RELEASE.jar], donde <m2-repo> es el repositorio local de Maven;

La ejecución da el siguiente resultado:

En [3], vemos que se han generado las tablas. Ahora comprobemos el DDL (Domain Definition Language) de la base de datos:

El script SQL de generación de tablas también se puede guardar en un archivo [1].

 

El script SQL generado es el siguiente:


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

Veamos, por ejemplo, el script SQL que genera la tabla [PRODUITS] (líneas 15-29):

  • línea 16: [ID] es la clave primaria (línea 23) con el atributo [AUTO_INCREMENT] (línea 5). Esto se corresponde con las anotaciones [@Id, @GeneratedValue(strategy = GenerationType.IDENTITY), @Column(name = ConfigJdbc.TAB_JPA_ID)] del campo [id] de la entidad JPA;
  • línea 17: la definición de la columna [DESCRIPTION] corresponde a la anotación [@Column(name = ConfigJdbc.TAB_PRODUITS_DESCRIPTION, length = 100)] del campo [description] de la entidad JPA;
  • línea 18: la columna [CATEGORIE_ID] es una clave externa de la tabla [PRODUITS] en la columna [CATEGORIES.ID] (línea 26). Además, esta clave externa tiene el atributo [ON DELETE CASCADE]. Esto se corresponde con las anotaciones [@ManyToOne(fetch = FetchType.LAZY), @JoinColumn(name = ConfigJdbc.TAB_PRODUITS_CATEGORIE_ID)] del campo [Produit.categorie] y con la anotación [@OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL }), @CascadeOnDelete] del campo [Categorie.produits];
  • línea 19: la definición de la columna [NOM] corresponde a la anotación [@Column(name = ConfigJdbc.TAB_PRODUITS_NOM, unique = true, length = 30, nullable = false)] del campo [Produit.nom];
  • línea 20: la definición de la columna [PRIX] corresponde a la anotación [@Column(name = ConfigJdbc.TAB_PRODUITS_PRIX, nullable = false)] del campo [Produit.prix];
  • líneas 24-25: el script crea tres índices para cada una de las columnas únicas de la tabla;

Las tablas generadas no tienen un valor por defecto para el campo VERSIONING de las tablas, mientras que el código Java espera que lo haya. Si este valor por defecto no está presente, algunas pruebas no se superan. Se añade este atributo de la siguiente manera:

 
 
 

Se hace para las cinco tablas que tienen la columna [VERSIONING]. El valor por defecto no importa. Solo tiene que existir. A continuación, se incrementa en 1 cada vez que se modifica la línea a la que pertenece.

Una vez hecho esto, comprueba que se ejecutan correctamente las siguientes configuraciones:

  • [spring-jdbc-generic-04.JUnitTestDao], que prueba la implementación JDBC;
  • [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink], que prueba las implementaciones JPA Hibernate o Eclipselink (en este caso será EclipseLink)

Ambas ejecuciones deben completarse con éxito.

9.2.6. Generación de tablas con Hibernate

Creamos las tablas de Hibernate con el siguiente entorno de Eclipse:

 

La generación de las tablas se realiza mediante la configuración de ejecución denominada [generic-create-dbproduitscategories-hibernate] sin agente Java;

El script SQL de la base de datos generada por Hibernate es el siguiente:


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

Las tablas generadas son las mismas, ya que Hibernate también ha utilizado las anotaciones JPA. Para Hibernate, no he encontrado el equivalente a la anotación EclipseLink [@OnCascadeDelete] que generó elatributo SQL [ON DELTE CASCADE] en la clave externa [PRODUITS.CATEGORIE_ID] (línea 25). Por lo tanto, hay que generar este atributo a mano, ya que es necesario para las pruebas:

 

Hay que hacer lo mismo con las dos claves externas de la tabla [USERS_ROLES]:

Por último, al igual que se hizo con la implementación EclipseLink, es necesario que las columnas [VERSIONING] de las cinco tablas tengan un valor por defecto:

 

Una vez hecho esto, compruebe que se ejecutan correctamente las siguientes configuraciones:

  • [spring-jdbc-generic-04.JUnitTestDao], que prueba la implementación JDBC;
  • [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink], que prueba las implementaciones JPA de Hibernate o Eclipselink (en este caso será Hibernate)

Ambas ejecuciones deben completarse con éxito.

9.2.7. Generación de tablas con OpenJpa

Repetimos el procedimiento anterior con una implementación JPA OpenJpa:

 

Nota: pulse Alt-F5 y vuelva a generar todos los proyectos Maven;

Modificamos la clase [ConfigJpa] que configura el proyecto [mysql-config-jpa-openjpa] de la siguiente manera:


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 {

    // el proveedor 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();
    }
}
  • líneas 40-41: creamos una propiedad para OpenJPA que indica cómo generar las claves externas al generar las tablas. Sin esta propiedad, no se generan las claves externas. El atributo [ForeignKeyDeleteAction=cascade] permite generar el atributo [ON DELETE CASCADE] en estas claves externas;

La generación de las tablas se realiza mediante la configuración de ejecución denominada [generic-create-dbproduitscategories-openjpa], que cuenta con dos agentes Java;

Image

  • el primer agente Java es el agente Spring ya utilizado con EclipseLink;
  • el segundo agente Java lo proporciona OpenJpa;

El script SQL de la base generada es entonces el siguiente:


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

Es el mismo que con EclipseLink. Por lo tanto, se realizarán las mismas correcciones en las tablas. Una vez hecho esto, compruebe que se ejecutan correctamente las siguientes configuraciones:

  • [spring-jdbc-generic-04.JUnitTestDao], que comprueba la implementación JDBC;
  • [spring-jpa-generic-JUnitTestDao-openjpa], que prueba una implementación de JPA OpenJpa;

Ambas ejecuciones deben completarse con éxito.

9.3. Generación de la base [dbproduits]

La base [dbproduits] es utilizada por los proyectos [spring-jdbc-01 à 03]. También se puede generar a partir de una entidad JPA.

  • en [1], los proyectos Eclipse. Estaremos en una configuración MySQL / EclipseLink. El proyecto de generación de la base [dbproduits] es [generic-create-dbproduits];
  • en [2], la tabla [PRODUITS] que se va a generar;

Nota: pulsa Alt-F5 y regenera todos los proyectos Maven;

9.3.1. Configuración de Maven

La configuración de Maven del proyecto [generic-create-dbproduits] es la siguiente:


<?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>
        <!-- configuración JPA del 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>

Solo hay una dependencia, en las líneas 22-26, en el proyecto que configura la capa JPA. Al final, las dependencias son las siguientes:

  

9.3.2. La configuración de Spring

  

La clase de configuración de Spring es la siguiente:


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 {

    // fuente de datos
    @Bean
    public DataSource dataSource() {
        // fuente de datos TomcatJdbc
        DataSource dataSource = new DataSource();
        // configuración de acceso JDBC
        dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
        dataSource.setUsername(ConfigJdbc.USER_DBPRODUITS);
        dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITS);
        dataSource.setUrl(ConfigJdbc.URL_DBPRODUITS);
        // conexiones abiertas inicialmente
        dataSource.setInitialSize(5);
        // resultado
        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();
    }

}
  • línea 18: se importan los beans de la clase [ConfigJpa] (apartado 7.3);
  • líneas 22-35: se redefine la fuente de datos [dataSource]. En [ConfigJpa], la fuente de datos es la base [dbproduitscategories]. Aquí será la base [dbproduits];
  • líneas 38-46: se redefine el bean [entityManagerFactory] de la clase [ConfigJpa]. En esta clase, las entidades JPA eran [Produit, Categorie]. Aquí es solo [Produit] y no tiene la misma definición que en el proyecto que configura la capa JPA;
  • línea 42: para definir esta nueva entidad JPA, se hace referencia a las entidades JPA definidas en el archivo [META-INF/persistence.xml]:
  

El archivo [persistence.xml] es el siguiente:


<?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">
        <!-- entidades JPA -->
        <class>generic.jpa.entities.dbproduits.Produit</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
    </persistence-unit>
</persistence>
  • línea 6: la única entidad JPA;
  • línea 4: el nombre de la unidad de persistencia [generic-jpa-entities-dbproduits] a la que se hace referencia en el bean [entityManagerFactory];

9.3.3. La entidad JPA [Produit]

  

La entidad JPA se define en el proyecto [mysql-config-jpa-eclipselink] de la siguiente manera:


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 {

    // campos
    @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;

    // constructores
    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 y setters
    ...
}

Se trata de una definición JPA que ya se ha convertido en un clásico. Solo cabe señalar los siguientes puntos:

  • línea 12: se ha asignado el nombre [Produit1] a la entidad. Por defecto, el nombre de una entidad es el nombre de la clase, en este caso [Produit]. Sin embargo, como hay otra entidad JPA [Produit] en el mismo proyecto, se ha señalado un error incluso antes de la ejecución. Lo hemos solucionado de esta manera;
  • línea 24: la categoría es aquí un simple número;
  • no hay relaciones entre entidades. Por lo tanto, tenemos una situación muy sencilla;

9.3.4. La clase ejecutable

  

La clase [CreateDatabase] es la siguiente:


package console;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class CreateDataBase {

    public static void main(String[] args) {
        // basta con instanciar el contexto Spring para crear las tablas de la base de datos [dbproduits]
        // también se necesita al menos un repositorio Spring Data; de lo contrario, no ocurre nada
        System.out.println("Travail en cours...");
        new AnnotationConfigApplicationContext(AppConfig.class).close();
        System.out.println("Travail terminé...");
    }

}

Es un código con el que ya nos hemos encontrado.

La tabla [PRODUITS] se crea con la siguiente configuración de ejecución:

Los registros de la consola son los siguientes:

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é...

Ahora, volvamos al cliente [MyManager] y actualicemos la vista [1-2]:

En [3], vemos que se ha generado una tabla. Ahora comprobemos el DDL (Domain Definition Language) de la base de datos:


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

Efectivamente, obtenemos la tabla esperada. Para verificarlo, ejecutaremos la siguiente configuración:

Debe completarse con éxito.

9.3.6. Generación de Hibernate

 

Nota: pulsa Alt-F5 y regenera todos los proyectos Maven;

La configuración de ejecución es la siguiente:

El script SQL generado por Hibernate es el siguiente:


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. Generación OpenJpa

 

Nota: pulsa Alt-F5 y regenera todos los proyectos Maven;

La configuración de ejecución es la siguiente:

El script SQL generado por OpenJpa es el siguiente:


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