Skip to content

9. Gerar bases de dados a partir de entidades JPA

É possível criar tabelas de base de dados a partir de entidades JPA. Vamos demonstrar isso agora. O objetivo é verificar se a base de dados gerada a partir das entidades JPA é, de facto, aquela que pretendemos.

9.1. Configurar o ambiente de trabalho

Começaremos por trabalhar com uma implementação EclipseLink JPA [1].

Em seguida, eliminamos as tabelas da base de dados MySQL [dbproduitscategories] utilizando o cliente [MyManager] (ver Secção 23.5). Começamos por eliminar as tabelas que contêm as chaves estrangeiras [1-3]:

Em seguida, repetimos o processo com as três tabelas restantes [4-6]:

Fazemos o mesmo com a tabela [dbproduits] utilizada pelos projetos [spring-jdbc-01 a 03]:

Além disso, deve importar os dois projetos de geração de bases de dados:

  • em [1], importe o projeto [generic-create-dbproduits], que se encontra em [<examples>/spring-database-generic/spring-jpa] [2];
  • em [4], importe o projeto [generic-create-dbproduitscategories], que se encontra em [<examples>/spring-database-generic/spring-jpa] [5];

Nota: Prima Alt-F5 e regenere todos os projetos Maven;

9.2. Gerar a base de dados [dbproduitscategories]

 

9.2.1. Configuração do Maven

O ficheiro [pom.xml] do projeto é o seguinte:


<?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>
  • linhas 22–26: a dependência do projeto [spring-jpa-generic] discutido na Secção 6.4;
  • linhas 28–32: a dependência de um weaver que será utilizado para enriquecer as entidades JPA das implementações EclipseLink e OpenJpa. A sua dependência não é obrigatória no ficheiro [pom.xml], mas o seu JAR será o agente Java utilizado. A inclusão da dependência no ficheiro [pom.xml] garante que o JAR estará disponível;

No final, as dependências são as seguintes:

  

9.2.2. Configuração do Spring

  

A classe [AppConfig] configura o projeto 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 {
 
}
  • Linha 10: A classe recupera os beans da classe [ConfigJpa]. Note-se que esta classe trabalha com as entidades JPA na base de dados [dbproduitscategories] (ver Secção 6.3);
  • linha 11: declaramos que o pacote [console] deve ser analisado para encontrar instâncias de [CrudRepository];

Na classe [ConfigJpa], encontramos o seguinte bean (varia consoante a implementação JPA utilizada):


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

A linha 8 é a mais importante aqui. Está presente em todas as implementações JPA utilizadas. Especifica que, se as tabelas associadas às entidades JPA não existirem, devem ser criadas. Utilizaremos esta propriedade para gerar as tabelas.

9.2.3. Os repositórios

  

A interface [ProductsRepository] é a seguinte:


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

A instanciação desta interface irá desencadear a instanciação da camada JPA. De facto, na linha 7, a interface faz referência à entidade JPA [Product], o que forçará a instanciação da camada JPA. Poderíamos ter utilizado qualquer interface [CrudRepository] que fizesse referência a uma das entidades JPA. Podemos ver que, embora o [repositório] apenas faça referência à entidade JPA [Product], todas as tabelas para todas as entidades JPA são geradas.

9.2.4. A classe executável

  

A classe [CreateDatabase] é a seguinte:


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é...");
    }
 
}
  • Linha 11: Instanciamos o contexto Spring e fechamo-lo imediatamente. Neste contexto, existe o bean [ProductsRepository] que faz referência à entidade JPA [Product]. Isto é suficiente para instanciar a camada JPA e, assim, gerar as tabelas na base de dados [productcategories].

Estamos na seguinte configuração:

 
  • a camada [JDBC] está configurada para a base de dados MySQL [dbproduitscategories];
  • a camada [JPA] está implementada com o EclipseLink;
  • a base de dados [dbproduitscategories] não tem tabelas;

Nota: Prima Alt-F5 e gere novamente todos os projetos Maven;

Estamos a utilizar a seguinte configuração de execução:

  • Em [1-2], esta configuração de execução requer um agente Java para que o teste seja bem-sucedido. Dependendo da situação, o EclipseLink nem sempre necessita deste agente, mas, neste caso, a execução falha se ele não estiver presente. Este agente não é um agente do EclipseLink, mas sim um agente do Spring. É fornecido pela dependência:

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

incluído no ficheiro [pom.xml] do projeto. O agente encontra-se em [<m2-repo>/org/springframework/spring-instrument/4.1.6.RELEASE/spring-instrument-4.1.6.RELEASE.jar], onde <m2-repo> é o repositório Maven local;

A execução produz o seguinte resultado:

Em [3], podemos ver que as tabelas foram geradas. Agora, vamos verificar o DDL (Domain Definition Language) da base de dados:

O script SQL para gerar as tabelas também pode ser guardado num ficheiro [1].

 

O script SQL gerado é o seguinte:


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

Vejamos, por exemplo, o script SQL que gera a tabela [PRODUCTS] (linhas 15–29):

  • linha 16: [ID] é a chave primária (linha 23) com o atributo [AUTO_INCREMENT] (linha 5). Isto corresponde às anotações [@Id, @GeneratedValue(strategy = GenerationType.IDENTITY), @Column(name = ConfigJdbc.TAB_JPA_ID)] para o campo [id] da entidade JPA;
  • linha 17: a definição da coluna [DESCRIPTION] corresponde à anotação [@Column(name = ConfigJdbc.TAB_PRODUITS_DESCRIPTION, length = 100)] para o campo [description] da entidade JPA;
  • linha 18: a coluna [CATEGORIE_ID] é uma chave estrangeira da tabela [PRODUITS] na coluna [CATEGORIES.ID] (linha 26). Além disso, esta chave estrangeira possui o atributo [ON DELETE CASCADE]. Isto corresponde às anotações [@ManyToOne(fetch = FetchType.LAZY), @JoinColumn(name = ConfigJdbc.TAB_PRODUITS_CATEGORIE_ID)] para o campo [Produit.categorie] e à anotação [@OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL }), @CascadeOnDelete] do campo [Categorie.produits];
  • linha 19: a definição da coluna [NAME] corresponde à anotação [@Column(name = ConfigJdbc.TAB_PRODUITS_NAME, unique = true, length = 30, nullable = false)] do campo [Product.name];
  • linha 20: a definição da coluna [PRICE] corresponde à anotação [@Column(name = ConfigJdbc.TAB_PRODUITS_PRIX, nullable = false)] do campo [Product.price];
  • linhas 24-25: o script cria três índices para cada uma das colunas únicas da tabela;

As tabelas geradas não têm um valor padrão para o campo VERSIONING, enquanto o código Java espera que haja um. Se este valor padrão estiver em falta, certos testes irão falhar. Adicionamos este atributo da seguinte forma:

 
 
 

Isto é feito para as cinco tabelas que possuem a coluna [VERSIONING]. O valor predefinido não importa; basta que exista. Em seguida, é incrementado em 1 cada vez que a linha a que pertence é modificada.

Depois de feito isto, verifique se as seguintes configurações de teste são bem-sucedidas:

  • [spring-jdbc-generic-04.JUnitTestDao], que testa a implementação JDBC;
  • [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink], que testa as implementações JPA do Hibernate ou do EclipseLink (neste caso, será o EclipseLink)

Ambos os testes devem ser bem-sucedidos.

9.2.6. Gerar tabelas com o Hibernate

Criamos as tabelas do Hibernate utilizando o seguinte ambiente Eclipse:

 

A geração das tabelas é realizada pela configuração de execução denominada [generic-create-dbproduitscategories-hibernate] sem um agente Java;

O script SQL para a base de dados gerado pelo Hibernate é o seguinte:


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

As tabelas geradas são as mesmas, uma vez que o Hibernate também utilizou anotações JPA. No caso do Hibernate, não consegui encontrar o equivalente à anotação do EclipseLink [@OnCascadeDelete], que gerou o atributo SQL [ON DELETE CASCADE] na chave estrangeira [PRODUCTS.CATEGORY_ID] (linha 25). Este atributo deve, portanto, ser gerado manualmente, uma vez que é necessário para os testes:

 

O mesmo deve ser feito para as duas chaves estrangeiras na tabela [USERS_ROLES]:

Por fim, tal como foi feito na implementação do EclipseLink, as colunas [VERSIONING] nas cinco tabelas devem ter um valor por defeito:

 

Depois de fazer isso, verifique se as seguintes configurações de teste são bem-sucedidas:

  • [spring-jdbc-generic-04.JUnitTestDao], que testa a implementação JDBC;
  • [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink], que testa as implementações JPA do Hibernate ou do Eclipselink (neste caso, será o Hibernate)

Ambos os testes devem ser bem-sucedidos.

9.2.7. Gerar tabelas com o OpenJpa

Repetimos o procedimento anterior com uma implementação JPA do OpenJpa:

 

Nota: Prima Alt-F5 e regenere todos os projetos Maven;

Modificamos a classe [ConfigJpa] que configura o projeto [mysql-config-jpa-openjpa] da seguinte forma:


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();
    }
}
  • Linhas 40-41: Criamos uma propriedade para o OpenJPA que especifica como gerar chaves estrangeiras ao criar tabelas. Sem esta propriedade, as chaves estrangeiras não são geradas. O atributo [ForeignKeyDeleteAction=cascade] permite que a cláusula [ON DELETE CASCADE] seja gerada nessas chaves estrangeiras;

A geração de tabelas é realizada pela configuração de tempo de execução denominada [generic-create-dbproduitscategories-openjpa], que possui dois agentes Java;

Image

  • O primeiro agente Java é o agente Spring já utilizado com o EclipseLink;
  • O segundo agente Java é fornecido pelo OpenJpa;

O script SQL para a base de dados gerada é então o seguinte:


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

Isto é igual ao EclipseLink. Por isso, faremos as mesmas correções nas tabelas. Depois de fazer isso, verifique se os seguintes casos de teste são aprovados:

  • [spring-jdbc-generic-04.JUnitTestDao], que testa a implementação JDBC;
  • [spring-jpa-generic-JUnitTestDao-openjpa], que testa uma implementação JPA do OpenJPA;

Ambas as execuções devem ser bem-sucedidas.

9.3. Gerar a base de dados [dbproduits]

A base de dados [dbproduits] é utilizada pelos projetos [spring-jdbc-01 a 03]. Também pode ser gerada a partir de uma entidade JPA.

  • Em [1], os projetos Eclipse. Iremos utilizar uma configuração MySQL/EclipseLink. O projeto para gerar a base de dados [dbproduits] é [generic-create-dbproduits];
  • em [2], a tabela [PRODUITS] a ser gerada;

Nota: Prima Alt-F5 e gere novamente todos os projetos Maven;

9.3.1. Configuração do Maven

A configuração do Maven para o projeto [generic-create-dbproduits] é a seguinte:


<?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>

Existe apenas uma dependência, nas linhas 22–26, no projeto que configura a camada JPA. Em última análise, as dependências são as seguintes:

  

9.3.2. A configuração do Spring

  

A classe de configuração do Spring é a seguinte:


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();
    }
 
}
  • linha 18: importamos os beans da classe [ConfigJpa] (secção 7.3);
  • linhas 22–35: redefinimos a fonte de dados [dataSource]. Em [ConfigJpa], a fonte de dados é a base de dados [dbproduitscategories]. Aqui, será a base de dados [dbproduits];
  • linhas 38–46: redefinimos o bean [entityManagerFactory] da classe [ConfigJpa]. Nessa classe, as entidades JPA eram [Product, Category]. Aqui, é apenas [Product], e não tem a mesma definição que no projeto que configura a camada JPA;
  • linha 42: para definir esta nova entidade JPA, fazemos referência às entidades JPA definidas no ficheiro [META-INF/persistence.xml]:
  

O ficheiro [persistence.xml] é o seguinte:


<?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>
  • linha 6: a única entidade JPA;
  • linha 4: o nome da unidade de persistência [generic-jpa-entities-dbproduits] referenciada no bean [entityManagerFactory];

9.3.3. A entidade JPA [Product]

  

A entidade JPA está definida no projeto [mysql-config-jpa-eclipselink] da seguinte forma:


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

Esta é uma definição JPA que se tornou padrão. Observe os seguintes pontos:

  • linha 12: atribuímos um nome à entidade [Product1]. Por predefinição, o nome de uma entidade é o nome da classe, neste caso [Product]. No entanto, como existe outra entidade JPA [Product] no mesmo projeto, foi apresentado um erro antes mesmo de a execução ter começado. Resolvemos o problema da seguinte forma;
  • linha 24: a categoria aqui é simplesmente um número;
  • não existem relações entre entidades. Temos, portanto, uma situação muito simples;

9.3.4. A classe executável

  

A classe [CreateDatabase] é a seguinte:


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é...");
    }
 
}

Este é um código que já vimos anteriormente.

A tabela [PRODUCTS] é criada com a seguinte configuração de tempo de execução:

Os registos da consola são os seguintes:

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

Agora, voltemos ao cliente [MyManager] e atualizemos a visualização [1-2]:

Em [3], podemos ver que foi criada uma tabela. Agora, vamos verificar o DDL (Linguagem de Definição de Domínio) da base de dados:


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

De facto, obtemos a tabela esperada. Para verificar isto, vamos executar a seguinte configuração:

Deve ser bem-sucedido.

9.3.6. Geração do Hibernate

 

Nota: Prima Alt-F5 e gere novamente todos os projetos Maven;

A configuração de tempo de execução é a seguinte:

O script SQL gerado pelo Hibernate é o seguinte:


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. Geração OpenJpa

 

Nota: Prima Alt-F5 e gere novamente todos os projetos Maven;

A configuração de tempo de execução é a seguinte:

O script SQL gerado pelo OpenJpa é o seguinte:


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