Skip to content

9. Geração de bases de dados a partir das entidades JPA

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

9.1. Configuração do ambiente de trabalho

Vamos trabalhar, em primeiro lugar, com uma implementação JPA, EclipseLink e [1].

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

Em seguida, recomeçamos com as três tabelas restantes [4-6]:

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

Além disso, é necessário importar os dois projetos de geração das duas bases de dados:

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

Nota: premir Alt-F5 e regenerar todos os projetos Maven;

9.2. Geração da 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] analisado no parágrafo 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 é necessária no ficheiro [pom.xml], mas o seu JAR será o agente Java utilizado. Colocar a dependência no ficheiro [pom.xml] garante-nos que o JAR estará efetivamente 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]. Recorde-se que esta classe trabalha com as entidades JPA da base de dados [dbproduitscategories] (ver parágrafo 6.3);
  • linha 11: declara-se que o pacote [console] deve ser analisado para encontrar instâncias de [CrudRepository];

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


    // o provedor JPA
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        // Nota: as entidades JPA e a configuração do EclipseLink encontram-se no ficheiro META-INF/persistence.xml
        EclipseLinkJpaVendorAdapter eclipseLinkJpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
        eclipseLinkJpaVendorAdapter.setShowSql(false);
        eclipseLinkJpaVendorAdapter.setDatabase(Database.MYSQL);
        eclipseLinkJpaVendorAdapter.setGenerateDdl(true);
        return eclipseLinkJpaVendorAdapter;
}

A linha 8 é importante neste contexto. Está presente em todas as implementações JPA utilizadas. Indica que, se as tabelas associadas às entidades JPA não existirem, devem ser criadas. Vamos basear-nos nesta propriedade para gerar as tabelas.

9.2.3. Os repositórios

  

A interface [ProduitsRepository] é a seguinte:


package console;

import generic.jpa.entities.dbproduitscategories.Produit;

import org.springframework.data.repository.CrudRepository;

public interface ProduitsRepository extends CrudRepository<Produit, Long> {

}

É a sua instanciação que irá provocar a instanciação da camada JPA. Com efeito, na linha 7, a interface faz referência à entidade JPA [Produit], o que forçará a instanciação da camada JPA. Poderíamos ter colocado qualquer interface [CrudRepository] que fizesse referência a uma das entidades JPA. Verifica-se, de facto, que, embora o [repository] apenas faça referência à entidade JPA [Produit], são geradas todas as tabelas de todas as entidades JPA.

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) {
        // basta instanciar o contexto Spring para criar as tabelas da base de dados [dbproduitscategories]
        // é também necessário, pelo menos, um Spring Data Repository; caso contrário, nada acontece
        System.out.println("Travail en cours...");
        new AnnotationConfigApplicationContext(AppConfig.class).close();
        System.out.println("Travail terminé...");
    }

}
  • linha 11: instanciamos o contexto Spring para o encerrar imediatamente. Neste contexto, existe o bean [ProduitsRepository] que faz referência às entidades JPA e [Produit]. Isto é suficiente para instanciar a camada JPA e, consequentemente, gerar as tabelas da base de dados [dbproduitscategories].

Estamos na seguinte configuração:

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

Nota: prima Alt-F5 e regenera todos os projetos Maven;

Utiliza-se a seguinte configuração de execução:

  • no [1-2], esta configuração de execução requer um agente Java para que o teste seja bem-sucedido. Dependendo do caso, 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 EclipseLink, mas sim um agente Spring. É fornecido pela dependência:

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

registada 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 local do Maven;

A execução produz o seguinte resultado:

No ficheiro [3], verifica-se que as tabelas foram geradas. Agora, vamos verificar o ficheiro DDL (Domain Definition Language) da base de dados:

O script SQL de geração das 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 [PRODUITS] (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)] do campo [id] da entidade JPA;
  • linha 17: a definição da coluna [DESCRIPTION] corresponde à anotação [@Column(name = ConfigJdbc.TAB_PRODUITS_DESCRIPTION, length = 100)] do 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)] do 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 [NOM] corresponde à anotação [@Column(name = ConfigJdbc.TAB_PRODUITS_NOM, unique = true, length = 30, nullable = false)] do campo [Produit.nom];
  • linha 20: a definição da coluna [PRIX] corresponde à anotação [@Column(name = ConfigJdbc.TAB_PRODUITS_PRIX, nullable = false)] do campo [Produit.prix];
  • 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 por defeito para o campo VERSIONING das tabelas, enquanto o código Java espera que exista um. Se este valor por defeito não estiver presente, alguns testes não são aprovados. Adiciona-se este atributo da seguinte forma:

 
 
 

Faz-se isto para as cinco tabelas que têm a coluna [VERSIONING]. O valor por defeito não tem importância. Basta que exista. Em seguida, é incrementado em 1 a cada alteração da linha a que pertence.

Feito isto, verifique se as seguintes configurações de execução 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 Hibernate ou Eclipselink (neste caso, será EclipseLink)

Ambas as execuções têm de ser bem-sucedidas.

9.2.6. Geração das tabelas com o Hibernate

Criamos as tabelas do Hibernate com o seguinte ambiente Eclipse:

 

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

O script SQL da base de dados gerada 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 as anotações JPA. No caso do Hibernate, não encontrei o equivalente à anotação EclipseLink [@OnCascadeDelete] que gerou oatributo SQL [ON DELTE CASCADE] na chave estrangeira [PRODUITS.CATEGORIE_ID] (linha 25). Por isso, é necessário gerar este atributo manualmente, uma vez que é necessário para os testes:

 

É necessário fazer o mesmo com as duas chaves estrangeiras da tabela [USERS_ROLES]:

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

 

Feito isto, verifique se as seguintes configurações de execução 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)

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

9.2.7. Geração das tabelas com o OpenJpa

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

 

Nota: premir Alt-F5 e regenerar todos os projetos Maven;

Alteramos 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 {

    // o provedor 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 OpenJPA que indica a forma de gerar as chaves estrangeiras durante a geração das tabelas. Sem esta propriedade, as chaves estrangeiras não são geradas. O atributo [ForeignKeyDeleteAction=cascade] permite gerar o atributo [ON DELETE CASCADE] nessas chaves estrangeiras;

A geração das tabelas é efetuada pela configuração 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 por OpenJpa;

O script SQL da base 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'
;

É o mesmo que com o EclipseLink. Por isso, serão efetuadas as mesmas correções nas tabelas. Depois de fazer isso, verifique se as seguintes configurações de execução são bem-sucedidas:

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

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

9.3. Geração da base de dados [dbproduits]

A base [dbproduits] é utilizada pelos projetos [spring-jdbc-01 à 03]. Também é possível gerá-la a partir de uma entidade JPA.

  • em [1], os projetos Eclipse. Estaremos numa configuração MySQL / EclipseLink. O projeto de geração da base de dados [dbproduits] é o [generic-create-dbproduits];
  • no [2], a tabela [PRODUITS] a gerar;

Nota: premir Alt-F5 e regenerar todos os projetos Maven;

9.3.1. Configuração do Maven

A configuração Maven do 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>
        <!-- configuração JPA do 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. No final, 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 {

    // fonte de dados
    @Bean
    public DataSource dataSource() {
        // fonte de dados TomcatJdbc
        DataSource dataSource = new DataSource();
        // configuração de acesso JDBC
        dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
        dataSource.setUsername(ConfigJdbc.USER_DBPRODUITS);
        dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITS);
        dataSource.setUrl(ConfigJdbc.URL_DBPRODUITS);
        // ligações inicialmente abertas
        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();
    }

}
  • linha 18: importam-se os beans da classe [ConfigJpa] (parágrafo 7.3);
  • linhas 22-35: redefine-se a fonte de dados [dataSource]. Na classe [ConfigJpa], a fonte de dados é a base de dados [dbproduitscategories]. Aqui, será a base de dados [dbproduits];
  • linhas 38-46: redefine-se o bean [entityManagerFactory] da classe [ConfigJpa]. Nesta classe, as entidades JPA eram [Produit, Categorie]. Aqui, é apenas [Produit] e não tem a mesma definição que no projeto que configura a camada JPA;
  • linha 42: para definir esta nova entidade JPA, faz-se 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">
        <!-- entidades 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], que é referenciada no bean [entityManagerFactory];

9.3.3. A entidade JPA [Produit]

  

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 {

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

    // construtores
    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 e setters
    ...
}

Trata-se de uma definição JPA que já se tornou clássica. Basta referir os seguintes pontos:

  • linha 12: foi atribuído o nome [Produit1] à entidade. Por predefinição, o nome de uma entidade é o nome da classe, neste caso [Produit]. No entanto, como existe outra entidade JPA [Produit] no mesmo projeto, foi sinalizado um erro antes mesmo de qualquer execução. Resolvemos o problema desta forma;
  • linha 24: a categoria é, neste caso, um simples 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) {
        // basta instanciar o contexto Spring para criar as tabelas da base de dados [dbproduits]
        // é também necessário, pelo menos, um Spring Data Repository; caso contrário, nada acontece
        System.out.println("Travail en cours...");
        new AnnotationConfigApplicationContext(AppConfig.class).close();
        System.out.println("Travail terminé...");
    }

}

Este é um código com o qual já nos deparámos.

A tabela [PRODUITS] é criada com a seguinte configuração 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], vemos que foi gerada uma tabela. Agora, vamos verificar a DDL (Domain Definition Language) 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'
;

Obtenemos, de facto, a tabela esperada. Para verificar isso, executaremos a seguinte configuração:

A execução deve ser bem-sucedida.

9.3.6. Geração do Hibernate

 

Nota: prima Alt-F5 e regenera todos os projetos Maven;

A configuração 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 de OpenJpa

 

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

A configuração 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'
;