Skip to content

9. إنشاء قواعد البيانات من الكيانات JPA

من الممكن إنشاء جداول قاعدة البيانات من الكيانات JPA. وهذا ما سنوضحه الآن. والهدف من ذلك هو التحقق من أن قاعدة البيانات التي تم إنشاؤها من الكيانات JPA هي بالفعل القاعدة المطلوبة.

9.1. إعداد بيئة العمل

سنعمل أولاً مع تطبيق JPA EclipseLink [1].

ثم نقوم بحذف الجداول من قاعدة البيانات MySQL و [dbproduitscategories] باستخدام العميل [MyManager] (انظر الفقرة 23.5). نبدأ بحذف الجداول التي تحتوي على المفاتيح الخارجية [1-3]:

ثم نبدأ من جديد مع الجداول الثلاثة المتبقية [4-6]:

ونفعل الشيء نفسه مع الجدول [dbproduits] الذي تستخدمه المشاريع [spring-jdbc-01 à 03]:

بالإضافة إلى ذلك، يجب استيراد مشروعي إنشاء قاعدتي البيانات:

  • في [1]، يتم استيراد المشروع [generic-create-dbproduits] الموجود في [<exemples>/spring-database-generic/spring-jpa] و[2]؛
  • في [4]، يتم استيراد المشروع [generic-create-dbproduitscategories] الموجود في [<exemples>/spring-database-generic/spring-jpa] و [5]؛

ملاحظة: اضغط على Alt-F5 وأعد إنشاء جميع مشاريع Maven؛

9.2. إنشاء قاعدة البيانات [dbproduitscategories]

 

9.2.1. تكوين Maven

الملف [pom.xml] الخاص بالمشروع هو كما يلي:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>dvp.spring.database</groupId>
    <artifactId>generic-create-dbproduitscategories</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>generic-create-dbproduitscategories</name>
    <description>création de la bases de données [dbproduitscategories] à l'aide des annotations JPA</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- spring-jpa-generic -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-jpa-generic</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- Weaver Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-instrument</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>spring.data.console.Main</start-class>
        <java.version>1.7</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • الأسطر 22-26: التبعية لمشروع [spring-jpa-generic] الذي تمت دراسته في الفقرة 6.4؛
  • الأسطر 28-32: التبعية لمُدمج سيُستخدم لإثراء كيانات JPA الخاصة بالتنفيذات EclipseLink و OpenJpa. هذه التبعية غير ضرورية في الملف [pom.xml]، لكن ملف jar الخاص به سيكون وكيل Java المستخدم. وضع التبعية في الملف [pom.xml] يضمن لنا أن ملف jar سيكون متاحًا بالفعل؛

وفي النهاية، تكون التبعيات كما يلي:

  

9.2.2. تكوين Spring

  

تقوم الفئة [AppConfig] بتكوين مشروع Spring:


package console;

import generic.jpa.config.ConfigJpa;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration
@Import({ ConfigJpa.class })
@EnableJpaRepositories(basePackages = { "console" })
public class AppConfig {

}
  • السطر 10: تسترد الفئة حبات الفئة [ConfigJpa]. تجدر الإشارة إلى أن هذه الفئة تعمل مع كيانات JPA الموجودة في قاعدة البيانات [dbproduitscategories] (انظر الفقرة 6.3
  • السطر 11: يُعلن أن الحزمة [console] يجب فحصها للعثور على مثيلات [CrudRepository

في الفئة [ConfigJpa]، نجد العنصر التالي (يختلف حسب التنفيذ JPA المستخدم):


    // المزود JPA
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        // ملاحظة: الكيانات JPA وتكوين Eclipselink موجودان في الملف META-INF/persistence.xml
        EclipseLinkJpaVendorAdapter eclipseLinkJpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
        eclipseLinkJpaVendorAdapter.setShowSql(false);
        eclipseLinkJpaVendorAdapter.setDatabase(Database.MYSQL);
        eclipseLinkJpaVendorAdapter.setGenerateDdl(true);
        return eclipseLinkJpaVendorAdapter;
}

السطر 8 هو المهم هنا. وهو موجود في جميع تطبيقات JPA المستخدمة. ويشير إلى أنه في حالة عدم وجود الجداول المرتبطة بالكيانات JPA، فيجب إنشاؤها. سنعتمد على هذه الخاصية لإنشاء الجداول.

9.2.3. المستودعات

  

الواجهة [ProduitsRepository] هي كما يلي:


package console;

import generic.jpa.entities.dbproduitscategories.Produit;

import org.springframework.data.repository.CrudRepository;

public interface ProduitsRepository extends CrudRepository<Produit, Long> {

}

إن إنشاء مثيل هذه الكيان هو الذي سيؤدي إلى إنشاء مثيل الطبقة JPA. ففي السطر 7، تشير الواجهة إلى الكيان JPA [Produit]، مما سيؤدي إلى إنشاء مثيل للطبقة JPA. كان من الممكن وضع أي واجهة [CrudRepository] تشير إلى إحدى الكيانات JPA. ونلاحظ بالفعل أنه على الرغم من أن [repository] لا تشير إلا إلى الكيان JPA [Produit]، إلا أن جميع الجداول الخاصة بجميع الكيانات JPA هي التي يتم إنشاؤها بالفعل.

9.2.4. الفئة القابلة للتنفيذ

  

الفئة [CreateDatabase] هي كما يلي:


package console;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class CreateDataBase {

    public static void main(String[] args) {
        // يكفي إنشاء مثيل لسياق Spring لإنشاء جداول قاعدة البيانات [dbproduitscategories]
        // كما يجب وجود مستودع بيانات Spring واحد على الأقل، وإلا فلن يحدث شيء
        System.out.println("Travail en cours...");
        new AnnotationConfigApplicationContext(AppConfig.class).close();
        System.out.println("Travail terminé...");
    }

}
  • السطر 11: يتم إنشاء مثيل لسياق Spring لإغلاقه على الفور. في هذا السياق، يوجد المكون [ProduitsRepository] الذي يشير إلى الكيانين JPA و[Produit]. وهذا يكفي لإنشاء مثيل لطبقة JPA وبالتالي إنشاء جداول قاعدة البيانات [dbproduitscategories].

نحن في التكوين التالي:

 
  • تم تكوين الطبقة [JDBC] لقاعدة البيانات [dbproduitscategories] التابعة لـ MySQL؛
  • تم تنفيذ الطبقة [JPA] مع EclipseLink؛
  • قاعدة البيانات [dbproduitscategories] لا تحتوي على جداول؛

ملاحظة: اضغط على Alt-F5 وأعد إنشاء جميع مشاريع Maven؛

يتم استخدام التكوين التالي للتشغيل:

  • في [1-2]، تتطلب إعدادات التشغيل هذه وجود وكيل Java لكي ينجح الاختبار. حسب الحالة، قد لا يحتاج EclipseLink دائمًا إلى هذا الوكيل، ولكن في هذه الحالة يفشل التنفيذ إذا لم يكن موجودًا. هذا الوكيل ليس وكيل EclipseLink بل وكيل Spring. يتم توفيره من خلال التبعية:

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

المسجلة في ملف [pom.xml] الخاص بالمشروع. يتم العثور على الوكيل في [<m2-repo>/org/springframework/spring-instrument/4.1.6.RELEASE/spring-instrument-4.1.6.RELEASE.jar] حيث يمثل <m2-repo> مستودع Maven المحلي؛

يُنتج التنفيذ النتيجة التالية:

في ملف [3]، نرى أن الجداول قد تم إنشاؤها. والآن دعونا نتحقق من ملف DDL (لغة تعريف المجال) الخاص بقاعدة البيانات:

يمكن أيضًا حفظ البرنامج النصي SQL الخاص بإنشاء الجداول في ملف [1].

 

النص البرمجي SQL الذي تم إنشاؤه هو التالي:


SET FOREIGN_KEY_CHECKS=0;

USE `dbproduitscategories`;

CREATE TABLE `categories` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `NOM` (`NOM`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `produits` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `DESCRIPTION` VARCHAR(100) COLLATE utf8_general_ci DEFAULT NULL,
  `CATEGORIE_ID` BIGINT(20) NOT NULL,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PRIX` DOUBLE NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  `CATEGORIE` INTEGER(11) NOT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `NOM` (`NOM`) USING BTREE,
  KEY `FK_PRODUITS_CATEGORIE_ID` (`CATEGORIE_ID`) USING BTREE,
  CONSTRAINT `FK_PRODUITS_CATEGORIE_ID` FOREIGN KEY (`CATEGORIE_ID`) REFERENCES `categories` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `roles` (
...
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `users` (
 ...
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `users_roles` (
...
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

لنلقِ نظرة على سبيل المثال على البرنامج النصي SQL الذي يُنشئ الجدول [PRODUITS] (الأسطر 15-29):

  • السطر 16: [ID] هو المفتاح الأساسي (السطر 23) مع السمة [AUTO_INCREMENT] (السطر 5). وهذا يتوافق مع التعليقات التوضيحية [@Id, @GeneratedValue(strategy = GenerationType.IDENTITY), @Column(name = ConfigJdbc.TAB_JPA_ID)] للحقل [id] في الكيان JPA؛
  • السطر 17: يتطابق تعريف العمود [DESCRIPTION] مع التعليق التوضيحي [@Column(name = ConfigJdbc.TAB_PRODUITS_DESCRIPTION, length = 100)] الخاص بالحقل [description] في الكيان JPA؛
  • السطر 18: العمود [CATEGORIE_ID] هو مفتاح خارجي للجدول [PRODUITS] على العمود [CATEGORIES.ID] (السطر 26). كما أن هذا المفتاح الأجنبي له السمة [ON DELETE CASCADE]. وهذا يتوافق مع التعليقات التوضيحية [@ManyToOne(fetch = FetchType.LAZY), @JoinColumn(name = ConfigJdbc.TAB_PRODUITS_CATEGORIE_ID)] للحقل [Produit.categorie] والتعليق التوضيحي [@OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL }), @CascadeOnDelete] للحقل [Categorie.produits
  • السطر 19: يتطابق تعريف العمود [NOM] مع التعليق التوضيحي [@Column(name = ConfigJdbc.TAB_PRODUITS_NOM, unique = true, length = 30, nullable = false)] الخاص بالحقل [Produit.nom
  • السطر 20: تعريف العمود [PRIX] يتطابق مع التعليق التوضيحي [@Column(name = ConfigJdbc.TAB_PRODUITS_PRIX, nullable = false)] الخاص بالحقل [Produit.prix
  • السطران 24-25: يقوم البرنامج النصي بإنشاء ثلاثة فهارس لكل عمود فريد في الجدول؛

لا تحتوي الجداول التي تم إنشاؤها على قيمة افتراضية للحقل VERSIONING في الجداول، في حين أن كود Java يتوقع وجودها. إذا لم تكن هذه القيمة الافتراضية موجودة، فلن يتم اجتياز بعض الاختبارات. يتم إضافة هذا السمة بالطريقة التالية:

 
 
 

يتم ذلك بالنسبة للجداول الخمسة التي تحتوي على العمود [VERSIONING]. لا يهم القيمة الافتراضية. المهم فقط أن تكون موجودة. بعد ذلك، يتم زيادة قيمتها بمقدار 1 عند كل تعديل للصف الذي تنتمي إليه.

بعد الانتهاء من ذلك، تحقق من نجاح تنفيذ التكوينات التالية:

  • [spring-jdbc-generic-04.JUnitTestDao] الذي يختبر التنفيذ JDBC؛
  • [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink] الذي يختبر عمليات التنفيذ JPA Hibernate أو Eclipselink (وهنا سيكون EclipseLink)

يجب أن تنجح كلتا العمليتين.

9.2.6. إنشاء الجداول باستخدام Hibernate

نقوم بإنشاء جداول Hibernate باستخدام بيئة Eclipse التالية:

 

يتم إنشاء الجداول من خلال إعداد التشغيل المسمى [generic-create-dbproduitscategories-hibernate] بدون وكيل Java؛

النص البرمجي SQL للقاعدة التي تم إنشاؤها بواسطة Hibernate هو التالي:


SET FOREIGN_KEY_CHECKS=0;

USE `dbproduitscategories`;

CREATE TABLE `categories` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `UK_7ajcg7japnxw846ru01damg8s` (`NOM`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `produits` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `DESCRIPTION` VARCHAR(100) COLLATE utf8_general_ci DEFAULT NULL,
  `CATEGORIE_ID` BIGINT(20) NOT NULL,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PRIX` DOUBLE NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `UK_hfvjn9lp7qoo5x79uu0ump3rf` (`NOM`) USING BTREE,
  KEY `FK_p3foj9yrqnmi7856n9s8mbpue` (`CATEGORIE_ID`) USING BTREE,
  CONSTRAINT `FK_p3foj9yrqnmi7856n9s8mbpue` FOREIGN KEY (`CATEGORIE_ID`) REFERENCES `categories` (`ID`)
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `roles` (
...
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `users` (
...
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `users_roles` (
 ...
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

الجداول التي تم إنشاؤها هي نفسها لأن Hibernate استخدم أيضًا التعليقات التوضيحية JPA. بالنسبة لـ Hibernate، لم أجد ما يعادل التعليق التوضيحي EclipseLink [@OnCascadeDelete] الذي أنشأالسمة SQL [ON DELTE CASCADE] على المفتاح الأجنبي [PRODUITS.CATEGORIE_ID] (السطر 25). لذا، يجب إنشاء هذه السمة يدويًا لأنها ضرورية للاختبارات:

 

ويجب فعل الشيء نفسه مع المفتاحين الأجنبيين في الجدول [USERS_ROLES]:

وأخيرًا، كما تم في حالة التنفيذ EclipseLink، يجب أن تحتوي الأعمدة [VERSIONING] في الجداول الخمسة على قيمة افتراضية:

 

وبعد القيام بذلك، تحقق من نجاح عمليات التنفيذ التالية:

  • [spring-jdbc-generic-04.JUnitTestDao] الذي يختبر التنفيذ JDBC؛
  • [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink] الذي يختبر عمليات التنفيذ JPA لـ Hibernate أو Eclipselink (في هذه الحالة سيكون Hibernate)

يجب أن تنجح كلتا العمليتين.

9.2.7. إنشاء الجداول باستخدام OpenJpa

نكرر الإجراء السابق باستخدام تطبيق JPA و OpenJpa:

 

ملاحظة: اضغط على Alt-F5 وأعد إنشاء جميع مشاريع Maven؛

نقوم بتعديل الفئة [ConfigJpa] التي تهيئ المشروع [mysql-config-jpa-openjpa] بالطريقة التالية:


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 {

    // المزود JPA
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        OpenJpaVendorAdapter openJpaVendorAdapter = new OpenJpaVendorAdapter();
        openJpaVendorAdapter.setShowSql(false);
        openJpaVendorAdapter.setDatabase(Database.MYSQL);
        openJpaVendorAdapter.setGenerateDdl(true);
        return openJpaVendorAdapter;
    }
..
    // EntityManagerFactory
    @Bean
    public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(jpaVendorAdapter);
        factory.setPackagesToScan(ENTITIES_PACKAGES);
        Map<String, Object> mapJpaProperties = factory.getJpaPropertyMap();
        mapJpaProperties.put("openjpa.jdbc.MappingDefaults",
                "ForeignKeyDeleteAction=cascade,JoinForeignKeyDeleteAction=restrict");
        factory.setDataSource(dataSource);
        factory.afterPropertiesSet();
        return factory.getObject();
    }
}
  • السطران 40-41: نقوم بإنشاء خاصية لـ OpenJPA تحدد طريقة إنشاء المفاتيح الخارجية عند إنشاء الجداول. بدون هذه الخاصية، لا يتم إنشاء المفاتيح الخارجية. تسمح السمة [ForeignKeyDeleteAction=cascade] بإنشاء السمة [ON DELETE CASCADE] على هذه المفاتيح الخارجية؛

يتم إنشاء الجداول من خلال تكوين التشغيل المسمى [generic-create-dbproduitscategories-openjpa] الذي يحتوي على وكيلين Java؛

Image

  • الوكيل الأول لـ Java هو وكيل Spring الذي سبق استخدامه مع EclipseLink؛
  • الوكيل الثاني لـ Java يتم توفيره بواسطة OpenJpa؛

وبالتالي، فإن البرنامج النصي SQL الخاص بالقاعدة التي تم إنشاؤها هو كما يلي:


SET FOREIGN_KEY_CHECKS=0;

USE `dbproduitscategories`;

CREATE TABLE `categories` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `U_CTGORIS_NOM` (`NOM`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `produits` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `DESCRIPTION` VARCHAR(100) COLLATE utf8_general_ci DEFAULT NULL,
  `CATEGORIE_ID` BIGINT(20) DEFAULT NULL,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PRIX` DOUBLE NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `U_PRODUTS_NOM` (`NOM`) USING BTREE,
  KEY `CATEGORIE_ID` (`CATEGORIE_ID`) USING BTREE,
  CONSTRAINT `produits_ibfk_1` FOREIGN KEY (`CATEGORIE_ID`) REFERENCES `categories` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `roles` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `NAME` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `U_ROLES_NAME` (`NAME`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `users` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `LOGIN` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `NAME` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PASSWORD` VARCHAR(60) COLLATE utf8_general_ci NOT NULL,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  UNIQUE KEY `U_USERS_LOGIN` (`LOGIN`) USING BTREE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

CREATE TABLE `users_roles` (
  `ID` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `VERSIONING` BIGINT(20) DEFAULT NULL,
  `ROLE_ID` BIGINT(20) NOT NULL,
  `USER_ID` BIGINT(20) NOT NULL,
  PRIMARY KEY (`ID`) USING BTREE,
  KEY `ROLE_ID` (`ROLE_ID`) USING BTREE,
  KEY `USER_ID` (`USER_ID`) USING BTREE,
  CONSTRAINT `users_roles_ibfk_2` FOREIGN KEY (`USER_ID`) REFERENCES `users` (`ID`) ON DELETE CASCADE,
  CONSTRAINT `users_roles_ibfk_1` FOREIGN KEY (`ROLE_ID`) REFERENCES `roles` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB
AUTO_INCREMENT=1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
;

وهو نفس البرنامج النصي المستخدم مع EclipseLink. لذا، سنقوم بإجراء نفس التصحيحات على الجداول. بعد الانتهاء من ذلك، تحقق من نجاح تنفيذ التكوينات التالية:

  • [spring-jdbc-generic-04.JUnitTestDao] الذي يختبر التنفيذ JDBC؛
  • [spring-jpa-generic-JUnitTestDao-openjpa] الذي يختبر تنفيذ JPA و OpenJpa؛

يجب أن تنجح كلتا العمليتين.

9.3. إنشاء قاعدة البيانات [dbproduits]

يتم استخدام قاعدة البيانات [dbproduits] من قبل المشاريع [spring-jdbc-01 à 03]. ويمكن أيضًا إنشاؤها من كيان JPA.

  • في [1]، مشاريع Eclipse. سنكون في تكوين MySQL / EclipseLink. مشروع إنشاء قاعدة البيانات [dbproduits] هو [generic-create-dbproduits
  • في [2]، الجدول [PRODUITS] المراد إنشاؤه؛

ملاحظة: اضغط على Alt-F5 وأعد إنشاء جميع مشاريع Maven؛

9.3.1. تكوين Maven

تكوين Maven لمشروع [generic-create-dbproduits] هو كما يلي:


<?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>
        <!-- تكوين JPA لـ SGBD -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>generic-config-jpa</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>spring.data.console.Main</start-class>
        <java.version>1.7</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>

لا يوجد سوى تابع واحد، في الأسطر 22-26، في المشروع الذي يقوم بتكوين الطبقة JPA. وفي النهاية، تكون التبعيات كما يلي:

  

9.3.2. تكوين Spring

  

فئة تكوين Spring هي كما يلي:


package console;

import generic.jdbc.config.ConfigJdbc;
import generic.jpa.config.ConfigJpa;

import javax.persistence.EntityManagerFactory;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;

@EnableJpaRepositories(basePackages = { "console" })
@Configuration
@Import({ ConfigJpa.class })
public class AppConfig {

    // مصدر البيانات
    @Bean
    public DataSource dataSource() {
        // مصدر البيانات TomcatJdbc
        DataSource dataSource = new DataSource();
        // تكوين الوصول JDBC
        dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
        dataSource.setUsername(ConfigJdbc.USER_DBPRODUITS);
        dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITS);
        dataSource.setUrl(ConfigJdbc.URL_DBPRODUITS);
        // الاتصالات المفتوحة في البداية
        dataSource.setInitialSize(5);
        // النتيجة
        return dataSource;
    }

    // EntityManagerFactory
    @Bean
    public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(jpaVendorAdapter);
        factory.setPersistenceUnitName("generic-jpa-entities-dbproduits");
        factory.setDataSource(dataSource);
        factory.afterPropertiesSet();
        return factory.getObject();
    }

}
  • السطر 18: يتم استيراد الفئات من الفئة [ConfigJpa] (الفقرة 7.3
  • الأسطر 22-35: يتم إعادة تعريف مصدر البيانات [dataSource]. في [ConfigJpa]، مصدر البيانات هو قاعدة البيانات [dbproduitscategories]. وهنا سيكون مصدر البيانات هو قاعدة البيانات [dbproduits
  • السطور 38-46: يتم إعادة تعريف الكائن [entityManagerFactory] التابع للفئة [ConfigJpa]. في هذه الفئة، كانت الكيانات JPA هي [Produit, Categorie]. هنا هي فقط [Produit] ولا تحمل نفس التعريف الموجود في المشروع الذي يهيئ الطبقة JPA؛
  • السطر 42: لتعريف هذه الكيان الجديد JPA، يتم الإشارة إلى الكيانات JPA المُعرَّفة في الملف [META-INF/persistence.xml]:
  

الملف [persistence.xml] هو كما يلي:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    <persistence-unit name="generic-jpa-entities-dbproduits" transaction-type="RESOURCE_LOCAL">
        <!-- الكيانات JPA -->
        <class>generic.jpa.entities.dbproduits.Produit</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
    </persistence-unit>
</persistence>
  • السطر 6: الكيان الوحيد JPA؛
  • السطر 4: اسم وحدة الاستمرارية [generic-jpa-entities-dbproduits] المشار إليها في الفول [entityManagerFactory

9.3.3. الكيان JPA [Produit]

  

يتم تعريف الكيان JPA في المشروع [mysql-config-jpa-eclipselink] على النحو التالي:


package generic.jpa.entities.dbproduits;

import generic.jdbc.config.ConfigJdbc;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity(name="Produit1")
@Table(name = ConfigJdbc.TAB_PRODUITS)
public class Produit {

    // الحقول
    @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;

    // المنشئات
    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;
    }

    // أدوات الاسترجاع والتعيين
    ...
}

هذا تعريف JPA أصبح الآن تقليديًا. تجدر الإشارة ببساطة إلى النقاط التالية:

  • السطر 12: أُطلق اسم [Produit1] على الكيان. بشكل افتراضي، يكون اسم الكيان هو اسم الفئة، وهو هنا [Produit]. ولكن نظرًا لوجود كيان آخر JPA [Produit] في نفس المشروع، تم الإبلاغ عن خطأ قبل حتى بدء التنفيذ. وقد تم إزالة هذا الخطأ بهذه الطريقة؛
  • السطر 24: الفئة هنا هي مجرد رقم؛
  • ولا توجد علاقات بين الكيانات. لذا فإننا أمام حالة بسيطة للغاية؛

9.3.4. الفئة القابلة للتنفيذ

  

الفئة [CreateDatabase] هي كما يلي:


package console;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class CreateDataBase {

    public static void main(String[] args) {
        // يكفي إنشاء مثيل لسياق Spring لإنشاء جداول قاعدة البيانات [dbproduits]
        // كما يلزم وجود مستودع بيانات Spring واحد على الأقل، وإلا فلن يحدث شيء
        System.out.println("Travail en cours...");
        new AnnotationConfigApplicationContext(AppConfig.class).close();
        System.out.println("Travail terminé...");
    }

}

هذا كود سبق أن صادفناه.

تم إنشاء الجدول [PRODUITS] بالتكوين التالي للتنفيذ:

سجلات وحدة التحكم هي كما يلي:

Travail en cours...
16:31:17.668 [main] INFO  o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6fadae5d: startup date [Tue Jun 02 16:31:17 CEST 2015]; root of context hierarchy
16:31:17.758 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'entityManagerFactory': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jpa.config.ConfigJpa; factoryMethodName=entityManagerFactory; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jpa.config.ConfigJpa] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=entityManagerFactory; initMethodName=null; destroyMethodName=(inferred); defined in console.AppConfig]
16:31:17.759 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'dataSource': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jpa.config.ConfigJpa; factoryMethodName=dataSource; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jpa.config.ConfigJpa] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=dataSource; initMethodName=null; destroyMethodName=(inferred); defined in console.AppConfig]
16:31:18.462 [main] INFO  o.s.o.j.LocalContainerEntityManagerFactoryBean - Building JPA container EntityManagerFactory for persistence unit 'generic-jpa-entities-dbproduits'
[EL Info]: server: 2015-06-02 16:31:18.624--ServerSession(817299424)--Detected server platform: org.eclipse.persistence.platform.server.NoServerPlatform.
[EL Info]: server: 2015-06-02 16:31:19.015--ServerSession(817299424)--Detected server platform: org.eclipse.persistence.platform.server.NoServerPlatform.
[EL Info]: 2015-06-02 16:31:19.021--ServerSession(817299424)--EclipseLink, version: Eclipse Persistence Services - 2.6.0.v20150309-bf26070
[EL Info]: connection: 2015-06-02 16:31:20.122--ServerSession(817299424)--/file:/D:/data/istia-1415/spring data/dvp/dvp-spring-database-04/spring-database-generic/spring-jpa/generic-create-dbproduits/target/classes_generic-jpa-entities-dbproduits login successful
16:31:20.654 [main] INFO  o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6fadae5d: startup date [Tue Jun 02 16:31:17 CEST 2015]; root of context hierarchy
[EL Info]: connection: 2015-06-02 16:31:20.656--ServerSession(817299424)--/file:/D:/data/istia-1415/spring data/dvp/dvp-spring-database-04/spring-database-generic/spring-jpa/generic-create-dbproduits/target/classes_generic-jpa-entities-dbproduits logout successful
[EL Warning]: 2015-06-02 16:31:20.657--session_manager_no_partition
Travail terminé...

الآن، لنعد إلى العميل [MyManager] ونقوم بتحديث العرض [1-2]:

في [3]، نرى أنه تم إنشاء جدول. والآن دعونا نتحقق من DDL (لغة تعريف المجال) لقاعدة البيانات:


SET FOREIGN_KEY_CHECKS=0;

USE `dbproduits`;

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

نحصل بالفعل على الجدول المتوقع. للتحقق من ذلك، سنقوم بتنفيذ التكوين التالي:

يجب أن تنجح هذه الإعدادات.

9.3.6. إنشاء Hibernate

 

ملاحظة: اضغط على Alt-F5 وأعد إنشاء جميع مشاريع Maven؛

إعدادات التشغيل هي كما يلي:

النص البرمجي SQL الذي تم إنشاؤه بواسطة Hibernate هو كما يلي:


USE `dbproduits`;

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

9.3.7. إنشاء OpenJpa

 

ملاحظة: اضغط على Alt-F5 وأعد إنشاء جميع مشاريع Maven؛

إعدادات التشغيل هي كما يلي:

النص البرمجي SQL الذي تم إنشاؤه بواسطة OpenJpa هو كما يلي:


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