6. [Cours]: مقدمة إلى API JDBC
الكلمات المفتاحية: قواعد البيانات العلائقية، API JDBC، SQLException.
6.1. Support
![]() | ![]() | ![]() |
يحتوي المجلد [support / chap-06] على مشاريع Eclipse الخاصة بهذا الفصل.
6.2. Architecture
![]() |
الطبقة JDBC (Java DataBase Connectivity) هي واجهة وصول عالمية إلى قواعد البيانات. وهي تقدم دائمًا نفس الواجهة للطبقة [DAO]. إذا تم تغيير SGBD، يكفي تغيير برنامج التشغيل JDBC. لا تتغير الطبقة [DAO].
6.3. خطوات تشغيل قاعدة البيانات
![]() |
في البنية المذكورة أعلاه، يتضمن تشغيل قاعدة البيانات بواسطة برنامج وحدة التحكم الخطوات التالية:
- تحميل برنامج التشغيل JDBC لقاعدة البيانات؛
- فتح اتصال بالقاعدة؛
- إصدار أمر SQL على قاعدة البيانات ومعالجة نتائج الأمر SQL؛
- إغلاق الاتصال؛
يتم تنفيذ الخطوة 1 مرة واحدة فقط. يتم تنفيذ الخطوات 2-4 بشكل متكرر. تجدر الإشارة إلى أنه لا يتم ترك الاتصال مفتوحًا. يتم إغلاقه بمجرد انتهاء الحاجة إليه.
6.3.1. الخطوة 1 - تحميل برنامج التشغيل JDBC في الذاكرة
الرمز
// تحميل برنامج التشغيل JDBC
try {
Class.forName(nom de la classe du pilote JDBC);
} catch (ClassNotFoundException e1) {
// معالجة الاستثناء
}
تهدف العملية الواردة في السطر 3 إلى تحميل برنامج التشغيل JDBC من قاعدة البيانات إلى الذاكرة. لا يلزم إجراء هذه العملية سوى مرة واحدة. ومع ذلك، فإن تكرارها لا يتسبب في حدوث أي خطأ. يتم البحث عن فئة برنامج التشغيل JDBC في مسار الفئات (Classpath) للمشروع. لذلك، يجب أن يكون ملف [jar] الذي يحتوي على فئة برنامج التشغيل JDBC قد أُدرج في مسار الفئات (Classpath) للمشروع في Eclipse.
6.3.2. الخطوة 2 - فتح اتصال
بمجرد تثبيت برنامج التشغيل JDBC، يُطلب منه فتح اتصال مع BD:
الرمز
package spring.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class IntroJdbc01 {
...
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// فتح الاتصال
connexion = DriverManager.getConnection(url, user, passwd);
...
} catch (SQLException e1) {
// معالجة الاستثناء
...
} finally {
// إغلاق الاتصال
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e2) {
// معالجة الاستثناء
...
}
}
}
- الأسطر 3-7: فئات تنفيذ واجهة JDBC موجودة جميعها في الحزمة [java.sql]. علاوة على ذلك، في حالة حدوث خطأ، فإنها تطلق جميعها استثناءً من النوع [SQLException] (السطر 19، 27). هذا الاستثناء مشتق من الفئة [Exception] وهو استثناء يُسمى "مُحكوم": يجب وضع try / catch لإدارته أو، كبديل، عدم إدارته والإشارة إلى أن الطريقة تسمح بخروج الاستثناء عن طريق استكمال توقيع الطريقة بـ [throws SQLException]؛
- السطر 17، [DriverManager.getConnection] هي طريقة ثابتة تتوقع ثلاثة معلمات:
- [url]: URL من قاعدة البيانات. وهي سلسلة أحرف تعتمد على BD المستخدمة. بالنسبة لـ MySQL، تكون على شكل [jdbc:mysql://localhost:3306/nom_de_la_bd]؛
- [user]: مالك الاتصال؛
- [passwd]: كلمة المرور الخاصة به؛
- السطور 24-30: يجب إغلاق الاتصال في الجملة [finally] حتى يتم إغلاقه سواء حدثت استثناءات أم لا.
6.3.3. الخطوة 3 - إصدار الأوامر SQL [SELECT]
بمجرد الحصول على اتصال، يمكن إصدار أوامر SQL. تختلف طريقة إدارة أوامر القراءة [SELECT] عن تلك المستخدمة في عمليات التحديث [UPDATE, INSERT, DELETE]. نبدأ بأوامر SQL [SELECT]:
الرمز
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// بدء الجلسة
connexion = DriverManager.getConnection(url, user, passwd);
// بدء المعاملة
connexion.setAutoCommit(false);
// في وضع القراءة فقط
connexion.setReadOnly(true);
// قراءة الجدول [PRODUITS]
ps = connexion.prepareStatement("SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS");
rs = ps.executeQuery();
System.out.println("Liste des produits : ");
while (rs.next()) {
System.out.println(new Produit(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getDouble(4), rs.getString(5)));
}
// تثبيت المعاملة
connexion.commit();
} catch (SQLException e1) {
// معالجة الاستثناء
doCatchException(connexion,e1);
} finally {
// معالجة finally
doFinally(rs, ps, connexion);
}
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
....
}
- السطران 8 و 10: فتح معاملة (السطر 8) في وضع القراءة فقط (السطر 10). المعاملة هي سلسلة من الأوامر SQL التي إما تنجح جميعها أو تفشل جميعها. وبالتالي، في معاملة تتضمن N أوامر SQL، إذا فشلت الأمر I+1، فسيتم إلغاء الأوامر I السابقة. بالنسبة لعملية القراءة، لا تكون المعاملة ضرورية. ومع ذلك، فإن إنشاء معاملة للقراءة فقط قد يسمح لبعض SGBD بإجراء بعض التحسينات؛
- السطر 12: استخدام أمر [PreparedStatement]. عادةً ما يحتوي أمر [PreparedStatement] على معلمات يُشار إليها بالحرف "؟". هنا لا توجد معلمات. [PreparedStatement] هو أمر تم إعداده بواسطة SGBD. هذا الإعداد له تكلفة ولا يتم إلا مرة واحدة. بعد ذلك، يتم تنفيذ هذا الأمر المُعد بواسطة SGBD باستخدام معلمات فعالة مختلفة ستحل محل المعلمات الشكلية "؟". تجدر الإشارة إلى أنه من الأفضل تسمية الأعمدة المطلوبة بدلاً من استخدام الرمز * للحصول على جميع الأعمدة. من خلال تحديد أسماء الأعمدة، يمكن بعد ذلك الحصول على قيمها بناءً على موضعها في الاستعلام SELECT؛
- السطر 13: تنفيذ [PreparedStatement]. يتم استرداد كائن من النوع [ResultSet]؛
يمثل كائن من النوع [ResultSet] جدولًا، أي مجموعة من الصفوف والأعمدة. في لحظة معينة، لا يمكن الوصول إلا إلى صف واحد من الجدول يُسمى الصف الحالي. عند الإنشاء الأولي لـ [ResultSet]، لا يوجد صف حالي. يجب إجراء عملية [ResultSet.next()] للحصول عليه. توقيع طريقة next هو كما يلي:
تحاول هذه الطريقة الانتقال إلى السطر التالي من [ResultSet] وتُرجع true في حالة نجاحها، وfalse في حالة الفشل. في حالة النجاح، يصبح السطر التالي هو السطر الحالي الجديد. يتم فقدان السطر السابق ولا يمكن الرجوع للخلف لاستعادته.
تحتوي جدول [ResultSet] على أعمدة تسمى labelCol1، labelCol2،... محددة في الاستعلام [SELECT] الذي تم تنفيذه. مع الاستعلام:
SELECT ID as myId, NOM as myNom, CATEGORIE as myCategorie, PRIX as myPrix, DESCRIPTION as myDescription FROM PRODUITS
- سيتم نقل العمود [ID] إلى عمود في [ResultSet] يسمى [myId]؛
- سيتم نقل العمود [NOM] إلى عمود في [ResultSet] يسمى [myNom]؛
- ...
فيما سبق، يُطلق على المعرفات [myCol] اسم تسميات الأعمدة. وفي حالة عدم وجود هذه التسميات، فإن أسماء أعمدة [ResultSet] تعتمد على SGBD. عندما يعمل [SELECT] على جدول واحد، ستكون تسميات الأعمدة افتراضيًا هي أسماء الأعمدة المطلوبة بواسطة SELECT. تنشأ المشكلة عندما يعمل [SELECT] على عدة جداول وتوجد فيها أسماء أعمدة متطابقة كما في المثال التالي:
SELECT PRODUITS.NOM, CATEGORIES.NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
بافتراض أن الجدول [PRODUITS] يحتوي على مفتاح خارجي إلى الجدول [CATEGORIES] الذي يرمز إليه العلاقة [Produits].CATEGORIE_ID --> [CATEGORIES].ID، وأن الجدولين [PRODUITS] و [CATEGORIES] يحتويان كلاهما على حقل [NOM]. في هذه الحالة، تعتمد الأسماء المحددة في [ResultSet] للأعمدة [PRODUITS.NOM] و [CATEGORIES.NOM] على SGBD. للتنقل بين SGBD، يجب استخدام تسميات الأعمدة هنا وسنكتب:
SELECT PRODUITS.NOM as p_NOM, CATEGORIES.NOM as c_NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
لاستخدام الحقول المختلفة في السطر الحالي لـ [ResultSet]، تتوفر الطرق التالية:
للحصول على العمود المسمى «labelColi» من السطر الحالي، وبالتالي العمود في [SELECT] الذي يحمل هذا التسمية. يشير Type إلى نوع الحقل coli. يمكن استخدام الطرق التالية لـ [getType]: getInt، getLong، getString، getDouble، getFloat، getDate، ... بدلاً من استخدام اسم العمود، يمكن استخدام موضعه في الاستعلام [SELECT] الذي تم تنفيذه:
حيث i هو مؤشر العمود المطلوب (i>=1).
- الأسطر 15-17: استرداد القيم المقروءة في BD؛
- السطر 19: يتم التحقق من صحة المعاملة (يُقال أيضًا أنها تم التزامها). وهذا ينهيها ويحرر الموارد التي استخدمتها SGBD من أجلها؛
- السطر 25: يتم تحرير الموارد في [finally]. ويستدعي هذا الأخير الطريقة التالية [doFinally]:
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// إغلاق ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// إغلاق [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
if (connexion != null) {
try {
// إغلاق الاتصال
connexion.close();
} catch (SQLException e3) {
// معالجة الاستثناء
}
}
}
- الأسطر 3-9: إغلاق [ResultSet]؛
- الأسطر 11-17: إغلاق [PreparedStatement]؛
- الأسطر 18-27: إغلاق الاتصال؛
يبدو أن عمليات الإغلاق في الأسطر 3-17 زائدة عن الحاجة، حيث يتم إغلاق الاتصال في الأسطر 18-25. في الواقع، في بعض الحالات، لا تكون زائدة عن الحاجة، ويُنصح بتركها [http://stackoverflow.com/questions/4507440/must-jdbc-resultsets-and-statements-be-closed-separately-although-the-connection].
- السطر 22: يتم معالجة الاستثناء بواسطة الطريقة التالية:
private static void doCatchException(Connection connexion, Throwable th) {
// إلغاء المعاملة
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// معالجة الاستثناء
}
}
- السطور 4-6: يتم إلغاء المعاملة. وهذا ينهيها وستتمكن SGBD من تحرير الموارد التي تم تخصيصها لها؛
6.3.4. الخطوة 3 - إصدار الأوامر SQL [INSERT, UPDATE, DELETE]
الأوامر SQL [INSERT, UPDATE, DELETE] هي عمليات تحديث: فهي تُعدّل قاعدة البيانات ولكنها لا تُرجع أي سطر. والمعلومة الوحيدة التي تُرجع هي عدد الأسطر التي تأثرت بعملية التحديث.
الرمز
Connection connexion = null;
PreparedStatement ps = null;
try {
// فتح الاتصال
connexion = DriverManager.getConnection(url, user, passwd);
// بدء المعاملة
connexion.setAutoCommit(false);
// في وضع القراءة/الكتابة
connexion.setReadOnly(false);
// تحديث الجدول
ps = connexion.prepareStatement("UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?");
// الفئة 1
ps.setInt(1, 10);
// التنفيذ
int nbLignes=ps.executeUpdate();
// تثبيت المعاملة
connexion.commit();
} catch (SQLException e1) {
// معالجة الاستثناء
doCatchException(connexion, e1);
} finally {
// معالجة finally
doFinally(null, ps, connexion);
}
}
- السطر 9: يتم استخدام الاتصال للقراءة والكتابة؛
- السطر 11: [PreparedStatement] مع معلمة واحدة (يُرمز لها بـ؟). يمكن أن يكون هناك عدة معلمات. يتم ترقيمها بدءًا من 1؛
- السطر 13: يتم تعيين قيمتها للمعلمة الوحيدة. المعلمة الأولى لـ [setType] هي موضع المعلمة في [PreparedStatement] (1، 2، ...) والثانية هي القيمة المخصصة لها. يمكن استخدام الطرق [setInt, setLong, setFloat, setDouble, setString, setDate, ...]؛
- السطر 15: يتم استخدام الطريقة [executeUpdate] وليس [executeQuery] المخصصة لأوامر SELECT. تعرض الطريقة عدد الأسطر المتأثرة بالعملية. قد تكون 0.
- السطر 17: يتم التحقق من صحة المعاملة؛
6.3.5. الخطوة 4 - إغلاق الاتصال
يجب إغلاق الاتصال في أسرع وقت ممكن في سياق متعدد المستخدمين لأن SGBD يقبل عددًا محدودًا من الاتصالات المفتوحة. في الأمثلة السابقة، تم إغلاقه في جملة [finally] لعمليات SQL بحيث يتم إغلاقه سواء حدثت استثناءات أم لا.
6.4. مشروع نموذجي
6.4.1. الدعم
![]() |
يحتوي المجلد [support / chap5] على مشاريع Eclipse الخاصة بهذا الفصل [1, 2]. يحتوي المجلد [database] على البرنامج النصي SQL الذي يسمح بإنشاء قاعدة البيانات MySQL النموذجية لهذا الفصل [1, 3].
6.4.2. قاعدة البيانات المستخدمة
تستخدم الأمثلة التالية قاعدة البيانات MySQL التالية:
![]() |
- [ID]: المفتاح الأساسي في الوضع AUTO_INCREMENT (إذا لم يتم تحديد مفتاح أساسي، فإن SGBD تقوم بإنشائه)؛
- [NOM]: اسم المنتج - فريد؛
- [CATEGORIE]: رقم فئته؛
- [PRIX]: سعره؛
- [DESCRIPTION]: وصف المنتج؛
سنقوم بإنشائه باستخدام الأداة [WampServer] بالطريقة التالية [1-9]:
![]() |
![]() |
![]() |
![]() |
6.4.3. مشروع Eclipse
![]() |
المشروع هو مشروع Maven محدد بواسطة الملف [pom.xml] التالي:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.jdbc</groupId>
<artifactId>intro-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.1</version>
</dependency>
</dependencies>
</project>
- الأسطر 8-12: برنامج التشغيل JDBC لـ SGBD MySQL5؛
- الأسطر 13-17: مكتبة قادرة على إدارة jSON (Javascript Object Notation) (انظر الفقرة 22.6). سنستخدمها لعرض منتجات قاعدة البيانات في شكل jSON؛
6.4.4. فئة المنتجات
فئة [Produit] هي كما يلي:
package istia.st.jdbc;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Produit {
// الحقول
private int id;
private String nom;
private int categorie;
private double prix;
private String description;
// المنشئات
public Produit() {
}
public Produit(int id, String nom, int categorie, double prix, String description) {
this.id = id;
this.nom = nom;
this.categorie = categorie;
this.prix = prix;
this.description = description;
}
// مُستردات ومُعيّنات
...
// إلى String
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}
- السطر 34: نستخدم مكتبة jSON لعرض سلسلة jSON الخاصة بالمنتج. وهذا يعطي عرضًا مشابهًا لما يلي:
Liste des produits :
{"id":1,"nom":"NOM1","categorie":1,"prix":100.0,"description":"DESC1"}
{"id":2,"nom":"NOM2","categorie":1,"prix":101.0,"description":"DESC2"}
ميزة الطريقة [toString] المذكورة أعلاه هي أنه إذا تمت إضافة/حذف حقول من الفئة، فإن طريقتها [toString] تظل صالحة. علاوة على ذلك، إذا كانت الحقول نفسها كائنات (قوائم، جداول، قواميس، كائنات مستخدم)، فإن مكتبات jSON تعرف كيفية تحويلها بدورها إلى سلسلة jSON؛
6.4.5. الفئة [Static]
تجمع الفئة [Static] في طرقها الكود المستخدم بشكل متكرر في الفئة الرئيسية:
package istia.st.jdbc;
import java.util.ArrayList;
import java.util.List;
public class Static {
public static List<String> getErreursFromThrowable(Throwable th) {
// استرداد قائمة رسائل الخطأ الخاصة بالاستثناء
List<String> erreurs = new ArrayList<String>();
while (th != null) {
// رسالة خطأ الكائن القابل للرمي
erreurs.add(th.getMessage());
// الانتقال إلى سبب الاستثناء
th = th.getCause();
}
// النتيجة
return erreurs;
}
public static void show(String title, List<String> messages){
// العنوان
System.out.println(String.format("%s : ",title));
// الرسائل
for(String message : messages){
System.out.println(String.format("- %s",message));
}
}
}
- الأسطر 8-19: تتيح الحصول على قائمة الأخطاء المُغلفة في كائن من النوع [Throwable]، وهي الفئة الأم للفئة [Exception]؛
- الأسطر 21-28: تعرض قائمة بالرسائل على الشاشة؛
يمكن أن يكون هذا الرمز في الفئة الرئيسية لأنها الوحيدة التي تستخدمه هنا. نحن هنا في حالة أوسع نطاقًا حيث قد تحتاج فئات أخرى إلى هذا الرمز.
6.4.6. هيكل الفئة الرئيسية
package istia.st.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class IntroJdbc01 {
// الثوابت
final static String url = "jdbc:mysql://localhost:3306/dbIntroJdbc";
final static String user = "root";
final static String passwd = "";
final static String insert = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?, ?)";
final static String delete = "DELETE FROM PRODUITS";
final static String select = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
final static String update = "UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?";
final static String insert2 = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (100,'X',1,1,'x')";
public static void main(String[] args) {
// تحميل برنامج التشغيل JDBC من MySQL
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e1) {
doCatchException("Pilote JDBC introuvable", null, e1);
return;
}
// إفراغ الجدول [PRODUITS]
delete();
// ملؤها
insert();
// قراءتها
select();
// تحديث
update();
// عرض
select();
// إدراج عنصرين متطابقين
// يجب أن يفشل الإدراج ولا يتم إدراج أي من العنصرين بسبب المعاملة
insert2();
// يتم التحقق
select();
// انتهى
System.out.println("Travail terminé");
}
// قائمة المنتجات
private static void select() {
...
}
// حذف المنتجات
public static void delete() {
...
}
// إضافة منتجات
public static void insert() {
...
}
// إضافة منتجين
public static void insert2() {
...
}
// تحديث بعض المنتجات
public static void update() {
..
}
private static void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// إغلاق ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// إغلاق [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// إغلاق الاتصال
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// يتم عرض رسائل الخطأ
Static.show("Les erreurs suivantes se sont produites lors de la fermeture de la connexion",
Static.getErreursFromThrowable(e3));
}
}
}
private static void doCatchException(String title, Connection connexion, Throwable th) {
// عرض رسائل الخطأ
Static.show(title, Static.getErreursFromThrowable(th));
// إلغاء المعاملة
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// عرض رسائل الخطأ
Static.show(title, Static.getErreursFromThrowable(e2));
}
}
}
6.4.7. حذف محتوى جدول المنتجات
تقوم الطريقة [delete] بحذف محتوى الجدول:
// حذف المنتجات
public static void delete() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// فتح الاتصال
connexion = DriverManager.getConnection(url, user, passwd);
// بدء المعاملة
connexion.setAutoCommit(false);
// إفراغ الجدول [PRODUITS]
ps = connexion.prepareStatement(delete);
ps.executeUpdate();
// تثبيت المعاملة
connexion.commit();
// العودة إلى الوضع الافتراضي
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// معالجة الاستثناء
doCatchException("Les erreurs suivantes se sont produites à la suppression du contenu de la table", connexion, e1);
} finally {
// معالجة finally
doFinally(null, null, connexion);
}
}
يستخدم هذا المثال المعاملات. تسمح المعاملة بتجميع الأوامر SQL التي يجب أن تنجح جميعها أو تلغى جميعها. هناك أربع عمليات يجب معرفتها:
- بدء معاملة: [connexion.setAutoCommit(false)]؛
- نهاية معاملة ناجحة: [connexion.commit()]. في هذه الحالة، يتم التحقق من صحة جميع العمليات التي أجريت على BD أثناء المعاملة؛
- نهاية معاملة فاشلة: [connexion.rollback()]. في هذه الحالة، يتم إلغاء جميع العمليات التي تمت على BD أثناء المعاملة؛
- العودة إلى الوضع [auto-commit] وهو الوضع الافتراضي لـ API JDBC: [connexion.setAutoCommit(true)]. في هذا الوضع، تخضع كل أمر SQL لمعاملة. وبالتالي، إذا تم إجراء إدخالين وفشل الثاني:
- في الوضع [AutoCommit=true]، يبقى الإدخال الأول (تمت المصادقة عليه بواسطة AutoCommit الأول)؛
- في الوضع [AutoCommit=false]، يتم إلغاء الإدخال الأول؛
في أمثلةنا، في كل مرة تحدث فيها استثناء، نقوم بإلغاء المعاملة في الطريقة [doCatchException]:
private static void doCatchException(String title, Connection connexion, Throwable th) {
// عرض رسائل الخطأ
Static.show(title, Static.getErreursFromThrowable(th));
// إلغاء المعاملة
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// عرض رسائل الخطأ
Static.show("Erreur lors de l'annulation de la transaction", Static.getErreursFromThrowable(e2));
}
}
6.4.8. إنشاء محتوى جدول المنتجات
تقوم الطريقة [insert] بإنشاء محتوى الجدول:
// إضافة المنتجات
public static void insert() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// فتح الاتصال
connexion = DriverManager.getConnection(url, user, passwd);
// بدء المعاملة
connexion.setAutoCommit(false);
// ملء الجدول
ps = connexion.prepareStatement(insert);
for (int i = 0; i < 10; i++) {
// التحضير
int n = i + 1;
ps.setInt(1, n);
ps.setString(2, String.format("NOM%s", n));
ps.setInt(3, n / 5 + 1);
ps.setDouble(4, 100 * (1 + (double) i / 100));
ps.setString(5, String.format("DESC%s", n));
// التنفيذ
ps.executeUpdate();
}
// تثبيت المعاملة
connexion.commit();
// العودة إلى الوضع الافتراضي
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// معالجة الاستثناء
doCatchException("Les erreurs suivantes se sont produites à la création du contenu de la table", connexion, e1);
} finally {
// معالجة finally
doFinally(null, null, connexion);
}
}
6.4.9. عرض محتوى جدول المنتجات
تعرض الطريقة [select] محتوى الجدول:
// قائمة المنتجات
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// فتح الاتصال
connexion = DriverManager.getConnection(url, user, passwd);
// بدء المعاملة
connexion.setAutoCommit(false);
// قراءة الجدول [PRODUITS]
ps = connexion.prepareStatement(select);
rs = ps.executeQuery();
System.out.println("Liste des produits : ");
while (rs.next()) {
System.out.println(new Produit(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getDouble(4), rs.getString(5)));
}
// تثبيت المعاملة
connexion.commit();
// العودة إلى الوضع الافتراضي
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// معالجة الاستثناء
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// معالجة finally
doFinally(null, null, connexion);
}
}
6.4.10. تحديث محتوى الجدول
تقوم الطريقة [update] بتحديث بعض المنتجات:
// تحديث بعض المنتجات
public static void update() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// فتح اتصال
connexion = DriverManager.getConnection(url, user, passwd);
// بداية المعاملة
connexion.setAutoCommit(false);
// يتم تحديث الجدول
ps = connexion.prepareStatement(update);
// الفئة 1
ps.setInt(1, 1);
// التنفيذ
ps.executeUpdate();
// تثبيت المعاملة
connexion.commit();
// العودة إلى الوضع الافتراضي
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// معالجة الاستثناء
doCatchException("Les erreurs suivantes se sont produites à la mise à jour du contenu de la table", connexion, e1);
} finally {
// معالجة finally
doFinally(null, null, connexion);
}
}
6.4.11. دور المعاملة
تقوم الطريقة [insert2] بإدراج منتجين لهما نفس المفتاح الأساسي في الجدول، وهو أمر غير ممكن. ونظرًا لأننا في معاملة، فسيتم إلغاء الإدراج الأول.
// إضافة من تجين بنفس المفاتيح الأولية
public static void insert2() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// فتح اتصال
connexion = DriverManager.getConnection(url, user, passwd);
// بدء المعاملة
connexion.setAutoCommit(false);
// إضافة سطر واحد
ps = connexion.prepareStatement(insert2);
// التنفيذ
ps.executeUpdate();
// يتم إضافة نفس السطر للمرة الثانية وبالتالي بنفس المفتاح الأساسي
// يجب أن تفشل عملية الإدراج ولا يجب إدراج أي من العنصرين بسبب المعاملة
ps.executeUpdate();
// تثبيت المعاملة
connexion.commit();
// العودة إلى الوضع الافتراضي
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// معالجة الاستثناء
doCatchException("Les erreurs suivantes se sont produites lors de l'ajout", connexion, e1);
} finally {
// معالجة finally
doFinally(null, null, connexion);
}
}
6.4.12. النتائج
تؤدي عملية تنفيذ الطريقة [main] إلى النتائج التالية:
6.5. استخدام مصدر بيانات من النوع [DataSource]
سنستأنف التطبيق السابق باستخدام مصدر بيانات من النوع [javax.sql.DataSource]:

سنستخدم مصدر بيانات تم تنفيذه بواسطة الفئة [org.apache.tomcat.jdbc.pool.DataSource]. تستخدم هذه الفئة مجموعة اتصالات، أي مجموعة من الاتصالات المفتوحة:
- عندما يتم إنشاء مثيل للمجموعة، يتم فتح عدد معين من الاتصالات مع قاعدة البيانات. هذا العدد قابل للتكوين؛
- عندما يفتح كود Java اتصالاً، يتم توفيره من المجمع؛
- عندما يغلق كود Java اتصالاً، يتم إرجاعه إلى المجموعة؛
في النهاية، لا يتم فتح الاتصالات إلا مرة واحدة، مما يحسن أداء الوصول إلى قاعدة البيانات. سيتم تعريف مصدر البيانات في فئة تكوين Spring
6.5.1. مشروع Eclipse
![]() |
المشروع هو مشروع Maven محدد بواسطة الملف [pom.xml] التالي:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.jdbc</groupId>
<artifactId>intro-jdbc-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<!-- مكتبة jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.1</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<!-- Tomcat Jdbc -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>8.0.20</version>
</dependency>
</dependencies>
</project>
- الأسطر 21-25: التبعية على Spring؛
- الأسطر 27-31: التبعية لمكتبة توفر مصدر البيانات؛
فئة تكوين Spring [AppConfig] هي كما يلي:
package istia.st.jdbc;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
// الثوابت
final static String URL = "jdbc:mysql://localhost:3306/dbIntroJdbc";
final static String USER = "root";
final static String PASSWD = "";
final static String INSERT = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?, ?)";
final static String DELETE = "DELETE FROM PRODUITS";
final static String SELECT = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
final static String UPDATE = "UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?";
final static String INSERT2 = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (100,'X',1,1,'x')";
final static String DRIVER_CLASSNAME = "com.mysql.jdbc.Driver";
@Bean
public DataSource dataSource() {
// مصدر البيانات TomcatJdbc
DataSource dataSource = new DataSource();
// تكوين الوصول JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// اتصال مفتوح في البداية
dataSource.setInitialSize(1);
// النتيجة
return dataSource;
}
}
- الأسطر 11-19: تم نقل الثوابت التي تم تعريفها مسبقًا في [IntroJdbc01] إلى [AppConfig]؛
- الأسطر 31-34: حبة Spring التي تحدد مصدر البيانات؛
- السطر 24: إنشاء مصدر البيانات الذي لم يتم تكوينه بعد؛
- الأسطر 26-29: المعلومات التي تسمح لمصدر البيانات بالاتصال بقاعدة البيانات؛
- السطر 31؛ ينشئ مجموعة من اتصال واحد. لا نحتاج إلى أكثر من ذلك هنا. لا توجد أبدًا عدة اتصالات متزامنة؛
6.5.2. الفئة الرئيسية
الفئة الرئيسية [IntroJdbc02] هي كما يلي:
package istia.st.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IntroJdbc02 {
// مصدر البيانات
private static DataSource dataSource;
public static void main(String[] args) {
// استرداد سياق Spring
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// استرداد مصدر البيانات
dataSource = ctx.getBean(DataSource.class);
// إفراغ الجدول [PRODUITS]
delete();
...
// انتهى
ctx.close();
System.out.println("Travail terminé");
}
// قائمة المنتجات
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// فتح الاتصال
connexion = dataSource.getConnection();
// بدء المعاملة
connexion.setAutoCommit(false);
...
} catch (SQLException e1) {
// معالجة الاستثناء
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// معالجة finally
doFinally(null, null, connexion);
}
}
...
}
- السطر 14: مصدر البيانات. تجدر الإشارة إلى أنه من النوع [javax.sql.DataSource] وهو واجهة؛
- السطر 18: إنشاء مثيلات كائنات Spring؛
- السطر 20: الحصول على مرجع لمصدر البيانات. تجدر الإشارة إلى أنه لم يتم ذكر الفئة المستخدمة فعليًا في أي مرحلة. وبالتالي، لا يوجد ما يشير هنا إلى استخدام تطبيق [TomcatJdbc]؛
- السطر 36: الحصول على اتصال مفتوح؛
- باقي الكود مطابق لكود الفئة [IntroJdbc01]؛
6.6. Conclusion
يمكن العثور على مزيد من المعلومات حول إدارة قواعد البيانات في المستند [Exploiter une base relationnelle avec l'écosystème Spring].












