Skip to content

18. تطبيق ويب MVC في بنية ثلاثية الطبقات – المثال 4، Postgres

18.1. قاعدة بيانات Postgres

في هذا الإصدار، سنقوم بتخزين قائمة الأشخاص في جدول قاعدة بيانات Postgres 8.x [http://www.postgres.org]. وفيما يلي، تم التقاط لقطات الشاشة من عميل EMS PostgreSQL Manager Lite [http://www.sqlmanager.net/fr/products/postgresql/manager]، وهو عميل إدارة مجاني لنظام إدارة قواعد البيانات Postgres.

تسمى قاعدة البيانات [dbpersonnes]. وهي تحتوي على جدول باسم [PERSONNES]:

Image

سيحتوي جدول [PERSONNES] على قائمة الأشخاص الذين يديرهم تطبيق الويب. تم إنشاؤه باستخدام عبارات SQL التالية:

CREATE TABLE "public"."PERSONNES" (
  "ID" INTEGER NOT NULL, 
  "VERSION" INTEGER NOT NULL, 
  "NOM" VARCHAR(30) NOT NULL, 
  "PRENOM" VARCHAR(30) NOT NULL, 
  "DATENAISSANCE" DATE NOT NULL, 
  "MARIE" BOOLEAN NOT NULL, 
  "NBENFANTS" INTEGER NOT NULL, 
  CONSTRAINT "PERSONNES_pkey" PRIMARY KEY("ID"), 
  CONSTRAINT "PERSONNES_chk_NBENFANTS" CHECK ("NBENFANTS" >= 0), 
  CONSTRAINT "PERSONNES_chk_NOM" CHECK (("NOM")::text <> ''::text), 
  CONSTRAINT "PERSONNES_chk_PRENOM" CHECK (("PRENOM")::text <> ''::text)
) WITH OIDS;

لن نتطرق إلى هذه الجدولة، التي تشبه جدول Firebird [PERSONNES] الذي تمت مناقشته سابقًا. لاحظ، مع ذلك، أن أسماء الأعمدة والجداول محاطة بعلامات اقتباس. علاوة على ذلك، هذه الأسماء حساسة لحالة الأحرف. من الممكن أن يكون هذا السلوك في Postgres 8.x قابلاً للتكوين. لم أقم بالتحقيق في هذا الأمر بشكل أعمق.

قد تحتوي الجدول [PERSONNES] على المحتوى التالي:

Image

بالإضافة إلى جدول [PERSONNES]، تحتوي قاعدة البيانات [dbpersonnes] على كائن يُسمى تسلسل باسم [SEQ_ID]. يُنتج هذا المولد أعدادًا صحيحة متتالية سنستخدمها لملء المفتاح الأساسي [ID] لفئة [PERSONNES]. لنأخذ مثالاً لتوضيح كيفية عمله:

يمكننا ملاحظة أن [القيمة التالية] لتسلسل [SEQ_ID] قد تغيرت (انقر عليها مرتين + F5 للتحديث):

 

عبارة SQL

SELECT nextval('"SEQ_ID"')

يسمح لنا بالتالي باسترداد القيمة التالية في تسلسل [SEQ_ID]. سنستخدمه في ملف [people-postgres.xml]، الذي يحتوي على عبارات SQL التي يتم تنفيذها على نظام إدارة قواعد البيانات (DBMS).

18.2. مشروع Eclipse لطبقات [dao] و [service]

لتطوير طبقات [dao] و [service] لتطبيقنا باستخدام قاعدة بيانات Postgres 8.x، سنستخدم مشروع Eclipse التالي [spring-mvc-39]:

Image

المشروع هو مشروع Java بسيط، وليس مشروع ويب Tomcat.


مجلد [src]


يحتوي هذا المجلد على شفرة المصدر لطبقات [dao] و[service]:

Image

قد تكون جميع الملفات التي تحتوي على [postgres] في أسمائها قد تم تعديلها أو لم يتم تعديلها مقارنة بإصدار Firebird. وفيما يلي، نصف الملفات التي تم تعديلها.


مجلد [database]


يحتوي هذا المجلد على البرنامج النصي لإنشاء قاعدة بيانات Postgres للمستخدمين:

Image

-- EMS PostgreSQL Manager Lite 3.1.0.1
-- ---------------------------------------
-- Host : localhost
-- Database : dbpersonnes



--
-- Definition for function plpgsql_call_handler (OID = 17230) : 
--
...
--
-- Structure for table PERSONNES (OID = 17254) : 
--
CREATE TABLE "PERSONNES" (
    "ID" integer NOT NULL,
    "VERSION" integer NOT NULL,
    "NOM" varchar(30) NOT NULL,
    "PRENOM" varchar(30) NOT NULL,
    "DATENAISSANCE" date NOT NULL,
    "MARIE" boolean NOT NULL,
    "NBENFANTS" integer NOT NULL,
    CONSTRAINT "PERSONNES_chk_NBENFANTS" CHECK (("NBENFANTS" >= 0)),
    CONSTRAINT "PERSONNES_chk_NOM" CHECK ((("NOM")::text <> ''::text)),
    CONSTRAINT "PERSONNES_chk_PRENOM" CHECK ((("PRENOM")::text <> ''::text))
);
--
-- Definition for sequence SEQ_ID (OID = 17261) : 
--
CREATE SEQUENCE "SEQ_ID"
    INCREMENT BY 1
    NO MAXVALUE
    NO MINVALUE
    CACHE 1;
--
-- Data for blobs (OID = 17254) (LIMIT 0,3)
--
INSERT INTO "PERSONNES" ("ID", "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS") VALUES (1, 1, 'Major', 'Joachim', '1984-11-13', true, 2);
INSERT INTO "PERSONNES" ("ID", "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS") VALUES (2, 1, 'Humbort', 'Mélanie', '1985-01-12', false, 1);
INSERT INTO "PERSONNES" ("ID", "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS") VALUES (3, 1, 'Lemarchand', 'Charles', '1986-01-01', false, 0);
--
-- Definition for index PERSONNES_pkey (OID = 17256) : 
--
ALTER TABLE ONLY "PERSONNES"
    ADD CONSTRAINT "PERSONNES_pkey" PRIMARY KEY ("ID");
--
-- Data for sequence public."SEQ_ID" (OID = 17261)
--
SELECT pg_catalog.setval('"SEQ_ID"', 37, true);
--
-- Comments
--
COMMENT ON SCHEMA public IS 'Standard public schema';

مجلد [lib]


يحتوي هذا المجلد على الأرشيفات التي يتطلبها التطبيق:

لاحظ وجود برنامج تشغيل JDBC لنظام إدارة قواعد البيانات Postgres 8.x. جميع هذه الملفات جزء من مسار فئات مشروع Eclipse.

18.3. طبقة [dao]

طبقة [dao] هي كما يلي:

Image

نحن نعرض فقط التغييرات مقارنة بإصدار [Firebird].

ملف التعيين [personne-postgres.xml] هو كما يلي:


<?xml version="1.0" encoding="UTF-8" ?>
 
<!DOCTYPE sqlMap
    PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
    "http://www.ibatis.com/dtd/sql-map-2.dtd">
 
<!-- warning - Postgresql 8 requires exact spelling of column names
     et des tables ainsi que des guillemets autour de ces noms -->
 
    <sqlMap>
        <!-- alias class [Person] -->
        <typeAlias alias="Personne.classe" 
            type="istia.st.mvc.personnes.entites.Personne"/>
        <!-- mapping table [PERSONNES] - object [Person] -->
        <resultMap id="Personne.map" 
            class="istia.st.mvc.personnes.entites.Personne">
            <result property="id" column="ID" />
            <result property="version" column="VERSION" />
            <result property="nom" column="NOM"/>
            <result property="prenom" column="PRENOM"/>
            <result property="dateNaissance" column="DATENAISSANCE"/>
            <result property="marie" column="MARIE"/>
            <result property="nbEnfants" column="NBENFANTS"/>
        </resultMap>
        <!-- list of all persons -->
        <select id="Personne.getAll" resultMap="Personne.map" > select "ID", 
            "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS" FROM 
            "PERSONNES"</select>
        <!-- get a specific person -->
        <select id="Personne.getOne" resultMap="Personne.map" >select "ID", 
            "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS" FROM 
            "PERSONNES" WHERE "ID"=#value#</select>
        <!-- add a person -->
        <insert id="Personne.insertOne" parameterClass="Personne.classe"> 
            <selectKey keyProperty="id">
                SELECT nextval('"SEQ_ID"') as value
            </selectKey> 
            insert into "PERSONNES"("ID", "VERSION", 
            "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS") VALUES(#id#, 
            #version#, #nom#, #prenom#, #dateNaissance#, #marie#, #nbEnfants#) 
            </insert>
        <!-- update a person -->
        <update id="Personne.updateOne" parameterClass="Personne.classe"> update 
            "PERSONNES" set "VERSION"=#version#+1, "NOM"=#nom#, "PRENOM"=#prenom#, 
            "DATENAISSANCE"=#dateNaissance#, "MARIE"=#marie#, "NBENFANTS"=#nbEnfants# 
            WHERE "ID"=#id# and "VERSION"=#version#</update>
        <!-- delete a person -->
        <delete id="Personne.deleteOne" parameterClass="int"> delete FROM 
            "PERSONNES" WHERE "ID"=#value# </delete>
    </sqlMap>

هذا هو نفس محتوى [people-firebird.xml] مع الاختلافات الطفيفة التالية:

  • تُوضع أسماء الأعمدة والجداول بين علامتي اقتباس، ويُراعي فيها التمييز بين الأحرف الكبيرة والصغيرة
  • تغيرت عبارة SQL "Person.insertOne" في الأسطر 34–41. تختلف طريقة إنشاء المفتاح الأساسي في Postgres عن تلك المستخدمة في Firebird:
    • السطر 36: يوفر جملة SQL [SELECT nextval('"SEQ_ID"')] المفتاح الأساسي. صيغة [as value] إلزامية. يمثل [value] المفتاح الذي تم الحصول عليه. سيتم تعيين هذه القيمة إلى حقل كائن [Person] المحدد بواسطة السمة [keyProperty] (السطر 35)، وهو هنا حقل [id].
    • يتم تنفيذ عبارات SQL داخل العلامة <insert> بالترتيب الذي تظهر به. لذلك، يتم تنفيذ SELECT قبل INSERT. بحلول وقت حدوث عملية الإدراج، سيكون حقل [id] للكائن [Person] قد تم تحديثه بواسطة عبارة SQL SELECT.
    • الأسطر 38-40: إدراج كائن [Person]

فئة التنفيذ [DaoImplCommon] لطبقة [dao] هي الفئة التي تمت دراستها في إصدار [Firebird].

تم تكييف تكوين طبقة [dao] مع نظام إدارة قواعد البيانات [Postgres]. وبالتالي، يكون ملف التكوين [spring-config-test-dao-postgres.xml] كما يلي:


<?xml version="1.0" encoding="ISO_8859-1"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- data source DBCP -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName">
            <value>org.postgresql.Driver</value>
        </property>
        <property name="url">
            <value>jdbc:postgresql:dbpersonnes</value>
        </property>
        <property name="username">
            <value>postgres</value>
        </property>
        <property name="password">
            <value>postgres</value>
        </property>
    </bean>
    <!-- SqlMapCllient -->
    <bean id="sqlMapClient" 
        class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
        <property name="dataSource">
            <ref local="dataSource"/>
        </property>
        <property name="configLocation">
            <value>classpath:sql-map-config-postgres.xml</value>
        </property>
    </bean>
    <!-- the [dao] layer access classes -->
    <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon">
        <property name="sqlMapClient">
            <ref local="sqlMapClient"/>
        </property>
    </bean>
</beans>
  • الأسطر 5–19: يشير bean [dataSource] الآن إلى قاعدة البيانات [Postgres] [dbpersonnes]، التي يديرها [postgres] بكلمة مرور [postgres]. يجب على القارئ تعديل هذا التكوين وفقًا لبيئته الخاصة.
  • السطر 31: فئة [DaoImplCommon] هي فئة التنفيذ لطبقة [dao]

بعد إجراء هذه التغييرات، يمكننا الانتقال إلى الاختبار.

18.4. اختبارات طبقات [dao] و[service]

اختبارات طبقات [dao] و [service] هي نفسها المستخدمة في إصدار [Firebird]. دعونا نطلق نظام إدارة قواعد البيانات Postgres ثم نجري اختبارات Eclipse. النتائج التي تم الحصول عليها هي كما يلي:

يمكننا أن نرى أن الاختبارات نجحت مع تطبيق [DaoImplCommon]. لن نحتاج إلى اشتقاق هذه الفئة كما كان علينا أن نفعل مع نظام إدارة قواعد البيانات [Firebird].

18.5. اختبارات تطبيق [Web]

لاختبار تطبيق الويب باستخدام نظام إدارة قواعد البيانات [Postgres]، نقوم بإنشاء مشروع Eclipse [mvc-personnes-04B] بطريقة مماثلة لتلك المستخدمة في إنشاء مشروع [mvc-personnes-03B] باستخدام قاعدة بيانات Firebird (انظر القسم 17.7). ومع ذلك، لن نحتاج إلى إعادة إنشاء أرشيفات [personnes-dao.jar] و [personnes-service.jar]. في الواقع، لم نقم بتعديل أي فئات مقارنة بمشروع [mvc-personnes-03B]. يحتوي أرشيف [personnes-dao.jar] ببساطة على فئة [DaoImplFirebird]، التي لم تعد هناك حاجة إليها.

Image

نقوم بنشر مشروع الويب [mvc-personnes-04B] داخل Tomcat:

نحن جاهزون لإجراء الاختبارات . أصبحت محتويات جدول [PERSONNES] الآن كما يلي:

Image

Tomcat قيد التشغيل. باستخدام متصفح، نطلب عنوان URL [http://localhost:8080/mvc-personnes-04B]:

Image

نضيف شخصًا جديدًا باستخدام رابط [Add]:

نتحقق من الإضافة في قاعدة البيانات:

Image

ندعو القارئ إلى إجراء المزيد من الاختبارات [تحديث، حذف].