Skip to content

9. Generieren von Datenbanken aus JPA-Entitäten

Es ist möglich, Datenbanktabellen aus JPA-Entitäten zu erstellen. Dies werden wir nun demonstrieren. Der Zweck besteht darin, zu überprüfen, ob die aus den JPA-Entitäten generierte Datenbank tatsächlich die gewünschte ist.

9.1. Einrichten der Arbeitsumgebung

Wir werden zunächst mit einer EclipseLink-JPA-Implementierung [1] arbeiten.

Anschließend löschen wir die Tabellen aus der MySQL-Datenbank [dbproduitscategories] mithilfe des Clients [MyManager] (siehe Abschnitt 23.5). Wir beginnen mit dem Löschen der Tabellen, die die Fremdschlüssel enthalten [1-3]:

Anschließend wiederholen wir den Vorgang mit den drei verbleibenden Tabellen [4-6]:

Das Gleiche tun wir mit der Tabelle [dbproduits], die von den Projekten [spring-jdbc-01 bis 03] verwendet wird:

Außerdem müssen Sie die beiden Projekte zur Datenbankgenerierung importieren:

  • Importieren Sie in [1] das Projekt [generic-create-dbproduits], das sich in [<examples>/spring-database-generic/spring-jpa] [2] befindet;
  • Importieren Sie in [4] das Projekt [generic-create-dbproduitscategories], das sich in [<examples>/spring-database-generic/spring-jpa] [5] befindet;

Hinweis: Drücken Sie Alt-F5 und generieren Sie alle Maven-Projekte neu;

9.2. Erstellen der Datenbank [dbproduitscategories]

 

9.2.1. Maven-Konfiguration

Die [pom.xml]-Datei des Projekts sieht wie folgt aus:


<?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>
  • Zeilen 22–26: die Abhängigkeit vom Projekt [spring-jpa-generic], das in Abschnitt 6.4 behandelt wurde;
  • Zeilen 28–32: die Abhängigkeit von einem Weaver, der zur Anreicherung der JPA-Entitäten der EclipseLink- und OpenJpa-Implementierungen verwendet wird. Diese Abhängigkeit ist in der [pom.xml]-Datei nicht erforderlich, aber die zugehörige JAR-Datei wird als Java-Agent verwendet. Durch die Aufnahme der Abhängigkeit in die [pom.xml]-Datei wird sichergestellt, dass die JAR-Datei verfügbar ist;

Letztendlich lauten die Abhängigkeiten wie folgt:

  

9.2.2. Spring-Konfiguration

  

Die Klasse [AppConfig] konfiguriert das Spring-Projekt:


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 {
 
}
  • Zeile 10: Die Klasse ruft die Beans aus der Klasse [ConfigJpa] ab. Beachten Sie, dass diese Klasse mit den JPA-Entitäten in der Datenbank [dbproduitscategories] arbeitet (siehe Abschnitt 6.3);
  • Zeile 11: Wir legen fest, dass das [console]-Paket durchsucht werden muss, um [CrudRepository]-Instanzen zu finden;

In der Klasse [ConfigJpa] finden wir die folgende Bean (variiert je nach verwendeter JPA-Implementierung):


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

Zeile 8 ist hier entscheidend. Sie ist in allen verwendeten JPA-Implementierungen vorhanden. Sie legt fest, dass die mit den JPA-Entitäten verbundenen Tabellen erstellt werden müssen, falls sie nicht existieren. Wir werden diese Eigenschaft nutzen, um die Tabellen zu generieren.

9.2.3. Die Repositorys

  

Die Schnittstelle [ProductsRepository] sieht wie folgt aus:


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

Die Instanziierung dieser Schnittstelle löst die Instanziierung der JPA-Schicht aus. Tatsächlich verweist die Schnittstelle in Zeile 7 auf die JPA-Entität [Product], was die Instanziierung der JPA-Schicht erzwingt. Wir hätten jede beliebige Schnittstelle [CrudRepository] verwenden können, die auf eine der JPA-Entitäten verweist. Wir sehen, dass zwar das [Repository] nur auf die JPA-Entität [Product] verweist, jedoch alle Tabellen für alle JPA-Entitäten generiert werden.

9.2.4. Die ausführbare Klasse

  

Die Klasse [CreateDatabase] sieht wie folgt aus:


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é...");
    }
 
}
  • Zeile 11: Wir instanziieren den Spring-Kontext und schließen ihn sofort wieder. In diesem Kontext befindet sich die Bean [ProductsRepository], die auf die JPA-Entität [Product] verweist. Dies reicht aus, um die JPA-Schicht zu instanziieren und somit die Tabellen in der Datenbank [productcategories] zu generieren.

Wir befinden uns in der folgenden Konfiguration:

 
  • Die [JDBC]-Schicht ist für die MySQL-Datenbank [dbproduitscategories] konfiguriert;
  • die [JPA]-Schicht ist mit EclipseLink implementiert;
  • die Datenbank [dbproduitscategories] enthält keine Tabellen;

Hinweis: Drücken Sie Alt-F5 und generieren Sie alle Maven-Projekte neu;

Wir verwenden die folgende Ausführungskonfiguration:

  • In [1-2] erfordert diese Ausführungskonfiguration einen Java-Agenten, damit der Test erfolgreich ist. Je nach Situation benötigt EclipseLink diesen Agenten nicht immer, aber hier schlägt die Ausführung fehl, wenn er nicht vorhanden ist. Dieser Agent ist kein EclipseLink-Agent, sondern ein Spring-Agent. Er wird durch die Abhängigkeit bereitgestellt:

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

in der [pom.xml]-Datei des Projekts enthalten. Der Agent befindet sich unter [<m2-repo>/org/springframework/spring-instrument/4.1.6.RELEASE/spring-instrument-4.1.6.RELEASE.jar], wobei <m2-repo> das lokale Maven-Repository ist;

Die Ausführung liefert das folgende Ergebnis:

In [3] sehen wir, dass die Tabellen generiert wurden. Sehen wir uns nun die DDL (Domain Definition Language) der Datenbank an:

Das SQL-Skript zum Erstellen der Tabellen kann auch in einer Datei gespeichert werden [1].

 

Das generierte SQL-Skript lautet wie folgt:


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

Sehen wir uns zum Beispiel das SQL-Skript an, das die Tabelle [PRODUCTS] generiert (Zeilen 15–29):

  • Zeile 16: [ID] ist der Primärschlüssel (Zeile 23) mit dem Attribut [AUTO_INCREMENT] (Zeile 5). Dies entspricht den Annotationen [@Id, @GeneratedValue(strategy = GenerationType.IDENTITY), @Column(name = ConfigJdbc.TAB_JPA_ID)] für das Feld [id] der JPA-Entität;
  • Zeile 17: Die Definition der Spalte [DESCRIPTION] entspricht der Annotation [@Column(name = ConfigJdbc.TAB_PRODUITS_DESCRIPTION, length = 100)] für das Feld [description] der JPA-Entität;
  • Zeile 18: Die Spalte [CATEGORIE_ID] ist ein Fremdschlüssel aus der Tabelle [PRODUITS] auf die Spalte [CATEGORIES.ID] (Zeile 26). Außerdem verfügt dieser Fremdschlüssel über das Attribut [ON DELETE CASCADE]. Dies entspricht den Annotationen [@ManyToOne(fetch = FetchType.LAZY), @JoinColumn(name = ConfigJdbc.TAB_PRODUITS_CATEGORIE_ID)] für das Feld [Produit.categorie] sowie der Annotation [@OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL }), @CascadeOnDelete] des Feldes [Categorie.produits];
  • Zeile 19: Die Definition der Spalte [NAME] entspricht der Annotation [@Column(name = ConfigJdbc.TAB_PRODUITS_NAME, unique = true, length = 30, nullable = false)] des Feldes [Product.name];
  • Zeile 20: Die Definition der Spalte [PRICE] entspricht der Annotation [@Column(name = ConfigJdbc.TAB_PRODUITS_PRIX, nullable = false)] des Feldes [Product.price];
  • Zeilen 24–25: Das Skript erstellt drei Indizes für jede der eindeutigen Spalten der Tabelle;

Die generierten Tabellen haben keinen Standardwert für das Feld VERSIONING, während der Java-Code erwartet, dass einer vorhanden ist. Fehlt dieser Standardwert, schlagen bestimmte Tests fehl. Wir fügen dieses Attribut wie folgt hinzu:

 
 
 

Dies geschieht für die fünf Tabellen, die die Spalte [VERSIONING] enthalten. Der Standardwert spielt keine Rolle; die Spalte muss lediglich vorhanden sein. Anschließend wird der Wert jedes Mal um 1 erhöht, wenn die Zeile, zu der er gehört, geändert wird.

Sobald dies erledigt ist, überprüfen Sie, ob die folgenden Testkonfigurationen erfolgreich sind:

  • [spring-jdbc-generic-04.JUnitTestDao], der die JDBC-Implementierung testet;
  • [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink], der die Hibernate- oder EclipseLink-JPA-Implementierungen testet (in diesem Fall ist es EclipseLink)

Beide Tests müssen erfolgreich sein.

9.2.6. Tabellen mit Hibernate generieren

Wir erstellen die Hibernate-Tabellen unter Verwendung der folgenden Eclipse-Umgebung:

 

Die Tabellengenerierung erfolgt über die Laufkonfiguration mit dem Namen [generic-create-dbproduitscategories-hibernate] ohne Java-Agent;

Das von Hibernate generierte SQL-Skript für die Datenbank lautet wie folgt:


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

Die generierten Tabellen sind identisch, da Hibernate ebenfalls JPA-Annotationen verwendet hat. Für Hibernate konnte ich kein Äquivalent zur EclipseLink-Annotation [@OnCascadeDelete] finden, die das SQL-Attribut [ON DELETE CASCADE] für den Fremdschlüssel [PRODUCTS.CATEGORY_ID] (Zeile 25) generiert hat. Dieses Attribut muss daher manuell generiert werden, da es für den Test erforderlich ist:

 

Das Gleiche muss für die beiden Fremdschlüssel in der Tabelle [USERS_ROLES] getan werden:

Schließlich müssen, wie bei der EclipseLink-Implementierung, die [VERSIONING]-Spalten in den fünf Tabellen einen Standardwert haben:

 

Sobald dies erledigt ist, überprüfen Sie, ob die folgenden Testkonfigurationen erfolgreich sind:

  • [spring-jdbc-generic-04.JUnitTestDao], der die JDBC-Implementierung testet;
  • [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink], der die Hibernate- oder Eclipselink-JPA-Implementierungen testet (in diesem Fall ist es Hibernate)

Beide Tests müssen erfolgreich sein.

9.2.7. Tabellen mit OpenJpa generieren

Wir wiederholen den vorherigen Vorgang mit einer OpenJpa-JPA-Implementierung:

 

Hinweis: Drücken Sie Alt-F5 und generieren Sie alle Maven-Projekte neu;

Wir ändern die Klasse [ConfigJpa], die das Projekt [mysql-config-jpa-openjpa] konfiguriert, wie folgt:


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();
    }
}
  • Zeilen 40–41: Wir erstellen eine Eigenschaft für OpenJPA, die festlegt, wie Fremdschlüssel beim Generieren von Tabellen erzeugt werden sollen. Ohne diese Eigenschaft werden keine Fremdschlüssel generiert. Das Attribut [ForeignKeyDeleteAction=cascade] ermöglicht die Generierung der Klausel [ON DELETE CASCADE] für diese Fremdschlüssel;

Die Tabellengenerierung erfolgt durch die Laufzeitkonfiguration mit dem Namen [generic-create-dbproduitscategories-openjpa], die zwei Java-Agenten enthält;

Image

  • Der erste Java-Agent ist der Spring-Agent, der bereits mit EclipseLink verwendet wird;
  • Der zweite Java-Agent wird von OpenJpa bereitgestellt;

Das SQL-Skript für die generierte Datenbank lautet dann wie folgt:


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

Dies entspricht der Vorgehensweise bei EclipseLink. Wir werden daher die gleichen Korrekturen an den Tabellen vornehmen. Sobald dies erledigt ist, überprüfen Sie, ob die folgenden Testfälle erfolgreich sind:

  • [spring-jdbc-generic-04.JUnitTestDao], der die JDBC-Implementierung testet;
  • [spring-jpa-generic-JUnitTestDao-openjpa], der eine OpenJPA-JPA-Implementierung testet;

Beide Durchläufe müssen erfolgreich sein.

9.3. Erstellen der Datenbank [dbproduits]

Die Datenbank [dbproduits] wird von den Projekten [spring-jdbc-01 bis 03] verwendet. Sie kann auch aus einer JPA-Entität generiert werden.

  • In [1] die Eclipse-Projekte. Wir verwenden eine MySQL/EclipseLink-Konfiguration. Das Projekt zum Erstellen der Datenbank [dbproduits] ist [generic-create-dbproduits];
  • in [2] die zu generierende Tabelle [PRODUITS];

Hinweis: Drücken Sie Alt-F5 und generieren Sie alle Maven-Projekte neu;

9.3.1. Maven-Konfiguration

Die Maven-Konfiguration für das Projekt [generic-create-dbproduits] lautet wie folgt:


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

Es gibt nur eine Abhängigkeit in Zeilen 22–26 des Projekts, die die JPA-Schicht konfiguriert. Letztendlich lauten die Abhängigkeiten wie folgt:

  

9.3.2. Die Spring-Konfiguration

  

Die Spring-Konfigurationsklasse sieht wie folgt aus:


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();
    }
 
}
  • Zeile 18: Wir importieren die Beans aus der Klasse [ConfigJpa] (Abschnitt 7.3);
  • Zeilen 22–35: Wir definieren die Datenquelle [dataSource] neu. In [ConfigJpa] ist die Datenquelle die Datenbank [dbproduitscategories]. Hier ist es die Datenbank [dbproduits];
  • Zeilen 38–46: Wir definieren die [entityManagerFactory]-Bean aus der Klasse [ConfigJpa] neu. In dieser Klasse waren die JPA-Entitäten [Product, Category]. Hier ist es nur [Product], und es hat nicht dieselbe Definition wie in dem Projekt, das die JPA-Schicht konfiguriert;
  • Zeile 42: Um diese neue JPA-Entität zu definieren, verweisen wir auf die in der Datei [META-INF/persistence.xml] definierten JPA-Entitäten:
  

Die Datei [persistence.xml] sieht wie folgt aus:


<?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>
  • Zeile 6: die einzelne JPA-Entität;
  • Zeile 4: der Name der Persistenz-Einheit [generic-jpa-entities-dbproduits], auf die in der [entityManagerFactory]-Bean verwiesen wird;

9.3.3. Die JPA-Entität [Product]

  

Die JPA-Entität ist im Projekt [mysql-config-jpa-eclipselink] wie folgt definiert:


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

Dies ist eine JPA-Definition, die mittlerweile zum Standard geworden ist. Beachten Sie folgende Punkte:

  • Zeile 12: Wir haben der Entität einen Namen gegeben [Product1]. Standardmäßig entspricht der Name einer Entität dem Klassennamen, in diesem Fall [Product]. Da es jedoch im selben Projekt eine weitere JPA-Entität namens [Product] gibt, wurde bereits vor Beginn der Ausführung ein Fehler gemeldet. Wir haben das Problem folgendermaßen gelöst:
  • Zeile 24: Die Kategorie ist hier lediglich eine Zahl;
  • es gibt keine Beziehungen zwischen Entitäten. Wir haben es also mit einer sehr einfachen Situation zu tun;

9.3.4. Die ausführbare Klasse

  

Die Klasse [CreateDatabase] sieht wie folgt aus:


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

Das ist Code, den wir schon kennen.

Die Tabelle [PRODUCTS] wird mit der folgenden Laufzeitkonfiguration erstellt:

Die Konsolenprotokolle lauten wie folgt:

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

Kehren wir nun zum [MyManager]-Client zurück und aktualisieren die Anzeige [1-2]:

In [3] sehen wir, dass eine Tabelle erstellt wurde. Sehen wir uns nun die DDL (Domain Definition Language) der Datenbank an:


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

Wir erhalten tatsächlich die erwartete Tabelle. Um dies zu überprüfen, führen wir die folgende Konfiguration aus:

Das sollte funktionieren.

9.3.6. Hibernate-Generierung

 

Hinweis: Drücken Sie Alt-F5 und generieren Sie alle Maven-Projekte neu;

Die Laufzeitkonfiguration lautet wie folgt:

Das von Hibernate generierte SQL-Skript lautet wie folgt:


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

9.3.7. OpenJpa-Generierung

 

Hinweis: Drücken Sie Alt-F5 und generieren Sie alle Maven-Projekte neu;

Die Laufzeitkonfiguration lautet wie folgt:

Das von OpenJpa generierte SQL-Skript lautet wie folgt:


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