19. تطبيق ويب MVC في بنية ثلاثية الطبقات – المثال 5، MySQL
19.1. قاعدة بيانات MySQL
في هذا الإصدار، سنقوم بتخزين قائمة الأشخاص في جدول قاعدة بيانات MySQL 4.x. استخدمنا حزمة [Apache – MySQL – PHP] المتوفرة على الرابط [http://www.easyphp.org]. وفيما يلي، لقطات الشاشة مأخوذة من عميل MySQL Manager Lite [http://www.sqlmanager.net/fr/products/mysql/manager]، وهو عميل إدارة مجاني لنظام إدارة قواعد البيانات MySQL.
تسمى قاعدة البيانات [dbpersonnes]. وهي تحتوي على جدول باسم [PERSONNES]:

سيحتوي جدول [PERSONNES] على قائمة بالأشخاص الذين يديرهم تطبيق الويب. تم إنشاؤه باستخدام عبارات SQL التالية:
يبدو أن MySQL 4.x أقل قدرة من نظامي إدارة قواعد البيانات السابقين. لم أتمكن من إضافة قيود (فحوصات) إلى الجدول.
- السطر 10: يجب أن يكون الجدول من النوع [InnoDB] وليس [MyISAM]، الذي لا يدعم المعاملات.
- السطر 2: المفتاح الأساسي من النوع auto_increment. إذا تم إدراج صف دون قيمة لعمود ID في الجدول، فسيقوم MySQL تلقائيًا بإنشاء عدد صحيح لهذا العمود. وهذا سيوفر علينا عناء إنشاء المفاتيح الأساسية بأنفسنا.
يمكن أن يحتوي الجدول [PERSONNES] على المحتوى التالي:

نعلم أنه عند إدراج كائن [Person] عبر طبقة [DAO] الخاصة بنا، فإن حقل [id] لهذا الكائن يساوي -1 قبل الإدراج ويكون له قيمة غير -1 بعد ذلك؛ هذه القيمة هي المفتاح الأساسي المخصص للصف الجديد الذي تم إدراجه في الجدول [PERSONNES]. دعونا نلقي نظرة على مثال لنرى كيف يمكننا تحديد هذه القيمة.
![]() |
![]() |
عبارة SQL
يسمح لنا بتحديد آخر قيمة تم إدخالها في حقل ID بالجدول. يجب تنفيذه بعد الإدخال. وهذا يختلف عن أنظمة إدارة قواعد البيانات [Firebird] و[Postgres]، حيث طلبنا قيمة المفتاح الأساسي للشخص المضاف قبل الإدخال. سنستخدمه في ملف [people-mysql.xml]، الذي يحتوي على عبارات SQL التي يتم تنفيذها على قاعدة البيانات.
19.2. مشروع Eclipse لطبقات [DAO] و[service]
لتطوير طبقات [dao] و[service] لتطبيقنا باستخدام قاعدة بيانات MySQL، سنستخدم مشروع Eclipse التالي [mvc-personnes-05]:

المشروع هو مشروع Java بسيط، وليس مشروع ويب Tomcat.
دليل [src]
يحتوي هذا المجلد على شفرة المصدر لطبقتي [dao] و[service]، بالإضافة إلى ملفات التكوين لهاتين الطبقتين:

قد تكون جميع الملفات التي تحتوي على [mysql] في أسمائها قد تم تعديلها أو لم يتم تعديلها لإصدارات Firebird و Postgres. وفيما يلي، نصف تلك التي تم تعديلها.
مجلد [database]
يحتوي هذا المجلد على البرنامج النصي لإنشاء قاعدة بيانات MySQL للمستخدمين:
![]()
المجلد [lib]
يحتوي هذا الدليل على الملفات التي يحتاجها التطبيق:
![]() |
لاحظ وجود برنامج تشغيل JDBC لنظام إدارة قواعد البيانات MySQL. جميع هذه الملفات هي جزء من مسار فئات مشروع Eclipse.
19.3. طبقة [dao]
طبقة [dao] هي كما يلي:

نحن نعرض فقط التغييرات مقارنة بإصدار [Firebird].
ملف التعيين [person-mysql.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">
<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">
insert into
PERSONNES(VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS)
VALUES(#version#, #nom#, #prenom#, #dateNaissance#, #marie#,
#nbEnfants#)
<selectKey keyProperty="id">
select LAST_INSERT_ID() as value
</selectKey>
</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>
<!-- obtain the value of the primary key [id] of the last person inserted -->
<select id="Personne.getNextId" resultClass="int">select
LAST_INSERT_ID()</select>
</sqlMap>
هذا هو نفس محتوى [people-firebird.xml] مع الاختلافات الطفيفة التالية:
- تم تغيير عبارة SQL "Person.insertOne" في الأسطر 29–37:
- يتم تنفيذ عبارة الإدراج SQL قبل عبارة SELECT، التي تسترد قيمة المفتاح الأساسي للصف الذي تم إدراجه
- لا يحتوي أمر الإدراج SQL على قيمة لعمود ID في جدول [PERSONNES]
وهذا يعكس مثال الإدراج الذي ناقشناه في القسم 19.1.
لاحظ أن هذا قد يكون مصدرًا محتملاً للمشاكل بين الخيوط المتزامنة. تخيل خيطين، Th1 و Th2، يقومان بعملية إدراج في نفس الوقت. هناك ما مجموعه أربعة أوامر SQL يجب تنفيذها. لنفترض أنها تُنفذ بالترتيب التالي:
- إدراج I1 بواسطة Th1
- insert I2 بواسطة Th2
- select S1 بواسطة Th1
- select S2 بواسطة Th2
في الخطوة 3، يسترد Th1 المفتاح الأساسي الذي تم إنشاؤه أثناء عملية الإدراج الأخيرة، وهو مفتاح Th2 وليس مفتاحه الخاص. لست متأكدًا مما إذا كانت طريقة [insert] في iBATIS محمية ضد هذا السيناريو. سنفترض أنها تتعامل مع هذا الأمر بشكل صحيح. إذا لم يكن الأمر كذلك، فسنحتاج إلى اشتقاق فئة التنفيذ [DaoImplCommon] من طبقة [dao] إلى فئة [DaoImplMySQL] حيث يتم مزامنة طريقة [insertPersonne]. وهذا من شأنه أن يحل المشكلة فقط بالنسبة للخيوط في تطبيقنا. إذا كان Th1 و Th2 في المثال أعلاه خيوطًا من تطبيقين مختلفين، فسيتعين عندئذٍ حل المشكلة باستخدام كل من المعاملات ومستوى عزل مناسب بين المعاملات. وسيكون مستوى [serializable]، الذي يتم فيه تنفيذ المعاملات كما لو كانت تعمل بالتسلسل، هو المستوى المناسب.
لاحظ أن هذه المشكلة لا توجد في أنظمة إدارة قواعد البيانات Firebird و Postgres، التي تنفذ SELECT قبل INSERT. على سبيل المثال، انظر التسلسل التالي:
- select S1 from Th1
- SELECT S2 من Th2
- insert I1 from Th1
- insert I2 from Th2
في الخطوتين 1 و 2، يسترد Th1 و Th2 قيم المفتاح الأساسي من نفس المولد. عادةً ما تكون هذه العملية ذرية، وسيسترد Th1 و Th2 قيمتين مختلفتين. إذا لم تكن العملية ذرية، واسترد Th1 و Th2 قيمتين متطابقتين، فسيفشل الإدراج الذي يتم تنفيذه في الخطوة 4 بواسطة Th2 بسبب تكرار المفتاح الأساسي. هذا خطأ قابل للاسترداد بالكامل، ويمكن لـ Th2 إعادة محاولة الإدراج.
سنترك عملية "Personne.insertOne" كما هي حاليًا في ملف [personnes-mysql.xml]، ولكن يجب أن يدرك القارئ أن هناك مشكلة محتملة هنا.
فئة التنفيذ [DaoImplCommon] لطبقة [dao] هي نفسها كما في الإصدارين السابقين.
تم تكييف تكوين طبقة [dao] مع نظام إدارة قواعد البيانات [MySQL]. وبالتالي، فإن ملف التكوين [spring-config-test-dao-mysql.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>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost/dbpersonnes</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value></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-mysql.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] الآن إلى قاعدة البيانات [MySQL] [dbpersonnes]، التي يكون [root] هو المسؤول عنها بدون كلمة مرور. يجب على القارئ تعديل هذا التكوين وفقًا لبيئته الخاصة.
- السطر 31: فئة [DaoImplCommon] هي فئة التنفيذ لطبقة [dao]
بعد إجراء هذه التغييرات، يمكننا الانتقال إلى مرحلة الاختبار.
19.4. اختبارات طبقات [dao] و[service]
اختبارات طبقات [dao] و [service] هي نفسها المستخدمة في إصدار [Firebird]. النتائج التي تم الحصول عليها هي كما يلي:
![]() |
يمكننا أن نرى أن الاختبارات نجحت مع تطبيق [DaoImplCommon]. لن نحتاج إلى اشتقاق هذه الفئة كما كان ضروريًا مع نظام إدارة قواعد البيانات [Firebird].
19.5. اختبارات تطبيق [Web]
لاختبار تطبيق الويب باستخدام نظام إدارة قواعد البيانات [MySQL]، نقوم بإنشاء مشروع Eclipse [mvc-personnes-05B] بطريقة مماثلة لتلك المستخدمة في إنشاء مشروع [mvc-personnes-03B] باستخدام قاعدة بيانات Firebird (انظر القسم 17.7). ومع ذلك، كما هو الحال مع Postgres، لن نحتاج إلى إعادة إنشاء أرشيفات [personnes-dao.jar] و [personnes-service.jar] لأننا لم نقم بتعديل أي فئات.
نقوم بنشر مشروع الويب [mvc-personnes-05B] داخل Tomcat:
![]() | ![]() |
يتم تشغيل نظام إدارة قواعد البيانات MySQL. ويكون محتوى جدول [PERSONNES] كما يلي:

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

نقوم بإضافة شخصًا جديدًا باستخدام رابط [Add]:
![]() | ![]() |
نتحقق من الإضافة في قاعدة البيانات:

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







