Skip to content

6. إدارة قواعد البيانات باستخدام واجهة برمجة تطبيقات JDBC

6.1. نظرة عامة

هناك العديد من قواعد البيانات في السوق. لتوحيد الوصول إلى قواعد البيانات في نظام MS Windows، طورت Microsoft واجهة تسمى ODBC (Open DataBase Connectivity). تخفي هذه الطبقة الميزات الخاصة بكل قاعدة بيانات خلف واجهة قياسية. هناك العديد من برامج تشغيل ODBC المتاحة في نظام MS Windows والتي تسهل الوصول إلى قواعد البيانات. فيما يلي، على سبيل المثال، قائمة ببرامج تشغيل ODBC المثبتة على جهاز يعمل بنظام Win95:

Image

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

Image

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

Image

6.2. الخطوات الرئيسية في عمليات قاعدة البيانات

6.2.1. مقدمة

في تطبيق Java يستخدم قاعدة بيانات مع واجهة JDBC، تتضمن العملية عمومًا الخطوات التالية:

  1. الاتصال بقاعدة البيانات
  2. إرسال استعلامات SQL إلى قاعدة البيانات
  3. استلام ومعالجة نتائج هذه الاستعلامات
  4. إغلاق الاتصال

يتم تنفيذ الخطوتين 2 و 3 بشكل متكرر، ولا يتم إغلاق الاتصال إلا في نهاية عمليات قاعدة البيانات. هذا نمط قياسي نسبيًا قد تكون على دراية به إذا كنت قد استخدمت قاعدة بيانات تفاعلية من قبل. سنفصل كل خطوة من هذه الخطوات باستخدام مثال. سنأخذ قاعدة بيانات Access باسم ARTICLES ذات البنية التالية:

name
النوع
الرمز
رمز العنصر المكون من 4 أحرف
الاسم
اسمه (سلسلة)
السعر
سعره (الفعلي)
المخزون_الحالي
المخزون الحالي (عدد صحيح)
الحد_الأدنى_للمخزون
الحد الأدنى للمخزون (عدد صحيح) الذي يجب تجديد المخزون عند انخفاضه إلى ما دونه

يتم تعريف قاعدة بيانات ACCESS هذه كمصدر بيانات "مستخدم" في مدير قاعدة بيانات ODBC:

Image

Image

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

Image

يتضمن هذا التكوين بشكل أساسي ربط قاعدة بيانات ARTICLES بملف Access articles.mdb المطابق لهذه القاعدة. وبمجرد الانتهاء من ذلك، تصبح قاعدة بيانات ARTICLES متاحة للتطبيقات التي تستخدم واجهة ODBC.

6.2.2. خطوة الاتصال

لاستخدام قاعدة بيانات، يجب على تطبيق Java أولاً إنشاء اتصال. ويتم ذلك باستخدام طريقة الفئة التالية:

Connection DriverManager.getConnection(String URL, String id, String mdp)

حيث

DriverManager: فئة Java تحتوي على قائمة برامج التشغيل المتاحة للتطبيق

Connection: فئة Java تنشئ رابطًا بين التطبيق وقاعدة البيانات، والتي من خلالها سيرسل التطبيق استعلامات SQL إلى قاعدة البيانات ويتلقى النتائج

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

    **<bdi dir="ltr" class="odt-ltr-term">jdbc:driver</bdi>\_name:<bdi dir="ltr" class="odt-ltr-term">source</bdi>\_name;<bdi dir="ltr" class="odt-ltr-term">param</bdi>=<bdi dir="ltr" class="odt-ltr-term">val1;param2</bdi>=<bdi dir="ltr" class="odt-ltr-term">val2</bdi>**

في أمثلةنا، حيث سنستخدم برامج تشغيل <bdi dir="ltr" class="odt-ltr-term">ODBC</bdi> فقط، يُسمى برنامج التشغيل **<bdi dir="ltr" class="odt-ltr-term">odbc</bdi>**. يتكون الجزء الثالث من عنوان <bdi dir="ltr" class="odt-ltr-term">URL</bdi> من اسم المصدر وأي معلمات. في أمثلةنا، ستكون هذه مصادر <bdi dir="ltr" class="odt-ltr-term">ODBC</bdi> معروفة للنظام. وبالتالي، سيكون عنوان <bdi dir="ltr" class="odt-ltr-term">URL</bdi> لمصدر بيانات *<bdi dir="ltr" class="odt-ltr-term">Articles</bdi>* المحدد سابقًا هو

    **<bdi dir="ltr" class="odt-ltr-term">jdbc:odbc:Articles</bdi>**

id: معرف المستخدم (تسجيل الدخول)

mdp: كلمة مرور المستخدم

باختصار، يتصل البرنامج بقاعدة بيانات:

  • يتم تحديده بواسطة اسم (URL)
  • بمعلومات اعتماد المستخدم (المعرف، كلمة المرور)

إذا كانت هذه المعلمات الثلاثة صحيحة، وإذا كان برنامج التشغيل القادر على إنشاء الاتصال بين تطبيق Java وقاعدة البيانات المحددة موجودًا، يتم إنشاء اتصال بين تطبيق Java وقاعدة البيانات. يتم تمثيل هذا الاتصال في البرنامج بواسطة كائن Connection الذي ترجع فئة DriverManager. نظرًا لأن هذا الاتصال قد يفشل لأسباب مختلفة، فقد يرمي استثناءً. لذلك سنكتب:


    Connection connexion=null;
    URL base=...;
    String id=...;
    String mdp=...;
try{
        connexion=DriverManager.getConnection(base,id,mdp);
} catch (Exception e){
        // traiter l’exception
}

للاتصال بقاعدة بيانات، يجب أن يكون لديك برنامج التشغيل المناسب. في أمثلةنا، سيكون هذا هو برنامج تشغيل ODBC القادر على التعامل مع قاعدة البيانات المطلوبة. في حين يجب أن يكون برنامج التشغيل هذا متاحًا في قائمة برامج تشغيل ODBC على الجهاز، يجب أن يكون لديك أيضًا فئة Java التي ستتفاعل معه. للقيام بذلك، سيطلب التطبيق الفئة اللازمة على النحو التالي:

    Class.forName(String nomClasse)

لا ترتبط فئة Class بأي شكل من الأشكال بواجهة JDBC. إنها فئة عامة لإدارة الفئات. تسمح طريقتها الثابتة forName بتحميل فئة ديناميكيًا، مما يجعل سماتها وطرقها الثابتة متاحة. تسمى الفئة التي تتفاعل مع برامج تشغيل MS Windows ODBC "sun.jdbc.odbc.JdbcOdbcDriver". لذلك سنكتب (قد ترمي الطريقة استثناءً):

try{
    Class.forName(« sun.jdbc.odbc.JdbcOdbcDriver ») ;
} catch (Exception e){
    // handle exception (non-existent class)
}

توجد الفئات المطلوبة لواجهة JDBC في حزمة java.sql. ولذلك، نكتب في بداية البرنامج:

    import java.sql.*;

فيما يلي برنامج يتيح لك الاتصال بقاعدة بيانات:

import java.sql.*;
import java.io.*;

// call: pg PILOTE URL UID MDP
// connects to the URL database using class JDBC PILOTE
// user UID is identified by password MDP

public class connexion1{
    static String syntaxe="pg PILOTE URL UID MDP";

    public static void main(String arg[]){
        // check number of arguments
        if(arg.length<2 || arg.length>4)
            erreur(syntaxe,1);
        // connection to base
        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);
        }
        // closing the base
        try{
            connect.close();
            System.out.println("Base " + arg[1] + " fermée");
        } catch (Exception e){}
    }// hand

    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }
}// class    

فيما يلي مثال على التنفيذ:

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. إذا كان connection هو الكائن الذي يمثل الاتصال بقاعدة البيانات، يتم الحصول على كائن Statement على النحو التالي:

    Statement requete=connexion.CreateStatement();

بمجرد الحصول على كائن Statement، يمكن تنفيذ استعلامات SQL. ويتم ذلك بطرق مختلفة اعتمادًا على ما إذا كان الاستعلام يهدف إلى استرداد البيانات أو تحديث قاعدة البيانات.

6.2.3.2. تنفيذ استعلام لاسترداد البيانات من قاعدة البيانات

عادةً ما يكون الاستعلام من النوع التالي:

    select col1, col2,... from table1, table2,...
    where condition
    order by expression
    ...

الكلمات الرئيسية في السطر الأول هي فقط المطلوبة؛ أما البقية فهي اختيارية. وهناك كلمات رئيسية أخرى غير موضحة هنا.

  1. يتم إجراء عملية ربط مع جميع الجداول المدرجة بعد الكلمة الرئيسية `FROM`
  2. يتم الاحتفاظ فقط بالأعمدة التي تلي الكلمة الرئيسية `select`
  3. يتم الاحتفاظ فقط بالصفوف التي تستوفي شرط الكلمة الرئيسية `where`
  4. تشكل الصفوف الناتجة، المرتبة وفقًا للتعبير الوارد في الكلمة الرئيسية `ORDER BY`، نتيجة الاستعلام.

نتيجة SELECT هي جدول. إذا أخذنا في الاعتبار جدول ARTICLES السابق وأردنا الحصول على أسماء العناصر التي يقل مخزونها الحالي عن الحد الأدنى، فسنكتب: SELECT name FROM articles WHERE current\_stock &lt; min\_stock*. وإذا أردنا فرزها أبجديًا حسب الاسم، فسنكتب: select name from articles where current_stock < minimum_stock order by name*

لتنفيذ هذا النوع من الاستعلامات، توفر فئة Statement طريقة executeQuery:

    ResultSet executeQuery(String requête)

حيث query هو نص استعلام SELECT المراد تنفيذه.

وبالتالي، إذا

  • كان connection هو الكائن الذي يمثل الاتصال بقاعدة البيانات
  • Statement s = connection.createStatement() ينشئ كائن Statement المطلوب لتنفيذ استعلامات SQL
  • تنفيذ الأمر ResultSet rs = s.executeQuery("select name from articles where current_stock < minimum_stock") يؤدي إلى تنفيذ استعلام SELECT وتعيين صفوف نتائج الاستعلام إلى كائن من نوع ResultSet.

6.2.3.3. فئة ResultSet: نتيجة استعلام SELECT

يمثل كائن ResultSet جدولًا، أي مجموعة من الصفوف والأعمدة. في أي وقت معين، لا يمكن الوصول إلا إلى صف واحد من الجدول، يُعرف بالصف الحالي. عند الإنشاء الأولي لـ ResultSet، يكون الصف الحالي هو الصف رقم 1 إذا لم يكن ResultSet فارغًا. للانتقال إلى الصف التالي، توفر فئة ResultSet الطريقة التالية:

    boolean next()

تحاول هذه الطريقة الانتقال إلى الصف التالي في ResultSet وتُرجع true في حالة النجاح، و false في حالة الفشل. في حالة النجاح، يصبح الصف التالي هو الصف الحالي الجديد. يتم فقدان الصف السابق ولا يمكن استرداده. يحتوي جدول ResultSet على الأعمدة col1 و col2 و... للوصول إلى الحقول المختلفة للصف الحالي، تتوفر الطرق التالية:

Type getType("coli") 

لاسترداد حقل "coli" من الصف الحالي. يشير Type إلى نوع حقل coli. غالبًا ما تُستخدم طريقة getString على جميع الحقول، مما يسمح لك باسترداد محتوى الحقل كسلسلة. يمكنك بعد ذلك تحويلها إذا لزم الأمر. إذا كنت لا تعرف اسم العمود، يمكنك استخدام الطرق

Type getType(i) 

حيث i هو مؤشر العمود المطلوب (i>=1).

6.2.3.4. مثال أول

فيما يلي برنامج يعرض محتويات قاعدة البيانات ARTICLES التي تم إنشاؤها سابقًا:

import java.sql.*;
import java.io.*;

// displays the contents of a ARTICLES system database

public class articles1{

    static final String DB="ARTICLES";        // database to exploit

    public static void main(String arg[]){

        Connection connect=null;        // connection to base
        Statement S=null;                // purpose of queries
        ResultSet RS=null;            // query result table
        try{
            // base connection
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // creation of a Statement object
            S=connect.createStatement();
            // execute a select query
            RS=S.executeQuery("select * from " + DB);
            // using the results table
            while(RS.next()){                // as long as there's a line to operate
                // it is displayed on screen
                System.out.println(RS.getString("code")+","+
                    RS.getString("nom")+","+
                    RS.getString("prix")+","+
                    RS.getString("stock_actu")+","+
                    RS.getString("stock_mini"));
            }// next line
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // closing the base
        try{
            connect.close();
            System.out.println("Base " + DB + " fermée");
        } catch (Exception e){}
    }// hand

    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }
}// class    

والنتائج هي كما يلي:

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(column_name). بدلاً من ذلك، نستخدم getType(column_number). ومع ذلك، للحصول على جميع الأعمدة، سنحتاج إلى معرفة عدد الأعمدة الموجودة في ResultSet. لا توفر فئة ResultSet هذه المعلومات. بل إن فئة ResultSetMetaData هي التي توفرها. بشكل عام، توفر هذه الفئة معلومات حول بنية الجدول، أي طبيعة أعمدة الجدول.

نصل إلى المعلومات المتعلقة ببنية ResultSet عن طريق إنشاء مثيل لكائن ResultSetMetaData أولاً. إذا كان RS هو ResultSet، يتم الحصول على ResultSetMetaData المرتبط به عن طريق:

    RS.getMetaData()

توجد طريقتان مفيدتان في فئة ResultSetMetaData:

  1. int getColumnCount()، التي تُرجع عدد الأعمدة في ResultSet
  2. String getColumnLabel(int iوالتي تُرجع اسم العمود i في ResultSet (i >= 1)

6.2.3.6. مثال ثانٍ

عرض البرنامج السابق محتويات قاعدة بيانات ARTICLES. هنا، نكتب برنامجًا ينفذ أي استعلام SQL SELECT يكتبه المستخدم على لوحة المفاتيح على قاعدة بيانات ARTICLES.

import java.sql.*;
import java.io.*;

// displays the contents of a ARTICLES system database

public class sql1{

    static final String DB="ARTICLES";        // database to exploit

    public static void main(String arg[]){

        Connection connect=null;        // connection to base
        Statement S=null;                // purpose of queries
        ResultSet RS=null;            // query result table
        String select;                    // query text SQL select
        int nbColonnes;                // no. of columns in ResultSet

        // creation of a keyboard input stream
        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{
            // base connection
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // creation of a Statement object
            S=connect.createStatement();
            // execution loop for SQL requests typed on keyboard
            System.out.print("Requête : ");
            select=in.readLine();
            while(!select.equals("fin")){
                // query execution
                RS=S.executeQuery(select);
                // number of columns
                nbColonnes=RS.getMetaData().getColumnCount();
                // using the results table
                System.out.println("Résultats obtenus\n\n");
                while(RS.next()){                // as long as there's a line to operate
                    // it is displayed on screen
                    for(int i=1;i<nbColonnes;i++)
                        System.out.print(RS.getString(i)+",");
                    System.out.println(RS.getString(nbColonnes));
                }// next line
                // following request
                System.out.print("Requête : ");
                select=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // closing the base and input stream
        try{
            connect.close();
            System.out.println("Base " + DB + " fermée");
            in.close();
        } catch (Exception e){}
    }// hand

    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }
}// class    

فيما يلي بعض النتائج التي تم التوصل إليها:

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:

    int executeUpdate(String requête)

يكمن الاختلاف في النتيجة: في حين أن executeQuery كانت تُرجع مجموعة النتائج (ResultSet)، فإن executeUpdate تُرجع عدد الصفوف التي تأثرت بعملية التحديث.

6.2.3.8. مثال ثالث

سنعود إلى البرنامج السابق ونعدله قليلاً: أصبحت الاستعلامات التي يتم كتابتها على لوحة المفاتيح الآن استعلامات تحديث لقاعدة بيانات ARTICLES.

import java.sql.*;
import java.io.*;

// displays the contents of a ARTICLES system database

public class sql2{

    static final String DB="ARTICLES";    // database to exploit

    public static void main(String arg[]){

        Connection connect=null;        // connection to base
        Statement S=null;                // purpose of queries
        ResultSet RS=null;            // query result table
        String sqlUpdate;                // text of SQL update request
        int nbLignes;                    // no. of lines affected by an update

        // creation of a keyboard input stream
        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{
            // base connection
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connect=DriverManager.getConnection("jdbc:odbc:"+DB,"","");
            System.out.println("Connexion avec la base " + DB + " établie");
            // creation of a Statement object
            S=connect.createStatement();
            // execution loop for SQL requests typed on keyboard
            System.out.print("Requête : ");
            sqlUpdate=in.readLine();
            while(!sqlUpdate.equals("fin")){
                // query execution
                nbLignes=S.executeUpdate(sqlUpdate);
                // follow-up
                System.out.println(nbLignes + " ligne(s) ont été mises à jour");
                // following request
                System.out.print("Requête : ");
                sqlUpdate=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // closing the base and input stream
        try{
            // free up resources linked to the base
            RS.close();
            S.close();
            connect.close();
            System.out.println("Base " + DB + " fermée");
            // keyboard flow closure
            in.close();
        } catch (Exception e){}
    }// hand

    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }
}// class    

فيما يلي نتائج عدة عمليات تشغيل لبرنامجي 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:

    boolean execute(String requête)

القيمة المرجعة هي القيمة المنطقية true إذا أعاد الاستعلام ResultSet* (executeQuery*) ، وfalse إذا أعاد رقمًا (executeUpdate*). يمكن استرداد ResultSet الناتج باستخدام طريقة getResultSet ، وعدد الصفوف المحدثة باستخدام طريقة getUpdateCount*. وبالتالي، سنكتب:


Statement S=...;
ResultSet RS=...;
int nbLignes;
String requête=...;
// exécution d’une requête SQL
if (S.execute(requête)){
    // on a un resultset
    RS=S.getResultSet();
    // exploitation du ResultSet
    ...
} else {
    // c’était une requête de mise à jour
    nbLignes=S.getUpdateCount();
    ...
}

6.2.3.10. المثال الرابع

نأخذ المفهوم من البرنامجين sql1 و sql2 ونطبقه على برنامج sql3 الذي يمكنه الآن تنفيذ أي استعلام SQL يتم كتابته على لوحة المفاتيح. ولجعل البرنامج أكثر عمومية، يتم تمرير خصائص قاعدة البيانات المراد استخدامها كمعلمات إلى البرنامج.

import java.sql.*;
import java.io.*;

// call: pg PILOTE URL UID MDP
// connects to the URL database using class JDBC PILOTE
// user UID is identified by password MDP

public class sql3{

    static String syntaxe="pg PILOTE URL UID MDP";

    public static void main(String arg[]){



        // check number of arguments
        if(arg.length<2 || arg.length>4)
            erreur(syntaxe,1);

        // init connection parameters
        Connection connect=null;
        String uid="";
        String mdp="";
        if(arg.length>=3) uid=arg[2];
        if(arg.length==4) mdp=arg[3];        

        // other data
        Statement S=null;                        // purpose of queries
        ResultSet RS=null;                    // result table of a query request
        String sqlText;                            // text of query SQL to be executed
        int nbLignes;                                // no. of lines affected by an update
        int nbColonnes;                            // nb of columns in a ResultSet

        // creation of a keyboard input stream
        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{
            // base connection
            Class.forName(arg[0]);
            connect=DriverManager.getConnection(arg[1],uid,mdp);
            System.out.println("Connexion avec la base " + arg[1] + " établie");
            // creation of a Statement object
            S=connect.createStatement();
            // execution loop for SQL requests typed on keyboard
            System.out.print("Requête : ");
            sqlText=in.readLine();
            while(!sqlText.equals("fin")){
                // query execution
                try{
                    if(S.execute(sqlText)){
                        // we have obtained a ResultSet - we exploit it
                        RS=S.getResultSet();
                        // number of columns
                        nbColonnes=RS.getMetaData().getColumnCount();
                        // using the results table
                        System.out.println("\nRésultats obtenus\n-----------------\n");
                        while(RS.next()){                // as long as there's a line to operate
                            // it is displayed on screen
                            for(int i=1;i<nbColonnes;i++)
                                System.out.print(RS.getString(i)+",");
                            System.out.println(RS.getString(nbColonnes));
                        }// next line of ResultSet
                    } else {
                        // it was an update request
                        nbLignes=S.getUpdateCount();
                        // follow-up
                        System.out.println(nbLignes + " ligne(s) ont été mises à jour");
                    }//if
                } catch (Exception e){
                    System.out.println("Erreur " +e);
                }
                // following request
                System.out.print("\nNouvelle Requête : ");
                sqlText=in.readLine();                
            }// while
        } catch (Exception e){
            erreur("Erreur " + e,2);
        }
        // closing the base and input stream
        try{
            // free up resources linked to the base
            RS.close();
            S.close();
            connect.close();
            System.out.println("Base " + arg[1] + " fermée");
            // keyboard flow closure
            in.close();
        } catch (Exception e){}
    }// hand

    public static void erreur(String msg, int exitCode){
        System.err.println(msg);
        System.exit(exitCode);
    }
}// class    

نقوم بإنشاء ملف الاستعلام التالي:

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

يتم تشغيل البرنامج على النحو التالي:

E:\data\java\jdbc\0>java sql3 sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles <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 é 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 é 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 é 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. حساب الضرائب باستخدام قاعدة بيانات

في المرة الأخيرة التي تناولنا فيها مشكلة حساب الضرائب، استخدمنا واجهة رسومية وتم تخزين البيانات في ملف. سنعيد النظر في هذه النسخة، مع افتراض أن البيانات موجودة في قاعدة بيانات ODBC-MySQL. MySQL هو نظام إدارة قواعد البيانات مفتوح المصدر يمكن استخدامه على منصات مختلفة، بما في ذلك Windows وLinux. باستخدام نظام إدارة قواعد البيانات هذا، تم إنشاء قاعدة بيانات باسم 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

الواجهة الرسومية للتطبيق هي كما يلي:

Image

خضعت الواجهة الرسومية لبعض التغييرات:

لا.
النوع
الاسم
الدور
1
JTextField
txtConnection
سلسلة اتصال قاعدة بيانات ODBC
2
JScrollPane
JScrollPane1
حاوية لمنطقة النص (Textarea) 3
3
JTextArea
txtStatus
يعرض رسائل الحالة، بما في ذلك رسائل الخطأ

سلسلة الاتصال التي تم إدخالها في (1) لها التنسيق التالي: DSN;login;password مع

DSN
هو اسم DSN لمصدر بيانات ODBC
login
هو هوية مستخدم لديه حق الوصول للقراءة إلى قاعدة البيانات
كلمة
كلمة المرور الخاصة به

تم إنشاء قاعدة بيانات dbimpots يدويًا باستخدام MySQL. يتم تحويلها إلى مصدر بيانات ODBC على النحو التالي:

  • قم بتشغيل "مدير مصدر بيانات ODBC" 32 بت

Image

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

Image

  • حدد برنامج تشغيل MySQL وانقر على [Finish]

Image

  • يطلب برنامج تشغيل MySQL بعض المعلومات:
1
اسم DSN الذي سيتم إعطاؤه لمصدر بيانات ODBC — يمكن أن يكون أي اسم
2
الجهاز الذي يعمل عليه نظام إدارة قواعد البيانات MySQL — هنا، localhost. تجدر الإشارة إلى أن قاعدة البيانات قد تكون قاعدة بيانات بعيدة. ولن تلاحظ التطبيقات المحلية التي تستخدم مصدر بيانات ODBC ذلك. وينطبق هذا بشكل خاص على تطبيق Java الخاص بنا.
3
قاعدة بيانات MySQL المراد استخدامها. MySQL هو نظام إدارة قواعد البيانات (DBMS) الذي يدير قواعد البيانات العلائقية، وهي مجموعات من الجداول المرتبطة ببعضها البعض من خلال علاقات. هنا، نحدد اسم قاعدة البيانات التي يتم إدارتها.
4
اسم المستخدم الذي لديه حقوق الوصول إلى قاعدة البيانات هذه
5
كلمة المرور الخاصة به

بمجرد تحديد مصدر بيانات ODBC، يمكننا اختبار برنامجنا:

 

دعونا نلقي نظرة على الكود الذي تم تعديله مقارنةً بالإصدار الرسومي الذي لا يحتوي على قاعدة بيانات. فيما يلي كود فئة *impots* المستخدم حتى الآن:

// creation of an impots class

public class impots{

    // data required for tax calculation
     // come from an external source

    private double[] limites, coeffR, coeffN;

     // manufacturer
    public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
         // check that the 3 arrays have the same size
        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+")");
        // it's good
        this.limites=LIMITES;
        this.coeffR=COEFFR;
        this.coeffN=COEFFN;
    }//manufacturer

     // tAX CALCULATION
    public long calculer(boolean marié, int nbEnfants, int salaire){
         // calculating the number of shares
        double nbParts;
        if (marié) nbParts=(double)nbEnfants/2+2;
        else nbParts=(double)nbEnfants/2+1;
        if (nbEnfants>=3) nbParts+=0.5;
         // calculation of taxable income & family quota
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // tAX CALCULATION
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // return result
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//calculate
}//class

تقوم هذه الفئة بإنشاء المصفوفات الثلاثة limits و coeffR و coeffN من ثلاث مصفوفات يتم تمريرها كمعلمات إلى منشئها. وقد قررنا إضافة منشئ جديد يتيح لنا إنشاء المصفوفات الثلاث نفسها من قاعدة بيانات:

  public impots(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: DSN database name
     // userIMPOTS, mdpIMPOTS: database login/password

في هذا المثال، قررنا عدم تنفيذ هذا المنشئ الجديد في فئة *impots* بل في فئة مشتقة، وهي *impotsJDBC*:

// imported packages
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // addition of a constructor for building
   // limit tables, coeffr, coeffn from table
   // database taxes
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: DSN database name
     // userIMPOTS, mdpIMPOTS: database login/password

     // data tables
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // connection to base
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // creation of a Statement object
    Statement S=connect.createStatement();
    // select request
    String select="select limites, coeffr, coeffn from impots";
    // query execution
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // running line operation
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// next line
     // closing resources
    RS.close();
    S.close();
    connect.close();
     // data transfer to bounded arrays
    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
  }//manufacturer
}//class

يقوم المنشئ بقراءة محتويات جدول impots من قاعدة البيانات التي تم تمريرها إليه كمعلمات، ويملأ المصفوفات الثلاث: limites و coeffR و coeffN. وقد تحدث بعض الأخطاء. ولا يتعامل المنشئ معها، بل «يحيلها» إلى البرنامج المستدعي:

  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

إذا نظرنا عن كثب إلى الكود السابق، يمكننا أن نرى أن فئة impotsJDBC تستخدم مباشرة حقول limits و coeffR و coeffN الخاصة بفئتها الأساسية impots. وبما أن هذه الحقول مُعلنة على أنها خاصة:

    private double[] limites, coeffR, coeffN;

فإن فئة impotsJDBC لا تملك وصولاً مباشراً إلى هذه الحقول. لذلك نقوم بإجراء تعديل أولي على الفئة الأساسية بكتابة:

  protected double[] limites=null;
  protected double[] coeffR=null;
  protected double[] coeffN=null;

تسمح السمة protected للفئات المشتقة من فئة impots بالوصول المباشر إلى الحقول المعلنة بهذه السمة. نحتاج إلى إجراء تعديل ثانٍ. يتم إعلان منشئ الفئة الفرعية impotsJDBC على النحو التالي:

  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

نعلم أنه قبل إنشاء كائن لفئة فرعية، يجب أولاً إنشاء كائن للفئة الأصلية. للقيام بذلك، يجب أن تستدعي منشئة الفئة الفرعية صراحةً منشئة الفئة الأصلية باستخدام عبارة super(....). هنا، لم يتم ذلك لأننا لا نستطيع تحديد أي منشئة من الفئة الأصلية يمكننا استدعاؤها. يوجد حاليًا منشئ واحد فقط، وهو غير مناسب. سيقوم المُجمع بعد ذلك بالبحث في الفئة الأم عن منشئ بدون معلمات يمكنه استدعاءه. لا يجد المُجمع منشئًا، وهذا يؤدي إلى حدوث خطأ في التجميع. لذلك نضيف منشئًا بدون معلمات إلى فئة impots الخاصة بنا:

  // constructeur vide
  protected impots(){}

نعلنها على أنها "محمية" بحيث لا يمكن استخدامها إلا من قبل الفئات الفرعية. يبدو هيكل فئة impots الآن كما يلي:

public class impots{

  // data required for tax calculation
   // come from an external source

  protected double[] limites=null;
  protected double[] coeffR=null;
  protected double[] coeffN=null;

   // empty builder
  protected impots(){}

   // manufacturer
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
...........
  }//manufacturer

   // tAX CALCULATION
  public long calculer(boolean marié, int nbEnfants, int salaire){
.............
    }//calculate
}//class

يصبح الإجراء الخاص بقائمة Initialize في تطبيقنا كما يلي:

  void mnuInitialiser_actionPerformed(ActionEvent e) {
    // retrieve the connection string
    Pattern séparateur=Pattern.compile("\\s*;\\s*");
    String[] champs=séparateur.split(txtConnexion.getText().trim());
     // three fields are required
    if(champs.length!=3){
      // error
      txtStatus.setText("Chaîne de connexion (DSN;uid;mdp) incorrecte");
       // back to visual interface
      txtConnexion.requestFocus();
      return;
    }//if
     // load data
    try{
       // creation of impotsJDBC object
      objImpots=new impotsJDBC(champs[0],champs[1],champs[2]);
       // confirmation
      txtStatus.setText("Données chargées");
       // salary can be modified
      txtSalaire.setEditable(true);
       // no more chgt possible
      mnuInitialiser.setEnabled(false);
      txtConnexion.setEditable(false);
    }catch(Exception ex){
       // problem
      txtStatus.setText("Erreur : " + ex.getMessage());
      // end
      return;
    }//catch
  }

بمجرد إنشاء الكائن objImpots، يصبح التطبيق مطابقًا للتطبيق الرسومي الذي تمت كتابته مسبقًا. ندعو القارئ إلى الرجوع إليه.

6.4. تمارين

6.4.1. التمرين 1

قم بتوفير واجهة رسومية لبرنامج sql3 السابق.

6.4.2. التمرين 2

لا يمكن لبرنامج Java الصغير الوصول إلى قاعدة البيانات إلا من خلال الخادم الذي تم تحميله منه. ونظرًا لأن البرنامج الصغير لا يملك حق الوصول إلى القرص الخاص بالجهاز الذي يعمل عليه، فلا يمكن أن تكون قاعدة البيانات موجودة على جهاز العميل الذي يستخدم البرنامج الصغير. وبالتالي، فإننا في الحالة التالية:

Image

قد يكون الجهاز الذي يعمل عليه التطبيق الصغير، والخادم، والجهاز الذي يستضيف قاعدة البيانات ثلاثة أجهزة مختلفة. هنا، نفترض أن قاعدة البيانات موجودة على الخادم.

المشكلة 1

اكتب تطبيق الخادم التالي بلغة Java:

  • يعمل تطبيق الخادم على منفذ يتم تمريره إليه كمعلمة
  • عندما يتصل عميل، يرسل تطبيق الخادم الرسالة
200 - Bienvenue - Envoyez votre requête
  • ثم يرسل العميل المعلمات اللازمة للاتصال بقاعدة البيانات والاستعلام الذي يريد تنفيذه بالصيغة التالية:
Pilote Java/URL base/UID/MDP/requête SQL

يتم فصل المعلمات بشرطة مائلة. المعلمات الأربع الأولى هي تلك الخاصة ببرنامج sql3 الموصوف في هذا الفصل.

  • ثم يقوم تطبيق الخادم بإنشاء اتصال بقاعدة البيانات المحددة، والتي يجب أن تكون موجودة على نفس الجهاز الذي يوجد عليه الخادم، ويقوم بتنفيذ استعلام SQL عليها. يتم إرجاع النتائج إلى العميل بالتنسيق التالي:
100 - ligne1
100 - ligne2

...

إذا كانت النتيجة من استعلام Select، أو

101 - nbLignes

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

500 - Message d’erreur
  • بمجرد تنفيذ الاستعلام، يقوم تطبيق الخادم بإغلاق الاتصال.

المشكلة 2

أنشئ تطبيق Java صغير يستعلم عن الخادم الموصوف أعلاه. يمكنك الاستلهام من الواجهة الرسومية في التمرين 1. ونظرًا لأن منفذ الخادم قد يختلف، فسيتم إدخاله عبر واجهة التطبيق الصغير. وينطبق الأمر نفسه على جميع المعلمات المطلوبة لإرسال الصف:

Pilote Java/URL base/UID/MDP/requête SQL

التي يجب على العميل إرسالها إلى الخادم.

6.4.3. التمرين 3

يصف النص التالي مشكلة كان من المفترض في الأصل حلها في Visual Basic. قم بتكييفها للمعالجة في Java داخل تطبيق صغير. سيعتمد هذا التطبيق الصغير على الخادم من التمرين 2. يمكن تعديل الواجهة الرسومية لتتناسب مع سياق التنفيذ الجديد.

نقترح إنشاء تطبيق يسلط الضوء على مختلف عمليات التحديث الممكنة على جدول في قاعدة بيانات ACCESS. تسمى قاعدة بيانات ACCESS articles.mdb. وتحتوي على جدول واحد يسمى articles يسرد العناصر التي تبيعها شركة ما. وهيكلها كما يلي:

name
النوع
الرمز
رمز العنصر المكون من 4 أحرف
الاسم
اسمه (سلسلة)
السعر
سعره (الفعلي)
المخزون_الحالي
المخزون الحالي (عدد صحيح)
الحد_الأدنى_للمخزون
الحد الأدنى للمخزون (عدد صحيح) الذي يجب تجديد المخزون عند انخفاضه إلى ما دونه

نقترح عرض هذا الجدول وتحديثه باستخدام النموذج التالي:

Image

العناصر في هذا النموذج هي كما يلي:

رقم
النوع
الاسم
الوظيفة
1
مربع نص
السجل
رقم السجل المعروض
القيمة "false"
2
مربع نص
الرمز
رمز العنصر
3
مربع نص
الاسم
اسم العنصر
4
مربع نص
السعر
سعر العنصر
5
مربع نص
المخزون
المخزون الحالي للمنتج
6
مربع نص
الحد الأدنى
الحد الأدنى لمخزون المنتج
7
البيانات
البيانات 1
ضبط البيانات المرتبطة بقاعدة البيانات
databasename=مسار ملف articles.mdb
مصدر السجلات=articles
connect=access
8
HScrollBar
الموضع
يسمح لك بالتنقل في الجدول
9
زر
موافق
يسمح لك بتأكيد التحديث - يظهر فقط أثناء التحديث
10
زر
إلغاء
يسمح لك بإلغاء التحديث - يظهر فقط أثناء التحديث
11
الإطار
الإطار 1
لأغراض جمالية
12
مربع النص
الاسم الأساسي
اسم قاعدة البيانات المفتوحة
تم تعيين enabled على false
13
مربع نص
sourcename
اسم الجدول المفتوح
تم تعيين enabled على false

إنشاء الورقة في VB

عنصر التحكم
الميزات الخاصة
data1
يتم ملء حقول databasename و recordsource. يجب أن يشير databasename إلى قاعدة بيانات Access articles.mdb الموجودة في الدليل الخاص بك، وأن يشير recordsource إلى جدول articles.
code
هذا مربع نص نريد ربطه بحقل code للسجل الحالي في data1. للقيام بذلك، نملأ حقلين:
datasource: أدخل data1 للإشارة إلى أن مربع النص مرتبط بالجدول المرتبط بـ data1
حقل البيانات: حدد حقل الرمز من جدول articles
بعد هذه الخطوات، سيحتوي مربع النص code دائمًا على حقل code للسجل الحالي في data1. وبالمقابل، سيؤدي تغيير محتوى مربع النص هذا إلى تحديث حقل code للسجل الحالي.
افعل الشيء نفسه مع مربعات النص الأخرى
name
مصدر البيانات: data1 حقل البيانات: name
السعر
مصدر البيانات: data1 حقل البيانات: price
الحالي
مصدر البيانات: data1 حقل البيانات: current_stock
الحد الأدنى
مصدر البيانات: data1 حقل البيانات: stock_min

القوائم

هيكل القائمة كما يلي

تحرير
تصفح
خروج
إضافة
رجوع
 
تحرير
التالي
 
حذف
الأول
 
 
الأخير
 

فيما يلي وظائف الخيارات المختلفة:

القائمة
القائمة
الوظيفة
إضافة
mnuadd
لإضافة سجل جديد إلى جدول العناصر
حذف
delete
لحذف السجل المعروض حاليًا من جدول العناصر
تحرير
تحرير
لتعديل السجل المعروض حاليًا في جدول العناصر
السابق
mnuprecedent
للانتقال إلى السجل السابق
التالي
لأسفل-التالي
للتخطي إلى التسجيل التالي
الأول
السابق
للانتقال إلى المقطع الأول
الأخير
mnunlast
للانتقال إلى آخر تسجيل
خروج
mnuquit
للخروج من التطبيق

تحميل الورقة

أثناء حدث form_load، يتم فتح الجدول المرتبط بـ data1 (data1.refresh). إذا فشل فتح الجدول، يتم عرض رسالة خطأ وينتهي البرنامج (end). خلاف ذلك، يتم عرض النموذج مع ظهور السجل الأول من جدول articles. لا يُسمح بإدخال أي بيانات في الحقول (الخاصية enabled مضبوطة على false). لا يمكن الإدخال إلا باستخدام خياري Add و Edit. يتم إخفاء زري "موافق" و"إلغاء" (visible=false).


الجزء 1

نقترح إنشاء الإجراءات المتعلقة بخيارات القائمة المختلفة بالإضافة إلى زري OK و Cancel. في الوقت الحالي، سنتجاهل النقاط التالية:

  • تمكين/تعطيل خيارات معينة في القائمة: على سبيل المثال، يجب تعطيل خيار "التالي" إذا كان المؤشر على السجل الأخير في الجدول
  • إدارة شريط التمرير الأفقي

قائمة "تصفح"/"التالي"

  • ينتقل إلى السجل التالي (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)
  • يعرض زري "موافق" و"إلغاء" (visible=true)

زر "موافق"

  • يحفظ تحديث السجل (data1.recordset.Update)
  • يخفي زري "موافق" و"إلغاء" (visible=false)
  • تمكين خيارات "تحرير" و"تصفح" و"خروج" (enabled=true)
  • يحدّث مربع النص في النموذج

زر "إلغاء"

  • يلغي تحديث السجل (data1.recordset.CancelUpdate)
  • يخفي زري "موافق" و"إلغاء" (visible=false)
  • تمكين خيارات "تحرير" و"تصفح" و"خروج" (enabled=true)
  • يحدّث مربع النص في النموذج

قائمة "تحرير/تعديل"

  • تسمح لك بتحرير السجل المعروض في النموذج
  • يدخل في وضع تحرير السجل (data1.recordset.edit)
  • يتيح الإدخال في الحقول الأربعة: الاسم والسعر وما إلى ذلك (enabled=true) ولكن ليس في حقل الرمز (enabled=false)
  • يعطل قوائم "تحرير" و"تصفح" و"خروج" (enabled=false)
  • يعرض زري "موافق" و"إلغاء" (visible=true)

قائمة "تحرير/حذف"

  • يسمح لك بحذف (data1.recordset.delete) السجل المعروض من الجدول
  • ينتقل إلى السجل التالي (data1.recordset.movenext)

قائمة الخروج

  • يفرغ النموذج (unload me)

حدث form_unload (cancel كعدد صحيح)

  • يتم تشغيله بواسطة عملية unload me أو عن طريق إغلاق النموذج باستخدام Alt-F4 أو النقر المزدوج على أيقونة علبة النظام، وليس بالضرورة عن طريق خيار الخروج.
  • يعرض السؤال "هل تريد حقًا الخروج من التطبيق؟" مع زرين نعم/لا (msgbox مع style=vbyes+vbno)
  • إذا كانت الإجابة "لا" (=vbno)، فاضبط الإلغاء على -1 واخرج من إجراء form_unload. الإلغاء المضبوط على -1 يشير إلى رفض إغلاق النافذة.
  • إذا كانت الإجابة "نعم" (=vbyes)، يتم إغلاق قاعدة البيانات (data1.recordset.close، data1.database.close).

الجزء 2 - إدارة القوائم

هنا، نركز على تمكين/تعطيل القوائم. بعد كل عملية تغير السجل الحالي، سنستدعي إجراءً يمكننا تسميته Oueston. سيتحقق هذا الإجراء من الشروط التالية:

. إذا كان الملف فارغًا، فإننا

  • قوائم "تصفح" و"تحرير/حذف"،
  • ونقوم بتمكين القوائم الأخرى

. إذا كان السجل الحالي هو السجل الأول، فإننا

  • نعطل "تصفح/السابق"
  • سيترك الباقي

. إذا كان السجل الحالي هو الأخير،

  • تعطيل "تصفح/التالي"
  • سيسمح بالباقي

الجزء 3 - إدارة شريط التمرير الأفقي

يحتوي شريط التمرير الأفقي على ثلاثة حقول مهمة:

  • 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، نكتب ببساطة

    data1.recordset.absoluteposition=position.value

بحيث يعكس السجل الحالي المعروض في النموذج الحركة التي تمت على عصا التحكم.

بمجرد الانتهاء من ذلك، سنقوم بعد ذلك باستدعاء إجراء Oueston لتحديث القوائم.

تحديث DCS

نظرًا لأن موضع مؤشر محرك الأقراص يجب أن يعكس الموضع في الجدول، يجب تحديث قيمة محرك الأقراص في كل مرة يحدث فيها تغيير في السجل الحالي في الجدول، يتم تشغيله بواسطة إحدى القوائم. نظرًا لأن كل منها يستدعي إجراء Oueston، فمن الأفضل وضع هذا التحديث داخل هذا الإجراء أيضًا. اكتب هنا ببساطة:

    position.value=data1.recordset.absoluteposition

الجزء 4 - خيار البحث

نضيف خيار التصفح/البحث، الذي يسمح للمستخدم بعرض عنصر ما عن طريق إدخال رمزه.

عند تمكين هذا الخيار، تحدث الخطوات التالية:

  • يتحول النظام إلى وضع "إضافة جديد" (Addnew)، وذلك فقط لتجنب تعديل السجل الحالي الذي كان مفتوحًا عند تمكين الخيار،
  • نقوم بتمكين الإدخال في حقل الرمز ومسح محتويات حقل السجل،
  • يتم تعطيل القوائم، ويتم عرض زري "موافق" و"إلغاء"
  • عندما ينقر المستخدم على "موافق"، يجب أن نبحث عن السجل المطابق للرمز الذي أدخله المستخدم. ومع ذلك، فإن إجراء OK_click مستخدم بالفعل لخياري "تصفح/إضافة" و"تصفح/تعديل". للتمييز بين هذه الحالات، نحتاج إلى إدارة متغير عام، سنسميه هنا "state"، والذي سيكون له ثلاث قيم محتملة: "add" و"modify" و"search". ستستخدم الإجراءات المرتبطة بزرّي "موافق" و"إلغاء" هذا المتغير لتحديد السياق الذي يتم استدعاؤها فيه.
  • إذا كانت الحالة هي "search"، في الإجراء المرتبط بـ OK، فإننا
    • نقوم بإلغاء عملية addnew (data1.recordset.cancelupdate) لأننا لم نكن ننوي إضافة سجل. لاحظ أن السجل الحالي يعود بعد ذلك إلى السجل الذي كان معروضًا على الشاشة قبل عملية "تصفح/بحث".
    • قم ببناء معايير البحث بناءً على الرمز وابدأ البحث (data1.recordset.findfirst criteria
    • وإذا فشل البحث (data1.recordset.nomatch=true)، نقوم بإخطار المستخدم، ثم نعود إلى وضع الإضافة (addnew) ونخرج من الإجراء OK. سيحتاج المستخدم إلى إعادة إدخال رمز جديد أو تحديد خيار Cancel.
    • إذا نجح البحث، يصبح السجل الذي تم العثور عليه هو السجل النشط الجديد. نقوم باستعادة القوائم وإخفاء أزرار "موافق"/"إلغاء" وتعطيل الإدخال في حقل الرمز والخروج من الإجراء.
  • . إذا كانت الحالة "search"، في الإجراء المرتبط بـ Cancel، فإننا
    • نقوم بإلغاء عملية addnew (data1.recordset.cancelupdate). ثم يعود النظام تلقائيًا إلى السجل الذي كان نشطًا قبل عملية التصفح/البحث.
    • استعادة القوائم، وإخفاء أزرار "موافق"/"إلغاء"، وتعطيل الإدخال في حقل الرمز، والخروج من الإجراء.

الجزء 5 - إدارة الكود

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

6.4.4. التمرين 4

نقدم هنا تطبيقًا ويبًا يعتمد على الخادم المستخدم في التمرين 2. وهو تطبيق أساسي للتجارة الإلكترونية.

يطلب العميل المنتجات باستخدام واجهة الويب التالية:

Image

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

  • اختيار عنصر من القائمة المنسدلة
  • تحديد الكمية المطلوبة
  • تأكيد الشراء بالنقر على زر "شراء"
  • يتم عرض مشترياتهم في قائمة العناصر المشتراة
  • يمكنه إزالة العناصر من هذه القائمة عن طريق تحديد عنصر والنقر على زر "إزالة"
  • عند النقر على زر "الملخص"، يظهر الملخص التالي:

Image

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

Image

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

Image

قبل إرسال الطلب، يطلب التطبيق تأكيدًا:

Image

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

Image

في الواقع، لا يقوم التطبيق بمعالجة الطلب. بل يكتفي بإرسال بريد إلكتروني إلى المستخدم يطلب منه دفع ثمن مشترياته:

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 الصغير.