6. إدارة قواعد البيانات باستخدام API JDBC
6.1. مقدمة
توجد العديد من قواعد البيانات في السوق. من أجل توحيد الوصول إلى قواعد البيانات في نظام التشغيل Windows، طورت Microsoft واجهة تسمى Open Connectivity. تخفي هذه الطبقة خصائص كل قاعدة بيانات تحت واجهة قياسية. يوجد في نظام التشغيل Windows العديد من برامج التشغيل التي تسهل الوصول إلى قواعد البيانات. فيما يلي، على سبيل المثال، قائمة ببرامج التشغيل المثبتة على جهاز يعمل بنظام Win95:

يمكن لأي تطبيق يعتمد على هذه البرامج التشغيلية استخدام أي من قواعد البيانات المذكورة أعلاه دون الحاجة إلى إعادة كتابة.

ولكي تتمكن تطبيقات Java أيضًا من الاستفادة من واجهة ODBC، أنشأت Sun واجهة JDBC (Java DataBase Connectivity) التي ستتدخل بين تطبيق Java وواجهة ODBC:

6.2. الخطوات المهمة في تشغيل قواعد البيانات
6.2.1. مقدمة
في تطبيق JAVA يستخدم قاعدة بيانات مع واجهة JDBC، نجد عادةً الخطوات التالية:
- الاتصال بقاعدة البيانات
- إرسال استعلامات SQL إلى قاعدة البيانات
- استلام ومعالجة نتائج هذه الاستعلامات
- إغلاق الاتصال
يتم تنفيذ الخطوتين 2 و 3 بشكل متكرر، ولا يتم إغلاق الاتصال إلا في نهاية تشغيل قاعدة البيانات. هذا مخطط كلاسيكي نسبيًا قد تكون معتادًا عليه إذا كنت قد استخدمت قاعدة بيانات بشكل تفاعلي. سنفصل كل خطوة من هذه الخطوات باستخدام مثال. لنفترض أن لدينا قاعدة بيانات ACCESS تسمى ARTICLES وتتمتع بالبنية التالية:
الاسم | النوع |
code | رمز المادة المكون من 4 أحرف |
nom | الاسم (سلسلة أحرف) |
prix | سعره (الفعلي) |
stock_actu | مخزونه الحالي (عدد صحيح) |
stock_mini | الحد الأدنى للمخزون (عدد صحيح) الذي يجب تجديد مخزون المنتج عنده |
يتم تعريف قاعدة البيانات ACCESS كمصدر بيانات "مستخدم" في مدير قواعد البيانات ODBC:


يتم تحديد خصائصها باستخدام الزر Configurer على النحو التالي:

تتمثل هذه التهيئة أساسًا في ربط قاعدة البيانات ARTICLES بملف Access articles.mdb المطابق لهذه القاعدة. وبذلك، تصبح قاعدة البيانات ARTICLES متاحة للتطبيقات التي تستخدم واجهة ODBC.
6.2.2. مرحلة الاتصال
لاستخدام قاعدة بيانات، يجب أن يقوم تطبيق Java أولاً بمرحلة الاتصال. ويتم ذلك باستخدام طريقة الفئة التالية:
مع
DriverManager: فئة Java تحتوي على قائمة برامج التشغيل المتاحة للتطبيق
Connection: فئة Java تربط بين التطبيق وقاعدة البيانات، والتي من خلالها يقوم التطبيق بإرسال طلبات SQL إلى قاعدة البيانات وتلقي النتائج
URL: الاسم الذي يحدد قاعدة البيانات. هذا الاسم مشابه لـ URL على الإنترنت. ولهذا السبب فهو جزء من فئة URL. ومع ذلك، لا يتدخل الإنترنت هنا بأي شكل من الأشكال. يكون شكل URL كما يلي:
jdbc:nom_du_pilote:nom_de_la_source;param=val1;param2=val2
في أمثلةنا التي سنستخدم فيها برامج تشغيل ODBC حصريًا، يُسمى برنامج التشغيل odbc. يتكون الجزء الثالث من URL من اسم المصدر مع المعلمات المحتملة. في أمثلةنا، ستكون هذه مصادر ODBC معروفة للنظام. وبالتالي، فإن URL لمصدر البيانات Articles المحدد سابقًا سيكون
jdbc:odbc:Articles
id: هوية المستخدم (login)
mdp: كلمة مرور المستخدم (password)
باختصار، يتصل البرنامج بقاعدة بيانات:
- يتم تحديدها باسم (URL)
- تحت هوية مستخدم (id, mdp)
إذا كانت هذه المعلمات الثلاثة صحيحة، وإذا كان هناك برنامج تشغيل قادر على ضمان اتصال تطبيق Java بقاعدة البيانات المحددة، يتم إجراء اتصال بين تطبيق Java وقاعدة البيانات. ويتم تجسيد هذا الاتصال للبرنامج من خلال كائن من النوع Connection الذي توفره الفئة DriverManager. نظرًا لأن هذا الاتصال قد يفشل لأسباب مختلفة، فمن المحتمل أن يطلق استثناءً. لذلك سنكتب:
Connection connexion=null;
URL base=...;
String id=...;
String mdp=...;
try{
connexion=DriverManager.getConnection(base,id,mdp);
} catch (Exception e){
// معالجة الاستثناء
}
لكي يكون الاتصال بقاعدة البيانات ممكنًا، يجب توفر برنامج التشغيل المناسب. في أمثلةنا، سيكون برنامج التشغيل ODBC قادرًا على إدارة قاعدة البيانات المطلوبة. وإذا كان يجب أن يكون هذا البرنامج التشغيلي متاحًا في قائمة برامج التشغيل ODBC الموجودة على الجهاز، فيجب أيضًا توفر الفئة JAVA التي ستعمل كواجهة تواصل معه. وللقيام بذلك، سيطلب التطبيق الفئة اللازمة له بالطريقة التالية:
لا ترتبط الفئة Class بأي شكل من الأشكال بواجهة JDBC. إنها فئة عامة لإدارة الفئات. تسمح طريقتها الثابتة forName بتحميل فئة ديناميكيًا وبالتالي الاستفادة من سماتها وطرقها الثابتة. تسمى الفئة التي تشكل واجهة مع برامج التشغيل ODBC لـ MS Windows « sun.jdbc.odbc.JdbcOdbcDriver ». لذلك سنكتب (قد تولد الطريقة استثناءً):
try{
Class.forName(« sun.jdbc.odbc.JdbcOdbcDriver ») ;
} catch (Exception e){
// معالجة الاستثناء (فئة غير موجودة)
}
توجد الفئات اللازمة للواجهة JDBC في الحزمة java.sql. لذلك سنكتب في بداية البرنامج:
فيما يلي برنامج يسمح بالاتصال بقاعدة بيانات:
import java.sql.*;
import java.io.*;
// استدعاء: pg PILOTE URL UID MDP
// يتصل بقاعدة البيانات URL بفضل الفئة JDBC PILOTE
// يتم تعريف المستخدم UID بكلمة مرور MDP
public class connexion1{
static String syntaxe="pg PILOTE URL UID MDP";
public static void main(String arg[]){
// التحقق من عدد الحجج
if(arg.length<2 || arg.length>4)
erreur(syntaxe,1);
// الاتصال بالقاعدة
Connection connect=null;
String uid="";
String mdp="";
if(arg.length>=3) uid=arg[2];
if(arg.length==4) mdp=arg[3];
try{
Class.forName(arg[0]);
connect=DriverManager.getConnection(arg[1],uid,mdp);
System.out.println("Connexion avec la base " + arg[1] + " établie");
} catch (Exception e){
erreur("Erreur " + e,2);
}
// إغلاق قاعدة البيانات
try{
connect.close();
System.out.println("Base " + arg[1] + " fermée");
} catch (Exception e){}
}// اليد
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// فئة
فيما يلي مثال على التنفيذ:
E:\data\java\jdbc\0>java connexion1 sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles
Connexion avec la base jdbc:odbc:articles établie
Base jdbc:odbc:articles fermée
6.2.3. إرسال الاستعلامات إلى قاعدة البيانات
تسمح واجهة JDBC بإرسال استعلامات SQL إلى قاعدة البيانات المتصلة بتطبيق Java وكذلك معالجة نتائج هذه الاستعلامات. لغة SQL (لغة الاستعلام الهيكلية) هي لغة استعلامات موحدة لقواعد البيانات العلائقية. وتتميز بعدة أنواع من الاستعلامات:
- استعلامات الاستعلام عن قاعدة البيانات (SELECT)
- استعلامات تحديث قاعدة البيانات (INSERT، DELETE، UPDATE)
- طلبات إنشاء/حذف الجداول (CREATE، DELETE)
نفترض هنا أن القارئ على دراية بأساسيات لغة SQL.
6.2.3.1. فئة Statement
لإرسال أي استعلام SQL إلى قاعدة بيانات، يجب أن يتوفر للتطبيق Java كائن من نوع Statement. سيخزن هذا الكائن، من بين أمور أخرى، نص الاستعلام. ويرتبط هذا الكائن بالضرورة بالاتصال الجاري. لذلك، فإن إحدى طرق الاتصال الذي تم إنشاؤه هي التي تسمح بإنشاء الكائنات Statement اللازمة لإرسال الاستعلامات SQL. إذا كان connexion هو الكائن الذي يمثل الاتصال بقاعدة البيانات، يتم الحصول على كائن Statement بالطريقة التالية:
بمجرد الحصول على كائن Statement، يمكن إصدار طلبات SQL. ويتم ذلك بطريقة مختلفة حسب ما إذا كان الطلب طلب استعلام أو تحديث للقاعدة.
6.2.3.2. إرسال طلب استعلام عن قاعدة البيانات
عادةً ما يكون طلب الاستعلام من النوع التالي:
الكلمات الرئيسية في السطر الأول هي فقط التي تعتبر إلزامية، أما البقية فهي اختيارية. وهناك كلمات رئيسية أخرى غير مذكورة هنا.
- يتم إجراء عملية ربط مع جميع الجداول الموجودة بعد الكلمة الرئيسية from
- يتم الاحتفاظ فقط بالأعمدة الموجودة بعد الكلمة الرئيسية select
- يتم الاحتفاظ فقط بالصفوف التي تستوفي شرط الكلمة الرئيسية where
- تشكل الأسطر الناتجة المرتبة وفقًا للتعبير الوارد في الكلمة الرئيسية order by نتيجة الاستعلام.
نتيجة select هي جدول. إذا أخذنا الجدول ARTICLES السابق بعين الاعتبار وأردنا الحصول على أسماء المنتجات التي يقل مخزونها الحالي عن الحد الأدنى، فسنكتب: select name from products where stock_actu<stock_mini. إذا أردنا ترتيبها أبجديًا حسب الأسماء، فسنكتب: select name from items where stock_actu<stock_mini order by name
لتنفيذ هذا النوع من الاستعلامات، توفر الفئة Statement الطريقة executeQuery:
حيث requête هو نص الاستعلام SELECT المراد إرساله.
وبالتالي، إذا كان
- كان connexion هو الكائن الذي يمثل الاتصال بقاعدة البيانات
- Statement s=connexion.createStatement() ينشئ الكائن Statement الضروري لإرسال الاستعلامات SQL
- ResultSet rs=s.executeQuery(« select name from articles where stock_actu<stock_mini ») تنفذ استعلام select وتعيّن صفوف نتائج الاستعلام إلى كائن من النوع ResultSet.
6.2.3.3. الفئة ResultSet: نتيجة استعلام select
يمثل كائن من النوع ResultSet جدولًا، أي مجموعة من الصفوف والأعمدة. في لحظة معينة، لا يمكن الوصول إلا إلى صف واحد من الجدول يُسمى الصف الحالي. عند الإنشاء الأولي لـ ResultSet، يكون الصف الحالي هو الصف رقم 1 إذا لم يكن ResultSet فارغًا. للانتقال إلى السطر التالي، تتوفر للفئة ResultSet الطريقة next:
تحاول هذه الطريقة الانتقال إلى السطر التالي من ResultSet وتُرجع true في حالة نجاحها، وfalse في حالة الفشل. في حالة النجاح، يصبح السطر التالي هو السطر الحالي الجديد. يتم فقدان السطر السابق ولا يمكن الرجوع للخلف لاستعادته. تحتوي جدول ResultSet على أعمدة col1 و col2 و... لاستخدام الحقول المختلفة في السطر الحالي، تتوفر الطرق التالية:
للحصول على الحقل « coli » من السطر الحالي. Type يشير إلى نوع الحقل coli. غالبًا ما يتم استخدام الطريقة getString على جميع الحقول، مما يسمح بالحصول على محتوى الحقل كسلسلة أحرف. ثم يتم التحويل إذا لزم الأمر. إذا لم يكن اسم العمود معروفًا، يمكن استخدام الطرق
حيث i هو مؤشر العمود المطلوب (i>=1).
6.2.3.4. مثال أول
فيما يلي برنامج يعرض محتوى قاعدة البيانات ARTICLES التي تم إنشاؤها مسبقًا:
import java.sql.*;
import java.io.*;
// عرض محتوى قاعدة بيانات النظام ARTICLES
public class articles1{
static final String DB="ARTICLES"; // قاعدة البيانات المطلوب استغلالها
public static void main(String arg[]){
Connection connect=null; // الاتصال بقاعدة البيانات
Statement S=null; // كائن إرسال الاستعلامات
ResultSet RS=null; // جدول نتائج الاستعلام
try{
// الاتصال بقاعدة البيانات
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// إنشاء كائن Statement
S=connect.createStatement();
// تنفيذ استعلام select
RS=S.executeQuery("select * from " + DB);
// استخدام جدول النتائج
while(RS.next()){ // طالما يوجد سطر للاستخدام
// يتم عرضها على الشاشة
System.out.println(RS.getString("code")+","+
RS.getString("nom")+","+
RS.getString("prix")+","+
RS.getString("stock_actu")+","+
RS.getString("stock_mini"));
}// السطر التالي
} catch (Exception e){
erreur("Erreur " + e,2);
}
// إغلاق قاعدة البيانات
try{
connect.close();
System.out.println("Base " + DB + " fermée");
} catch (Exception e){}
}// اليد
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// فئة
النتائج التي تم الحصول عليها هي التالية:
Connexion avec la base ARTICLES établie
a300,vélo,1202,30,2
d600,arc,5000,10,2
d800,canoé,1502,12,6
x123,fusil,3000,10,2
s345,skis nautiques,1800,3,2
f450,essai3,3,3,3
f807,cachalot,200000,0,0
z400,léopard,500000,1,1
g457,panthère,800000,1,1
Base ARTICLES fermée
6.2.3.5. الفئة ResultSetMetadata
في المثال السابق، نعرف أسماء أعمدة ResultSet. إذا لم نكن نعرفها، فلا يمكننا استخدام الطريقة getType(nom_colonne). بدلاً من ذلك، سنستخدم getType(رقم العمود). ومع ذلك، للحصول على جميع الأعمدة، يجب أن نعرف عدد الأعمدة في ResultSet الذي تم الحصول عليه. لا توفر لنا الفئة ResultSet هذه المعلومات. بل توفرها لنا الفئة ResultSetMetaData. وبشكل عام، توفر لنا هذه الفئة معلومات عن بنية الجدول، أي عن طبيعة أعمدةه.
يمكن الوصول إلى المعلومات حول بنية ResultSet عن طريق إنشاء مثيل لكائن ResultSetMetaData أولاً. إذا كان RS هو ResultSet، يتم الحصول على ResultSetMetaData المرتبط به عن طريق:
تجدر الإشارة إلى طريقتين مفيدتين في الفئة ResultSetMetaData:
- int getColumnCount() التي تعطي عدد أعمدة ResultSet
- String getColumnLabel(int i) التي تعطي اسم العمود i في ResultSet (i>=1)
6.2.3.6. مثال ثانٍ
كان البرنامج السابق يعرض محتوى قاعدة البيانات ARTICLES. نكتب هنا برنامجًا ينفذ على قاعدة البيانات ARTICLES أي استعلام SQL Select يكتبه المستخدم على لوحة المفاتيح.
import java.sql.*;
import java.io.*;
// يعرض محتوى قاعدة بيانات النظام ARTICLES
public class sql1{
static final String DB="ARTICLES"; // قاعدة البيانات المطلوب استغلالها
public static void main(String arg[]){
Connection connect=null; // الاتصال بقاعدة البيانات
Statement S=null; // كائن إرسال الاستعلامات
ResultSet RS=null; // جدول نتائج الاستعلام
String select; // نص الاستعلام SQL select
int nbColonnes; // عدد أعمدة ResultSet
// إنشاء تدفق إدخال من لوحة المفاتيح
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(System.in));
} catch(Exception e){
erreur("erreur lors de l'ouverture du flux clavier ("+e+")",3);
}
try{
// الاتصال بقاعدة البيانات
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// إنشاء كائن Statement
S=connect.createStatement();
// حلقة تنفيذ الاستعلامات SQL المكتوبة على لوحة المفاتيح
System.out.print("Requête : ");
select=in.readLine();
while(!select.equals("fin")){
// تنفيذ الاستعلام
RS=S.executeQuery(select);
// عدد الأعمدة
nbColonnes=RS.getMetaData().getColumnCount();
// استخراج جدول النتائج
System.out.println("Résultats obtenus\n\n");
while(RS.next()){ // طالما يوجد سطر للاستخدام
// يتم عرضه على الشاشة
for(int i=1;i<nbColonnes;i++)
System.out.print(RS.getString(i)+",");
System.out.println(RS.getString(nbColonnes));
}// السطر التالي
// الاستعلام التالي
System.out.print("Requête : ");
select=in.readLine();
}// بينما
} catch (Exception e){
erreur("Erreur " + e,2);
}
// إغلاق قاعدة البيانات وتدفق الإدخال
try{
connect.close();
System.out.println("Base " + DB + " fermée");
in.close();
} catch (Exception e){}
}// main
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// فئة
فيما يلي بعض النتائج التي تم الحصول عليها:
Connexion avec la base ARTICLES établie
Requête : select * from articles order by prix desc
Résultats obtenus
g457,panthère,800000,1,1
z400,léopard,500000,1,1
f807,cachalot,200000,0,0
d600,arc,5000,10,2
x123,fusil,3000,10,2
s345,skis nautiques,1800,3,2
d800,canoé,1502,12,6
a300,vélo,1202,30,2
f450,essai3,3,3,3
Requête : select nom, prix from articles where prix >10000 order by prix desc
Résultats obtenus
panthère,800000
léopard,500000
cachalot,200000
6.2.3.7. إرسال طلب تحديث قاعدة البيانات
يسمح كائن من النوع Statement بتخزين طلبات SQL. الطريقة التي يستخدمها هذا الكائن لإصدار طلبات تحديث SQL (INSERT، UPDATE، DELETE) لم تعد الطريقة executeQuery التي تمت دراستها سابقًا، بل الطريقة executeUpdate:
يكمن الاختلاف في النتيجة: في حين أن executeQuery كانت تُرجع جدول النتائج (ResultSet)، فإن executeUpdate تُرجع عدد الأسطر المتأثرة بعملية التحديث.
6.2.3.8. مثال ثالث
نستأنف البرنامج السابق ونقوم بتعديله قليلاً: أصبحت الاستعلامات التي يتم إدخالها عبر لوحة المفاتيح الآن استعلامات لتحديث قاعدة البيانات ARTICLES.
import java.sql.*;
import java.io.*;
// يعرض محتوى قاعدة بيانات النظام ARTICLES
public class sql2{
static final String DB="ARTICLES"; // قاعدة البيانات المطلوب استغلالها
public static void main(String arg[]){
Connection connect=null; // الاتصال بقاعدة البيانات
Statement S=null; // كائن إرسال الاستعلامات
ResultSet RS=null; // جدول نتائج الاستعلام
String sqlUpdate; // نص الاستعلام SQL للتحديث
int nbLignes; // عدد الأسطر المتأثرة بالتحديث
// إنشاء تدفق إدخال من لوحة المفاتيح
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(System.in));
} catch(Exception e){
erreur("erreur lors de l'ouverture du flux clavier ("+e+")",3);
}
try{
// الاتصال بقاعدة البيانات
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
System.out.println("Connexion avec la base " + DB + " établie");
// إنشاء كائن Statement
S=connect.createStatement();
// حلقة تنفيذ الاستعلامات SQL المكتوبة على لوحة المفاتيح
System.out.print("Requête : ");
sqlUpdate=in.readLine();
while(!sqlUpdate.equals("fin")){
// تنفيذ الاستعلام
nbLignes=S.executeUpdate(sqlUpdate);
// المتابعة
System.out.println(nbLignes + " ligne(s) ont été mises à jour");
// الاستعلام التالي
System.out.print("Requête : ");
sqlUpdate=in.readLine();
}// while
} catch (Exception e){
erreur("Erreur " + e,2);
}
// إغلاق قاعدة البيانات وتدفق الإدخال
try{
// تحرير الموارد المرتبطة بالقاعدة
RS.close();
S.close();
connect.close();
System.out.println("Base " + DB + " fermée");
// إغلاق تدفق لوحة المفاتيح
in.close();
} catch (Exception e){}
}// اليد
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// فئة
فيما يلي نتيجة عمليات تنفيذ مختلفة للبرامج sql1 و sql2:
قائمة الأسطر في قاعدة البيانات ARTICLES:
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES établie
Requête : select nom,stock_actu from articles
Résultats obtenus
vélo,30
arc,10
canoé,12
fusil,10
skis nautiques,3
essai3,3
cachalot,0
léopard,1
panthère,1
يتم تعديل بعض الأسطر:
E:\data\java\jdbc\0>java sql2
Connexion avec la base ARTICLES établie
Requête : update articles set stock_actu=stock_actu+1 where stock_actu>10
2 ligne(s) ont été mises à jour
التحقق:
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES établie
Requête : select nom,stock_actu from articles
Résultats obtenus
vélo,31
arc,10
canoé,13
fusil,10
skis nautiques,3
essai3,3
cachalot,0
léopard,1
panthère,1
نضيف سطراً:
E:\data\java\jdbc\0>java sql2
Connexion avec la base ARTICLES établie
Requête : insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',200,20,10)
1 ligne(s) ont été mises à jour
التحقق:
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES établie
Requ_te : select nom,stock_actu from articles
Résultats obtenus
vélo,31
arc,10
canoé,13
fusil,10
skis nautiques,3
essai3,3
cachalot,0
léopard,1
panthère,1
nouveau,20
يتم حذف سطر:
E:\data\java\jdbc\0>java sql2
Connexion avec la base ARTICLES établie
Requête : delete from articles where code='x400'
1 ligne(s) ont été mises à jour
Requête : fin
التحقق:
E:\data\java\jdbc\0>java sql1
Connexion avec la base ARTICLES établie
Requête : select nom,stock_actu from articles
Résultats obtenus
vélo,31
arc,10
cano_,13
fusil,10
skis nautiques,3
essai3,3
cachalot,0
léopard,1
panthère,1
6.2.3.9. إرسال أي طلب SQL
يحتوي الكائن Statement الضروري لإرسال طلبات SQL على طريقة execute قادرة على تنفيذ أي نوع من طلبات SQL:
النتيجة المعروضة هي القيمة المنطقية true إذا كانت الاستعلام قد أعطت ResultSet (executeQuery)، وfalse إذا أعطت عددًا (executeUpdate). يمكن استرداد القيمة ResultSet التي تم الحصول عليها باستخدام الطريقة getResultSet، وعدد الأسطر التي تم تحديثها باستخدام الطريقة getUpdateCount. وبالتالي، سنكتب:
Statement S=...;
ResultSet RS=...;
int nbLignes;
String requête=...;
// تنفيذ استعلام SQL
if (S.execute(requête)){
// لدينا مجموعة نتائج
RS=S.getResultSet();
// استغلال ResultSet
...
} else {
// كان هذا استعلام تحديث
nbLignes=S.getUpdateCount();
...
}
6.2.3.10. المثال الرابع
نستعيد روح البرامج sql1 و sql2 في برنامج sql3 القادر الآن على تنفيذ أي استعلام SQL يتم كتابته على لوحة المفاتيح. ولجعل البرنامج أكثر عمومية، يتم تمرير خصائص قاعدة البيانات المراد استغلالها كمعلمات إلى البرنامج.
import java.sql.*;
import java.io.*;
// استدعاء: pg PILOTE URL UID MDP
// يتصل بقاعدة البيانات URL بفضل الفئة JDBC PILOTE
// يتم تعريف المستخدم UID بكلمة مرور MDP
public class sql3{
static String syntaxe="pg PILOTE URL UID MDP";
public static void main(String arg[]){
// التحقق من عدد الحجج
if(arg.length<2 || arg.length>4)
erreur(syntaxe,1);
// تهيئة معلمات الاتصال
Connection connect=null;
String uid="";
String mdp="";
if(arg.length>=3) uid=arg[2];
if(arg.length==4) mdp=arg[3];
// بيانات أخرى
Statement S=null; // كائن إرسال الطلبات
ResultSet RS=null; // جدول نتائج استعلام
String sqlText; // نص الاستعلام SQL المطلوب تنفيذه
int nbLignes; // عدد الأسطر المتأثرة بالتحديث
int nbColonnes; // عدد أعمدة ResultSet
// إنشاء تدفق إدخال من لوحة المفاتيح
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(System.in));
} catch(Exception e){
erreur("erreur lors de l'ouverture du flux clavier ("+e+")",3);
}
try{
// الاتصال بقاعدة البيانات
Class.forName(arg[0]);
connect=DriverManager.getConnection(arg[1],uid,mdp);
System.out.println("Connexion avec la base " + arg[1] + " établie");
// إنشاء كائن Statement
S=connect.createStatement();
// حلقة تنفيذ الاستعلامات SQL المكتوبة على لوحة المفاتيح
System.out.print("Requête : ");
sqlText=in.readLine();
while(!sqlText.equals("fin")){
// تنفيذ الاستعلام
try{
if(S.execute(sqlText)){
// تم الحصول على ResultSet - يتم استغلاله
RS=S.getResultSet();
// عدد الأعمدة
nbColonnes=RS.getMetaData().getColumnCount();
// استغلال جدول النتائج
System.out.println("\nRésultats obtenus\n-----------------\n");
while(RS.next()){ // طالما يوجد سطر للاستخدام
// يتم عرضه على الشاشة
for(int i=1;i<nbColonnes;i++)
System.out.print(RS.getString(i)+",");
System.out.println(RS.getString(nbColonnes));
}// السطر التالي من ResultSet
} else {
// كان هذا طلب تحديث
nbLignes=S.getUpdateCount();
// متابعة
System.out.println(nbLignes + " ligne(s) ont été mises à jour");
}//if
} catch (Exception e){
System.out.println("Erreur " +e);
}
// الطلب التالي
System.out.print("\nNouvelle Requête : ");
sqlText=in.readLine();
}// بينما
} catch (Exception e){
erreur("Erreur " + e,2);
}
// إغلاق قاعدة البيانات وتدفق الإدخال
try{
// تحرير الموارد المرتبطة بالقاعدة
RS.close();
S.close();
connect.close();
System.out.println("Base " + arg[1] + " fermée");
// إغلاق تدفق لوحة المفاتيح
in.close();
} catch (Exception e){}
}// main
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
}// فئة
يتم إنشاء ملف الاستعلامات التالي:
select * from articles
update articles set stock_mini=stock_mini+5 where stock_mini<5
select nom,stock_mini from articles
insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',100,20,10)
select * from articles
delete from articles where code='x400'
select * from articles
fin
يتم تشغيل البرنامج على النحو التالي:
وبالتالي، يأخذ البرنامج مدخلاته من الملف requetes ويضع مخرجاته في الملف results. النتائج التي تم الحصول عليها هي التالية:
Connexion avec la base jdbc:odbc:articles établie
Requête : (requete 1 du fichier des requetes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,3
d600,arc,5000,10,3
d800,canoé,1502,13,7
x123,fusil,3000,10,3
s345,skis nautiques,1800,3,3
f450,essai3,3,3,4
f807,cachalot,200000,0,1
z400,léopard,500000,1,2
g457,panthère,800000,1,2
Nouvelle Requête : (requete 2 du fichier des requetes : update articles set stock_mini=stock_mini+5 where stock_mini<5)
8 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 3 du fichier des requetes : select nom,stock_mini from articles)
Résultats obtenus
-----------------
vélo,8
arc,8
canoé,7
fusil,8
skis nautiques,8
essai3,9
cachalot,6
léopard,7
panthère,7
Nouvelle Requête : (requete 4 du fichier des requetes : insert into articles (code,nom,prix,stock_actu,stock_mini) values ('x400','nouveau',100,20,10))
1 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 5 du fichier des requetes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
x400,nouveau,100,20,10
Nouvelle Requête : (requete 6 du fichier des requêtes : delete from articles where code='x400')
1 ligne(s) ont été mises à jour
Nouvelle Requête : (requete 7 du fichier des requêtes : select * from articles)
Résultats obtenus
-----------------
a300,vélo,1202,31,8
d600,arc,5000,10,8
d800,canoé,1502,13,7
x123,fusil,3000,10,8
s345,skis nautiques,1800,3,8
f450,essai3,3,3,9
f807,cachalot,200000,0,6
z400,léopard,500000,1,7
g457,panthère,800000,1,7
6.3. IMPOTS باستخدام قاعدة بيانات
آخر مرة تناولنا فيها مشكلة حساب الضرائب، كان ذلك باستخدام واجهة رسومية وكانت البيانات مخزنة في ملف. نستأنف هذه النسخة بافتراض أن البيانات موجودة الآن في قاعدة بيانات ODBC-MySQL. MySQL هو SGBD من المجال العام قابل للاستخدام على منصات مختلفة بما في ذلك Windows وLinux. مع هذا SGBD، تم إنشاء قاعدة بيانات dbimpots تحتوي على جدول واحد يسمى impots. يتم التحكم في الوصول إلى قاعدة البيانات عن طريق اسم المستخدم/كلمة المرور، وهما هنا admimpots/mdpimpots. توضح لقطة الشاشة كيفية استخدام قاعدة البيانات dbimpots مع MySQL:
C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> use dbimpots;
Database changed
mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots |
+--------------------+
1 row in set (0.00 sec)
mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES | | NULL | |
| coeffR | double | YES | | NULL | |
| coeffN | double | YES | | NULL | |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN |
+---------+--------+---------+
| 12620 | 0 | 0 |
| 13190 | 0.05 | 631 |
| 15640 | 0.1 | 1290.5 |
| 24740 | 0.15 | 2072.5 |
| 31810 | 0.2 | 3309.5 |
| 39970 | 0.25 | 4900 |
| 48360 | 0.3 | 6898 |
| 55790 | 0.35 | 9316.5 |
| 92970 | 0.4 | 12106 |
| 127860 | 0.45 | 16754 |
| 151250 | 0.5 | 23147.5 |
| 172040 | 0.55 | 30710 |
| 195000 | 0.6 | 39312 |
| 0 | 0.65 | 49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)
mysql>quit
واجهة التطبيق الرسومية هي كما يلي:

خضعت الواجهة الرسومية لبعض التعديلات:
رقم | النوع | الاسم | الدور |
1 | JTextField | txtConnexion | سلسلة الاتصال بقاعدة البيانات ODBC |
2 | JScrollPane | JScrollPane1 | حاوية لمنطقة النص 3 |
3 | JTextArea | txtStatus | يعرض رسائل الحالة، ولا سيما رسائل الأخطاء |
تتخذ سلسلة تسجيل الدخول المكتوبة في (1) الشكل التالي: DSN;login;password مع
اسم DSN لمصدر البيانات ODBC | |
| هوية مستخدم لديه حق القراءة في قاعدة البيانات |
| كلمة المرور الخاصة به |
تم إنشاء قاعدة البيانات dbimpots يدويًا باستخدام MySQL. يتم تحويلها إلى مصدر البيانات ODBC بالطريقة التالية:
- يتم تشغيل مدير مصادر البيانات ODBC 32 بت

- استخدم الزر [Add] لإضافة مصدر بيانات جديد ODBC

- يتم تحديد برنامج التشغيل MySQL ويتم إجراء [Terminer]

- يطلب برنامج التشغيل MySQL عددًا من المعلومات:
1 | الاسم DSN الذي سيُعطى لمصدر البيانات ODBC - ربما أي اسم |
2 | الجهاز الذي يعمل عليه SGBD MySQL - هنا localhost. من المثير للاهتمام ملاحظة أن قاعدة البيانات قد تكون قاعدة بيانات بعيدة. لن تلاحظ التطبيقات المحلية التي تستخدم مصدر البيانات ODBC ذلك. وهذا ينطبق بشكل خاص على تطبيق Java الخاص بنا. |
3 | قاعدة البيانات MySQL المراد استخدامها. MySQL هي SGBD التي تدير قواعد البيانات العلائقية، وهي مجموعات من الجداول المرتبطة ببعضها البعض عبر علاقات. هنا، نحدد اسم قاعدة البيانات المدارة. |
4 | اسم مستخدم لديه حق الوصول إلى هذه القاعدة |
5 | كلمة مروره |
بمجرد تحديد مصدر البيانات ODBC، يمكننا اختبار برنامجنا:
![]() | ![]() |
دعونا نلقي نظرة على الكود الذي تم تعديله مقارنةً بالإصدار الرسومي الذي لا يستخدم قاعدة بيانات. لنتذكر كود الفئة impots المستخدم حتى الآن:
// إنشاء فئة الضرائب
public class impots{
// البيانات اللازمة لحساب الضريبة
// تأتي من مصدر خارجي
private double[] limites, coeffR, coeffN;
// المنشئ
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// نتحقق من أن الجداول الثلاثة لها نفس الحجم
boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
// حسناً
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//المصنع
// حساب الضريبة
public long calculer(boolean marié, int nbEnfants, int salaire){
// حساب عدد الحصص
double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// حساب الدخل الخاضع للضريبة و"الناتج العائلي"
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// حساب الضريبة
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// إرجاع النتيجة
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//الحساب
}//الفئة
تقوم هذه الفئة بإنشاء الجداول الثلاثة limites و coeffR و coeffN من الجداول الثلاثة التي تم تمريرها كمعلمات إلى منشئها. قررنا إضافة منشئ جديد إليها يسمح بإنشاء الجداول الثلاثة نفسها من قاعدة بيانات:
public impots(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: اسم قاعدة البيانات DSN
// userIMPOTS، mdpIMPOTS: اسم المستخدم/كلمة المرور للوصول إلى قاعدة البيانات
في هذا المثال، قررنا عدم تنفيذ هذا المنشئ الجديد في الفئة impots بل في فئة مشتقة impotsJDBC:
// الحزم المستوردة
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// إضافة مُنشئ يسمح بإنشاء
// الجداول limits و coeffr و coeffn من الجدول
// ضرائب قاعدة بيانات
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: اسم قاعدة البيانات DSN
// userIMPOTS، mdpIMPOTS: اسم المستخدم/كلمة المرور للوصول إلى قاعدة البيانات
// جداول البيانات
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
// الاتصال بقاعدة البيانات
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
// إنشاء كائن Statement
Statement S=connect.createStatement();
// استعلام select
String select="select limites, coeffr, coeffn from impots";
// تنفيذ الاستعلام
ResultSet RS=S.executeQuery(select);
while(RS.next()){
// استخدام السطر الحالي
aLimites.add(RS.getString("limites"));
aCoeffR.add(RS.getString("coeffr"));
aCoeffN.add(RS.getString("coeffn"));
}// السطر التالي
// إغلاق الموارد
RS.close();
S.close();
connect.close();
// نقل البيانات إلى جداول محدودة
int n=aLimites.size();
limites=new double[n];
coeffR=new double[n];
coeffN=new double[n];
for(int i=0;i<n;i++){
limites[i]=Double.parseDouble((String)aLimites.get(i));
coeffR[i]=Double.parseDouble((String)aCoeffR.get(i));
coeffN[i]=Double.parseDouble((String)aCoeffN.get(i));
}//for
}//منشئ
}//فئة
يقوم المُنشئ بقراءة محتوى الجدول impots من قاعدة البيانات التي تم تمريرها إليه كمعلمات ويملأ الجداول الثلاثة limites و coeffR و coeffN. قد تحدث بعض الأخطاء. لا يعالجها المنشئ بل "يرفعها" إلى البرنامج المستدعي:
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
إذا انتبهنا إلى الكود السابق، فسنلاحظ أن الفئة impotsJDBC تستخدم مباشرة الحقول limites، coeffR، coeffN من فئتها الأساسية impots. وقد تم إعلان هذه الحقول على أنها خاصة:
فإن الفئة impotsJDBC لا يمكنها الوصول إلى هذه الحقول مباشرة. لذلك نقوم بإجراء تعديل أولي على الفئة الأساسية بكتابة:
يسمح السمة protected للفئات المشتقة من الفئة impots بالوصول المباشر إلى الحقول المعلنة باستخدام هذه السمة. علينا إجراء تعديل ثانٍ. يتم تعريف منشئ الفئة الفرعية impotsJDBC على النحو التالي:
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
نعلم أنه قبل إنشاء كائن من فئة فرعية، يجب أولاً إنشاء كائن من الفئة الأم. للقيام بذلك، يجب أن يستدعي منشئ الفئة الفرعية صراحةً منشئ الفئة الأم باستخدام تعليمة super(....). لم يتم ذلك هنا لأننا لا نرى أي منشئ من الفئة الأم يمكننا استدعاؤه. لا يوجد سوى منشئ واحد في الوقت الحالي، وهو غير مناسب. سيبحث المُجمِّع إذن في الفئة الأم عن منشئ بدون معلمات يمكنه استدعاؤه. لا يجد أي منشئ، مما يؤدي إلى حدوث خطأ في التجميع. لذلك نضيف منشئًا بدون معلمات إلى فئتنا impots:
ونعلنها "protected" بحيث لا يمكن استخدامها إلا من قبل الفئات الفرعية. أصبح هيكل الفئة impots الآن كما يلي:
public class impots{
// البيانات اللازمة لحساب الضريبة
// تأتي من مصدر خارجي
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// مُنشئ فارغ
protected impots(){}
// الشركة المصنعة
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
...........
}//الصانع
// حساب الضريبة
public long calculer(boolean marié, int nbEnfants, int salaire){
.............
}//حساب
}//الفئة
تصبح إجراءات القائمة Initialiser في تطبيقنا كما يلي:
void mnuInitialiser_actionPerformed(ActionEvent e) {
// يتم استرداد سلسلة الاتصال
Pattern séparateur=Pattern.compile("\\s*;\\s*");
String[] champs=séparateur.split(txtConnexion.getText().trim());
// يلزم وجود ثلاثة حقول
if(champs.length!=3){
// خطأ
txtStatus.setText("Chaîne de connexion (DSN;uid;mdp) incorrecte");
// العودة إلى الواجهة المرئية
txtConnexion.requestFocus();
return;
}//if
// يتم تحميل البيانات
try{
// إنشاء الكائن impotsJDBC
objImpots=new impotsJDBC(champs[0],champs[1],champs[2]);
// تأكيد
txtStatus.setText("Données chargées");
// يمكن تعديل الراتب
txtSalaire.setEditable(true);
// لا يمكن إجراء المزيد من التغييرات
mnuInitialiser.setEnabled(false);
txtConnexion.setEditable(false);
}catch(Exception ex){
// مشكلة
txtStatus.setText("Erreur : " + ex.getMessage());
// نهاية
return;
}//catch
}
بمجرد إنشاء الكائن objImpots، يصبح التطبيق مطابقًا للتطبيق الرسومي الذي تمت كتابته مسبقًا. يُرجى الرجوع إليه.
6.4. تمارين
6.4.1. التمرين 1
توفير واجهة رسومية للبرنامج sql3 السابق.
6.4.2. التمرين 2
لا يمكن لبرنامج Java الصغير الوصول إلى قاعدة البيانات إلا من خلال الخادم الذي تم تحميله منه. في الواقع، نظرًا لأن البرنامج الصغير لا يمكنه الوصول إلى القرص الخاص بالجهاز الذي يتم تشغيله عليه، لا يمكن أن تكون قاعدة البيانات موجودة على جهاز العميل الذي يستخدم البرنامج الصغير. لذلك، نحن في الحالة التالية:

قد تكون الجهاز الذي يقوم بتشغيل التطبيق الصغير، والخادم، والجهاز الذي يحتوي على قاعدة البيانات ثلاثة أجهزة مختلفة. نفترض هنا أن قاعدة البيانات موجودة على الخادم.
المشكلة 1
اكتب باللغة Java التطبيق الخادم التالي:
- يعمل تطبيق الخادم على منفذ يتم تمريره إليه كمعلمة
- عندما يتصل عميل، يرسل تطبيق الخادم الرسالة
- ثم يرسل العميل المعلمات اللازمة للاتصال بقاعدة البيانات والاستعلام الذي يريد تنفيذه بالشكل التالي:
يتم فصل المعلمات بواسطة شرطة مائلة. المعلمات الأربع الأولى هي معلمات البرنامج sql3 الموصوف في هذا الفصل.
- ثم يقوم تطبيق الخادم بإنشاء اتصال بقاعدة البيانات المحددة التي يجب أن تكون موجودة على نفس الجهاز الذي يعمل عليه، ويقوم بتنفيذ الاستعلام SQL عليها. يتم إرجاع النتائج إلى العميل بالشكل التالي:
...
إذا كانت النتيجة ناتجة عن استعلام Select أو
لإرجاع عدد الأسطر المتأثرة باستعلام التحديث. في حالة حدوث خطأ في الاتصال بالقاعدة أو في تنفيذ الاستعلام، ترجع التطبيق
- بمجرد تنفيذ الاستعلام، يقوم تطبيق الخادم بإغلاق الاتصال.
المشكلة 2
أنشئ تطبيق Java صغيرًا يسمح باستعلام الخادم السابق. يمكن الاستعانة بواجهة المستخدم الرسومية للتمرين 1 السابق. نظرًا لأن منفذ الخادم قد يختلف، فسيتم إدخاله في واجهة التطبيق الصغير. وينطبق الأمر نفسه على جميع المعلمات اللازمة لإرسال السطر:
التي يجب على العميل إرسالها إلى الخادم.
6.4.3. التمرين 3
يعرض النص التالي مشكلة كان من المقرر في البداية معالجتها في Visual Basic. قم بتكييفها لمعالجتها في Java في تطبيق صغير. سيعتمد هذا الأخير على الخادم في التمرين 2. يمكن تعديل الواجهة الرسومية لتأخذ في الاعتبار سياق التنفيذ الجديد.
نقترح إنشاء تطبيق يسلط الضوء على مختلف عمليات التحديث الممكنة لجدول في قاعدة بيانات ACCESS. تسمى قاعدة البيانات ACCESS بـ articles.mdb. وتحتوي على جدول واحد باسم articles يسرد المنتجات التي تبيعها الشركة. وهيكلها كما يلي:
الاسم | النوع |
code | رمز المنتج المكون من 4 أحرف |
nom | الاسم (سلسلة أحرف) |
prix | سعره (الفعلي) |
stock_actu | مخزونه الحالي (عدد صحيح) |
stock_mini | الحد الأدنى للمخزون (عدد صحيح) الذي يجب تجديد مخزون المنتج عنده |
نقترح عرض هذه الجدولة وتحديثها من خلال النموذج التالي:

عناصر التحكم في هذا النموذج هي كما يلي:
رقم | النوع | الاسم | الوظيفة |
1 | مربع نص | الملف | رقم البطاقة المعروضة enabled هو false |
2 | مربع نص | الرمز | رمز المنتج |
3 | مربع نص | الاسم | اسم المنتج |
4 | مربع نص | السعر | سعر المنتج |
5 | مربع نص | الحالي | المخزون الحالي للمنتج |
6 | مربع نص | الحد الأدنى | الحد الأدنى لمخزون المنتج |
7 | data | data1 | عنصر التحكم data المرتبط بقاعدة البيانات databasename=مسار ملف articles.mdb مصدر السجلات=articles connect=access |
8 | HScrollBar | position | يسمح بالتنقل داخل الجدول |
9 | زر | OK | يسمح بتأكيد التحديث - لا يظهر إلا عند إجراء التحديث |
10 | زر | إلغاء | يسمح بإلغاء التحديث - لا يظهر إلا أثناء التحديث |
11 | frame | frame1 | للتزيين |
12 | مربع النص | basename | اسم قاعدة البيانات المفتوحة enabled هو false |
13 | مربع نص | sourcename | اسم الجدول المفتوح enabled هو false |
عنصر التحكم | الخصائص |
data1 | تم ملء الحقول databasename و recordsource. يجب أن يشير databasename إلى قاعدة بيانات Access articles.mdb في دليلك وأن يشير recordsource إلى جدول articles. |
code | هذا مربع نص نريد ربطه بحقل code في السجل الحالي لـ data1. للقيام بذلك، نملأ حقلين: datasource: نضع data1 للإشارة إلى أن مربع النص مرتبط بالجدول المرتبط بـ data1 datafield: نختار حقل code من جدول articles بعد هذه العمليات، سيحتوي مربع النص code دائمًا على حقل code للسجل الحالي في data1. وبالعكس، سيؤدي تعديل محتوى مربع النص هذا إلى تعديل حقل code للسجل الحالي. ونفعل الشيء نفسه مع مربعات النص الأخرى |
nom | مصدر البيانات: data1 حقل البيانات: الاسم |
prix | مصدر البيانات: data1 حقل البيانات: السعر |
actuel | مصدر البيانات: data1 حقل البيانات: stock_actu |
minimum | مصدر البيانات: data1 حقل البيانات: stock_mini |
هيكل القوائم هو كما يلي
تحرير | تصفح | خروج |
إضافة | السابق | |
تعديل | التالي | |
حذف | الأول | |
الأخير |
دور الخيارات المختلفة هو كما يلي:
القائمة | name | الوظيفة |
mnuajouter | لإضافة سجل جديد إلى جدول المواد | |
mnusupprimer | لحذف السجل المعروض حاليًا من جدول المنتجات | |
mnumodifier | لتعديل السجل المعروض حاليًا في جدول المواد | |
mnuprecedent | للانتقال إلى السجل السابق | |
mnusuivant | للانتقال إلى السجل التالي | |
السابق | للانتقال إلى السجل الأول | |
mnudernier | للانتقال إلى السجل الأخير | |
mnuquitter | للخروج من التطبيق |
عند حدوث الحدث form_load، يتم فتح الجدول المرتبط بـ data1 (data1.refresh). إذا فشل الفتح، يتم عرض رسالة خطأ ويتم إنهاء البرنامج (end). وإلا، يتم عرض النموذج مع عرض السجل الأول من الجدول articles. لا يمكن إدخال أي بيانات في الحقول (الخاصية enabled على false). لا يمكن الإدخال إلا باستخدام الخيارين "إضافة" و"تعديل". يتم إخفاء الزرين OK و"إلغاء" (visible=false).
نقترح إنشاء الإجراءات المرتبطة بالخيارات المختلفة في القائمة وكذلك بالأزرار OK و"إلغاء". في البداية، سنتجاهل النقاط التالية:
- تمكين/تعطيل بعض خيارات القائمة: على سبيل المثال، يجب تعطيل خيار "التالي" إذا كان المؤشر موضوعًا على آخر سجل في الجدول
- إدارة شريط التمرير الأفقي
القائمة "تصفح/التالي"
- ينتقل إلى السجل التالي (data1.RecordSet.MoveNext) إذا لم نكن في نهاية الملف (data1.RecordSet.EOF). تحديث مربع النص الخاص بالملف (data1.recordset.absoluteposition/ data1.recordset.recordcount).
القائمة "استعراض/السابق"
- ينتقل إلى السجل السابق (data1.RecordSet.MovePrevious) إذا لم نكن في بداية الملف (data1.RecordSet.BOF). يقوم بتحديث مربع النص الخاص بالملف.
القائمة "تصفح/الأول"
- ينتقل إلى السجل الأول (data1.RecordSet.MoveFirst) إذا لم يكن الملف فارغًا (data1.recordset.recordcount=0). يقوم بتحديث مربع النص الخاص بالملف.
القائمة "تصفح/الأخير"
- ينتقل إلى السجل الأخير (data1.RecordSet.MoveLast) إذا لم يكن الملف فارغًا (data1.recordset.recordcount=0). يقوم بتحديث مربع النص الخاص بالملف.
القائمة تحرير/إضافة
- يسمح بإضافة سجل إلى الجدول
- يضعك في وضع إضافة سجل (data1.recordset.addnew)
- يسمح بالإدخال في الحقول الخمسة: الرمز، الاسم، السعر،... (enabled=true)
- يمنع القوائم تحرير، استعراض، خروج (enabled=false)
- يعرض الأزرار OK وإلغاء (visible=true)
زر OK
- يؤكد تعديل السجل (data1.recordset.Update)
- يخفي الأزرار OK و"إلغاء" (visible=false)
- يسمح بخيارات "تحرير" و"استعراض" و"خروج" (enabled=true)
- يحدّث مربع النص الخاص بالملف
زر "إلغاء"
- إلغاء تفعيل تعديل السجل (data1.recordset.CancelUpdate)
- يخفي الأزرار OK و"إلغاء" (visible=false)
- يسمح بخيارات "تحرير" و"استعراض" و"خروج" (enabled=true)
- يحدّث مربع النص الخاص بالملف
القائمة "تحرير/تعديل"
- يسمح بتعديل السجل المعروض في النموذج
- يضع في وضع تحرير السجل (data1.recordset.edit)
- يسمح بالإدخال في الحقول الأربعة: الاسم، السعر،... (enabled=true) ولكن لا يسمح بالإدخال في حقل الرمز (enabled=false)
- يمنع القوائم "تحرير" و"استعراض" و"خروج" (enabled=false)
- يعرض الأزرار OK وإلغاء (visible=true)
القائمة "تحرير/حذف"
- يسمح بحذف (data1.recordset.delete) السجل المعروض من الجدول
- ينتقل إلى السجل التالي (data1.recordset.movenext)
القائمة "خروج"
- يفرغ الورقة (unload me)
الحدث form_unload (cancel as integer)
- يتم تنشيطه بواسطة عملية unload me أو إغلاق النموذج بواسطة Alt-F4 أو النقر المزدوج على مربع النظام، وبالتالي ليس بالضرورة بواسطة خيار الخروج.
- طرح السؤال "هل تريد حقًا الخروج من التطبيق" مع زرين نعم/لا (msgbox مع style=vbyes+vbno)
- إذا كانت الإجابة "لا" (=vbno)، يتم تعيين cancel إلى -1 ويتم الخروج من الإجراء form_unload. يشير cancel إلى -1 إلى رفض إغلاق النافذة.
- إذا كانت الإجابة "نعم" (=vbyes)، يتم إغلاق قاعدة البيانات (data1.recordset.close، data1.database.close).
هنا، نهتم بتفعيل/تعطيل القوائم. بعد كل عملية تغير السجل الحالي، سنستدعي إجراءً يمكن أن نسميه Oueston. سيتحقق هذا الإجراء من الشروط التالية:
. إذا كان الملف فارغًا،
- سنقوم بتعطيل قوائم "استعراض" و"تحرير/حذف"،
- ونسمح بالبقية
. إذا كانت البطاقة الحالية هي البطاقة الأولى،
- يتم تعطيل "استعراض/السابق"
- ويسمح بالباقي
. إذا كانت البطاقة الحالية هي الأخيرة،
- يتم تعطيل "تصفح/التالي"
- السماح بالباقي
يحتوي المتغير الأفقي على ثلاثة حقول مهمة:
- min: قيمته الدنيا
- max: قيمته القصوى
- القيمة: قيمته الحالية
تهيئة المحرك
عند التحميل (form_load)، سيتم تهيئة المحرك المتغير على النحو التالي:
- min=0
- max=data1.recordset.recordcount-1
- القيمة=1
تجدر الإشارة إلى أنه فور فتح قاعدة البيانات (data1.refresh)، يكون عدد السجلات في الجدول الممثل بـ data1.recordset.recordcount غير صحيح. يجب الانتقال إلى نهاية الجدول (MoveLast)، ثم العودة إلى بداية الجدول (MoveFirst) حتى يصبح صحيحًا.
التأثير المباشر على المحرك
يمثل موضع مؤشر المحول الموضع في الجدول.
عندما يقوم المستخدم بتعديل المتغير (المسمى هنا position)، يتم تشغيل الحدث position_change. في هذا الحدث، سنقوم بتغيير السجل الحالي في الجدول بحيث يعكس التغيير الذي تم إجراؤه على المنزلق. للقيام بذلك، سنستخدم الحقل absoluteposition في data1.recordset. عند تعيين القيمة i لهذا الحقل، تصبح البطاقة رقم i في الجدول هي البطاقة الحالية. يتم ترقيم السجلات بدءًا من 0، وبالتالي يكون لها رقم في النطاق [0,data1.recordset.recordcount-1]. في الإجراء position_change، يكفي أن نكتب
حتى تعكس البطاقة الحالية المعروضة على النموذج التغيير الذي تم إجراؤه على المحول.
وبعد ذلك، سنستدعي إجراء Oueston لتحديث القوائم.
تحديث المحول
نظرًا لأن موضع مؤشر المتغير يجب أن يعكس الموضع في الجدول، يجب تحديث قيمة المتغير في كل مرة يحدث فيها تغيير في السجل الحالي في الجدول، ناتج عن أحد القوائم. وبما أن كل قائمة تستدعي إجراء Oueston، فمن الأفضل وضع هذا التحديث أيضًا في هذا الإجراء. يكفي كتابة ما يلي هنا:
نضيف خيار "تصفح/بحث" الذي يهدف إلى السماح للمستخدم بعرض عنصر يحدد رمزه.
عند تنشيط هذا الخيار، تحدث التسلسلات التالية:
- ننتقل إلى "إضافة سجل" (Addnew)، وذلك لغرض وحيد هو عدم تعديل السجل الحالي الذي كنا عليه عند تنشيط الخيار،
- يتم السماح بالإدخال في حقل الرمز ومسح محتوى حقل السجل،
- يتم تعطيل القوائم وعرض الأزرار OK و"إلغاء"
- عندما يضغط المستخدم على OK، يتعين علينا البحث عن السجل المطابق للرمز الذي أدخله المستخدم. لكن الإجراء OK_click يُستخدم بالفعل في خياري "تصفح/إضافة" و"تصفح/تعديل". للتمييز بين هذه الحالات، يتعين علينا إدارة متغير عام سنسميه هنا "الحالة" وسيكون له ثلاث قيم محتملة: "إضافة" و"تعديل" و"بحث". ستستخدم الإجراءات المرتبطة بالأزرار OK و"إلغاء" هذا المتغير لمعرفة السياق الذي يتم استدعاؤها فيه.
- إذا كانت قيمة "الحالة" هي "البحث"، ففي الإجراء المرتبط بـ OK،
- يتم إلغاء عملية addnew (data1.recordset.cancelupdate) لأنه لم يكن هناك نية لإضافة سجل. تجدر الإشارة إلى أن السجل الحالي يعود إلى ما كان موجودًا على الشاشة قبل عملية التصفح/البحث.
- يتم إنشاء معيار البحث المرتبط بالرمز ويتم بدء البحث (data1.recordset.findfirst معيار)،
- إذا فشل البحث (data1.recordset.nomatch=true)، يتم إخطار المستخدم بذلك، ثم نعود إلى وضع الإضافة (addnew) ونخرج من الإجراء OK. سيتعين على المستخدم إعادة كتابة رمز جديد أو اختيار خيار "إلغاء".
- إذا نجح البحث، تصبح السجلات التي تم العثور عليها هي السجلات الحالية الجديدة. يتم إعادة عرض القوائم، وإخفاء الأزرار OK/إلغاء، ومنع الإدخال في حقل الرمز، ثم الخروج من الإجراء.
- . إذا كانت الحالة "بحث"، في الإجراء المرتبط بـ "إلغاء"،
- يتم إلغاء عملية addnew (data1.recordset.cancelupdate). ثم يتم العودة تلقائيًا إلى السجل الحالي قبل عملية "تصفح/بحث".
- إعادة عرض القوائم، وإخفاء الأزرار OK/إلغاء، ومنع الإدخال في حقل الرمز، ثم الخروج من الإجراء.
يجب تحديد كل منتج بشكل فريد بواسطة رمزه. تأكد من أنه في خيار "تصفح/إضافة"، يتم رفض الإضافة إذا كان السجل المراد إضافته يحتوي على رمز منتج موجود بالفعل في الجدول.
6.4.4. التمرين 4
نقدم هنا تطبيق ويب يعتمد على الخادم الخاص بالتمرين 2. إنه تطبيق تجارة إلكترونية في مرحلة مبكرة.
يطلب العميل المنتجات من خلال واجهة الويب التالية:

يمكنه القيام بالعمليات التالية:
- يختار منتجًا من القائمة المنسدلة
- تحديد الكمية المطلوبة
- يؤكد عملية الشراء باستخدام الزر Acheter
- يتم عرض عملية الشراء في قائمة المنتجات المشتراة
- يمكنه حذف منتجات من هذه القائمة، عن طريق تحديد منتج واستخدام الزر Retirer
- عندما يضغط على الزر Bilan، يحصل على النتيجة التالية:

يتيح كشف الحساب للمستخدم الاطلاع على تفاصيل فاتورته. يمكن للمستخدم الوصول إلى تفاصيل عن أحد العناصر المحددة في القائمة المنسدلة باستخدام الزر Informations:

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

قبل تسجيل طلبه، يطلب التطبيق تأكيدًا:

بمجرد تأكيد الطلب، تقوم التطبيق بمعالجته وإرسال صفحة تأكيد:

في الواقع، لا يعالج التطبيق الطلب. بل يكتفي بإرسال بريد إلكتروني إلى المستخدم يطلب منه دفع مبلغ المشتريات:
Cher client,
Vous trouverez ci-dessous le détail de votre commande au magasin SuperPrix. Elle vous sera livrée après réception de votre chèque établi à l'ordre de SuperPrix et à envoyer à l'adresse suivante :
SuperPrix
ISTIA
62 av Notre-Dame du Lac
49000 Angers
France
Nous vous remercions vivement de votre commande
----------------------------------------
Votre commande
----------------------------------------
article, quantité, prix unitaire, total
========================================
vélo, 2, 1202.00 F, 2404.00 F
skis nautiques, 3, 1800.00 F, 5400.00 F
Total à payer : 7804 F
السؤال: قم بإنشاء ما يعادل هذا التطبيق الويب باستخدام تطبيق Java صغير.

