6. [الدورة]: مقدمة إلى واجهة برمجة تطبيقات JDBC
الكلمات المفتاحية: قواعد البيانات العلائقية، واجهة برمجة تطبيقات JDBC، استثناء SQLException.
6.1. الدعم
![]() | ![]() | ![]() |
يحتوي المجلد [support / chap-06] على مشاريع Eclipse الخاصة بهذا الفصل.
6.2. البنية
![]() |
طبقة JDBC (Java Database Connectivity) هي واجهة عالمية للوصول إلى قواعد البيانات. وهي تقدم دائمًا نفس الواجهة لطبقة [DAO]. إذا قمت بتغيير نظام إدارة قواعد البيانات (DBMS)، فما عليك سوى تغيير برنامج تشغيل JDBC. وتبقى طبقة [DAO] دون تغيير.
6.3. خطوات تشغيل قاعدة البيانات
![]() |
في البنية المذكورة أعلاه، يتضمن تشغيل قاعدة البيانات عبر برنامج وحدة التحكم الخطوات التالية:
- تحميل برنامج تشغيل JDBC الخاص بقاعدة البيانات؛
- فتح اتصال بقاعدة البيانات؛
- تنفيذ عبارة SQL على قاعدة البيانات ومعالجة نتائج عبارة SQL؛
- إغلاق الاتصال؛
يتم تنفيذ الخطوة 1 مرة واحدة فقط. يتم تنفيذ الخطوات 2-4 بشكل متكرر. لاحظ أن الاتصالات لا تُترك مفتوحة؛ بل يتم إغلاقها بمجرد انتهاء الحاجة إليها.
6.3.1. الخطوة 1 - تحميل برنامج تشغيل JDBC في الذاكرة
// driver loading JDBC
try {
Class.forName(nom de la classe du pilote JDBC);
} catch (ClassNotFoundException e1) {
// handle the exception
}
الغرض من العملية في السطر 3 هو تحميل برنامج تشغيل JDBC الخاص بقاعدة البيانات في الذاكرة. لا يلزم تنفيذ هذه العملية سوى مرة واحدة. ومع ذلك، فإن تكرارها لا يتسبب في حدوث خطأ. يتم البحث عن فئة برنامج تشغيل JDBC في مسار فئات المشروع. لذلك، في مشروع Eclipse، يجب أن يكون ملف [jar] الذي يحتوي على فئة برنامج تشغيل JDBC قد تم تضمينه في مسار فئات المشروع.
6.3.2. الخطوة 2 - فتح اتصال
بمجرد تثبيت برنامج تشغيل JDBC، نطلب منه فتح اتصال بالقاعدة البيانات:
الكود
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 {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
...
} catch (SQLException e1) {
// we handle the exception
...
} finally {
// close connection
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e2) {
// handle the exception
...
}
}
}
- الأسطر 3–7: الفئات التي تنفذ واجهة JDBC موجودة جميعها في الحزمة [java.sql]. علاوة على ذلك، في حالة حدوث خطأ، فإنها جميعًا ترمي استثناء [SQLException] (الأسطر 19 و27). هذا الاستثناء مشتق من فئة [Exception] وهو ما يُسمى باستثناء مُتحقق منه: يجب عليك استخدام كتلة try/catch لمعالجته، أو بدلاً من ذلك، اختيار عدم معالجته والإشارة إلى أن الطريقة تسمح بانتشار الاستثناء عن طريق إضافة [throws SQLException] إلى توقيع الطريقة؛
- السطر 17: [DriverManager.getConnection] هي طريقة ثابتة تأخذ ثلاثة معلمات:
- [url]: عنوان URL لقاعدة البيانات. هذه سلسلة تعتمد على قاعدة البيانات المستخدمة. بالنسبة لـ MySQL، تكون على شكل [jdbc:mysql://localhost:3306/db_name]؛
- [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 {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
// start of transaction
connexion.setAutoCommit(false);
// in read-only mode
connexion.setReadOnly(true);
// table [PRODUITS] is read
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)));
}
// commit transaction
connexion.commit();
} catch (SQLException e1) {
// we handle the exception
doCatchException(connexion,e1);
} finally {
// we treat the finally
doFinally(rs, ps, connexion);
}
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
....
}
- السطران 8 و 10: فتح معاملة (السطر 8) في وضع القراءة فقط (السطر 10). المعاملة هي سلسلة من عبارات SQL التي إما تنجح جميعها أو تفشل جميعها. وبالتالي، في معاملة تحتوي على N عبارة SQL، إذا فشلت العبارة (I+1)، فسيتم التراجع عن العبارات I السابقة. بالنسبة لعملية القراءة، لا تكون المعاملة ضرورية. ومع ذلك، فإن إنشاء معاملة للقراءة فقط يمكن أن يسمح لبعض أنظمة إدارة قواعد البيانات (DBMS) بإجراء بعض التحسينات؛
- السطر 12: استخدام [PreparedStatement]. عادةً ما تحتوي [PreparedStatement] على معلمات يُشار إليها بالحرف ?. هنا، لا تحتوي على ذلك. [PreparedStatement] هو عبارة تم إعدادها بواسطة نظام إدارة قواعد البيانات (DBMS). هذا الإعداد له تكلفة ويتم تنفيذه مرة واحدة فقط. ثم يتم تنفيذ هذه العبارة المعدة بواسطة نظام إدارة قواعد البيانات (DBMS) باستخدام المعلمات الفعلية التي تحل محل المعلمات الشكلية ?. لاحظ أنه من الأفضل تحديد الأعمدة المطلوبة بدلاً من استخدام الرمز * لاسترداد جميع الأعمدة. من خلال تحديد أسماء الأعمدة، يمكن بعد ذلك استرداد قيمها بناءً على موضعها في عبارة 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]؛
- سيتم نقل عمود [NAME] إلى عمود في [ResultSet] باسم [myName]؛
- ...
في المثال أعلاه، يُطلق على المعرفات [myCol] اسم تسميات الأعمدة. بدون هذه التسميات، تعتمد أسماء أعمدة [ResultSet] على نظام إدارة قواعد البيانات (DBMS). عندما يعمل [SELECT] على جدول واحد، تكون تسميات الأعمدة افتراضيًا هي أسماء الأعمدة المطلوبة بواسطة SELECT. تنشأ المشكلة عندما يعمل [SELECT] على جداول متعددة وتحتوي تلك الجداول على أسماء أعمدة متطابقة، كما في المثال التالي:
SELECT PRODUITS.NOM, CATEGORIES.NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
بافتراض أن الجدول [PRODUCTS] يحتوي على مفتاح خارجي للجدول [CATEGORIES]، ممثلاً بالعلاقة [PRODUCTS].CATEGORY_ID --> [CATEGORIES].ID، وأن كلا الجدولين [PRODUCTS] و[CATEGORIES] يحتويان على حقل [NAME]. في هذه الحالة، تعتمد الأسماء المحددة في [ResultSet] للأعمدة [PRODUCTS.NAME] و [CATEGORIES.NAME] على نظام إدارة قواعد البيانات (DBMS). ولضمان قابلية النقل بين أنظمة إدارة قواعد البيانات، يجب استخدام تسميات الأعمدة هنا، وسنكتب:
SELECT PRODUITS.NOM as p_NOM, CATEGORIES.NOM as c_NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
للوصول إلى الحقول المختلفة للصف الحالي في [ResultSet]، تتوفر الطرق التالية:
لاسترداد العمود المسمى "labelColi" من الصف الحالي، أي العمود في عبارة [SELECT] الذي يحمل هذا الاسم. يشير النوع إلى نوع بيانات حقل "labelColi". يمكن استخدام طرق [getType] التالية: getInt، getLong، getString، getDouble، getFloat، getDate، ... بدلاً من استخدام اسم العمود، يمكنك استخدام موضعه في استعلام [SELECT] الذي تم تنفيذه:
حيث i هو مؤشر العمود المطلوب (i>=1).
- الأسطر 15–17: استرجاع القيم المقروءة من قاعدة البيانات؛
- السطر 19: يتم التحقق من صحة المعاملة (المعروف أيضًا باسم الالتزام). يؤدي هذا إلى إنهائها وتحرير الموارد التي خصصها نظام إدارة قواعد البيانات لها؛
- السطر 25: يتم تحرير الموارد في كتلة [finally]. وهذا يستدعي الطريقة [doFinally] التالية:
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// closure ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// closure [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
if (connexion != null) {
try {
// close connection
connexion.close();
} catch (SQLException e3) {
// handle the exception
}
}
}
- الأسطر 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: يتم التعامل مع الاستثناء بواسطة الطريقة [doCatchException] التالية:
private static void doCatchException(Connection connexion, Throwable th) {
// cancel transaction
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// handle the exception
}
}
- الأسطر 4–6: يتم التراجع عن المعاملة. يؤدي ذلك إلى إنهائها، ويمكن لنظام إدارة قواعد البيانات (DBMS) تحرير الموارد المخصصة لها؛
6.3.4. الخطوة 3 - إصدار عبارات SQL [INSERT، UPDATE، DELETE]
تعد عبارات SQL [INSERT، UPDATE، DELETE] عمليات تحديث: فهي تُعدّل قاعدة البيانات ولكنها لا تُرجع أي صفوف. والمعلومة الوحيدة التي تُرجع هي عدد الصفوف التي تأثرت بعملية التحديث.
الرمز
Connection connexion = null;
PreparedStatement ps = null;
try {
// ouverture connexion
connexion = DriverManager.getConnection(url, user, passwd);
// début transaction
connexion.setAutoCommit(false);
// en mode lecture / écriture
connexion.setReadOnly(false);
// on met à jour la table
ps = connexion.prepareStatement("UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?");
// catégorie 1
ps.setInt(1, 10);
// exécution
int nbLignes=ps.executeUpdate();
// commit transaction
connexion.commit();
} catch (SQLException e1) {
// on traite l'exception
doCatchException(connexion, e1);
} finally {
// on traite le 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 - إغلاق الاتصال
يجب إغلاق الاتصال بأسرع ما يمكن في بيئة متعددة المستخدمين لأن نظام إدارة قواعد البيانات (DBMS) يقبل عددًا محدودًا من الاتصالات المفتوحة. في الأمثلة السابقة، تم إغلاقه في جملة [finally] لعمليات SQL بحيث يتم إغلاقه بغض النظر عما إذا حدث استثناء أم لا.
6.4. مشروع نموذجي
6.4.1. الدعم
![]() |
يحتوي المجلد [support / chap5] على مشاريع Eclipse الخاصة بهذا الفصل [1، 2]. ويحتوي المجلد [database] على البرنامج النصي SQL لإنشاء قاعدة بيانات MySQL النموذجية الخاصة بهذا الفصل [1، 3].
6.4.2. قاعدة البيانات المستخدمة
تستخدم الأمثلة التالية قاعدة بيانات MySQL التالية:
![]() |
- [ID]: المفتاح الأساسي في وضع AUTO_INCREMENT (إذا لم يتم تحديد مفتاح أساسي، يقوم نظام إدارة قواعد البيانات بإنشائه)؛
- [NAME]: اسم المنتج — فريد؛
- [CATEGORY]: رقم الفئة؛
- [PRICE]: سعره؛
- [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 لنظام إدارة قواعد البيانات MySQL5؛
- الأسطر 13–17: مكتبة قادرة على معالجة JSON (ترميز كائنات JavaScript) (انظر القسم 22.6). سنستخدمها لعرض المنتجات من قاعدة البيانات بتنسيق JSON؛
6.4.4. فئة Product
فئة [Product] هي كما يلي:
package istia.st.jdbc;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Produit {
// fields
private int id;
private String nom;
private int categorie;
private double prix;
private String description;
// manufacturers
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;
}
// getters and setters
...
// to 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) {
// retrieve the list of exception error msgs
List<String> erreurs = new ArrayList<String>();
while (th != null) {
// throwable error message
erreurs.add(th.getMessage());
// we move on to the cause of throwable
th = th.getCause();
}
// result
return erreurs;
}
public static void show(String title, List<String> messages){
// title
System.out.println(String.format("%s : ",title));
// messages
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 {
// constants
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) {
// loading the JDBC driver from MySQL
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e1) {
doCatchException("Pilote JDBC introuvable", null, e1);
return;
}
// empty table [PRODUITS]
delete();
// fill it
insert();
// we read it
select();
// update
update();
// display
select();
// insertion of two identical elements
// insertion must fail and neither element is inserted because of the transaction
insert2();
// we check
select();
// finish
System.out.println("Travail terminé");
}
// product list
private static void select() {
...
}
// product deletion
public static void delete() {
...
}
// add products
public static void insert() {
...
}
// add 2 products
public static void insert2() {
...
}
// product updates
public static void update() {
..
}
private static void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// closure ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// closure [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// close connection
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// display error msg
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) {
// display error msg
Static.show(title, Static.getErreursFromThrowable(th));
// cancel transaction
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// display error msg
Static.show(title, Static.getErreursFromThrowable(e2));
}
}
}
6.4.7. حذف محتويات جدول المنتجات
تقوم طريقة [delete] بحذف محتويات الجدول:
// product deletion
public static void delete() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
// start of transaction
connexion.setAutoCommit(false);
// empty table [PRODUITS]
ps = connexion.prepareStatement(delete);
ps.executeUpdate();
// commit transaction
connexion.commit();
// return to default mode
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// we handle the exception
doCatchException("Les erreurs suivantes se sont produites à la suppression du contenu de la table", connexion, e1);
} finally {
// we treat the finally
doFinally(null, null, connexion);
}
}
يستخدم هذا المثال المعاملات. تسمح لك المعاملة بتجميع عبارات SQL التي يجب أن تنجح جميعها أو يتم التراجع عنها جميعًا. هناك أربع عمليات يجب الانتباه إليها:
- بدء معاملة: [connection.setAutoCommit(false)];
- إنهاء المعاملة بنجاح: [connection.commit()]. في هذه الحالة، يتم تثبيت جميع العمليات التي تم إجراؤها على قاعدة البيانات أثناء المعاملة؛
- إنهاء المعاملة بفشل: [connection.rollback()]. في هذه الحالة، يتم التراجع عن جميع العمليات التي تم إجراؤها على قاعدة البيانات أثناء المعاملة؛
- العودة إلى وضع [auto-commit]، وهو الوضع الافتراضي لواجهة برمجة تطبيقات JDBC: [connection.setAutoCommit(true)]. في هذا الوضع، يكون كل جملة SQL جزءًا من معاملة. وبالتالي، إذا قمت بإجراء عمليتي إدراج وفشلت الثانية:
- في وضع [AutoCommit=true]، يبقى الإدراج الأول (تم تنفيذه بواسطة أول AutoCommit)؛
- في وضع [AutoCommit=false]، يتم التراجع عن عملية الإدراج الأولى؛
في أمثلةنا، كلما حدث استثناء، نقوم بإلغاء المعاملة في طريقة [doCatchException]:
private static void doCatchException(String title, Connection connexion, Throwable th) {
// display error msg
Static.show(title, Static.getErreursFromThrowable(th));
// cancel transaction
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// display error msg
Static.show("Erreur lors de l'annulation de la transaction", Static.getErreursFromThrowable(e2));
}
}
6.4.8. إنشاء محتويات جدول المنتجات
تقوم طريقة [insert] بإنشاء محتوى الجدول:
// add products
public static void insert() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
// start of transaction
connexion.setAutoCommit(false);
// fill the table
ps = connexion.prepareStatement(insert);
for (int i = 0; i < 10; i++) {
// preparation
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));
// execution
ps.executeUpdate();
}
// commit transaction
connexion.commit();
// return to default mode
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// we handle the exception
doCatchException("Les erreurs suivantes se sont produites à la création du contenu de la table", connexion, e1);
} finally {
// we treat the finally
doFinally(null, null, connexion);
}
}
6.4.9. عرض محتويات جدول المنتجات
تعرض طريقة [select] محتويات الجدول:
// product list
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
// start of transaction
connexion.setAutoCommit(false);
// table [PRODUITS] is read
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)));
}
// commit transaction
connexion.commit();
// return to default mode
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// we handle the exception
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// we treat the finally
doFinally(null, null, connexion);
}
}
6.4.10. تحديث محتويات الجدول
تقوم طريقة [update] بتحديث منتجات معينة:
// product updates
public static void update() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
// start of transaction
connexion.setAutoCommit(false);
// table is updated
ps = connexion.prepareStatement(update);
// category 1
ps.setInt(1, 1);
// execution
ps.executeUpdate();
// commit transaction
connexion.commit();
// return to default mode
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// we handle the exception
doCatchException("Les erreurs suivantes se sont produites à la mise à jour du contenu de la table", connexion, e1);
} finally {
// we treat the finally
doFinally(null, null, connexion);
}
}
6.4.11. دور المعاملة
تقوم الطريقة [insert2] بإدراج منتجين لهما نفس المفتاح الأساسي في الجدول، وهو أمر غير مسموح به. وبما أننا في معاملة، فسيتم التراجع عن الإدراج الأول.
// add 2 pro ducts with the same primary keys
public static void insert2() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
// start of transaction
connexion.setAutoCommit(false);
// add 1 line
ps = connexion.prepareStatement(insert2);
// execution
ps.executeUpdate();
// we add the same line a 2nd time, with the same primary key
// the insertion must fail and neither of the two elements must be inserted because of the transaction
ps.executeUpdate();
// commit transaction
connexion.commit();
// return to default mode
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// we handle the exception
doCatchException("Les erreurs suivantes se sont produites lors de l'ajout", connexion, e1);
} finally {
// we treat the 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>
<!-- library 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 {
// constantes
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() {
// source de données TomcatJdbc
DataSource dataSource = new DataSource();
// configuration accès JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// une connexion ouverte initialement
dataSource.setInitialSize(1);
// résultat
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 {
// data source
private static DataSource dataSource;
public static void main(String[] args) {
// spring context retrieval
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// data source recovery
dataSource = ctx.getBean(DataSource.class);
// empty table [PRODUITS]
delete();
...
// finish
ctx.close();
System.out.println("Travail terminé");
}
// product list
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// opening connection
connexion = dataSource.getConnection();
// start of transaction
connexion.setAutoCommit(false);
...
} catch (SQLException e1) {
// we handle the exception
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// we treat the finally
doFinally(null, null, connexion);
}
}
...
}
- السطر 14: مصدر البيانات. لاحظ أنه من النوع [javax.sql.DataSource]، وهو واجهة؛
- السطر 18: إنشاء مثيلات لكائنات Spring؛
- السطر 20: الحصول على مرجع لمصدر البيانات. لاحظ أن الفئة الفعلية المستخدمة لم يتم ذكرها مطلقًا. وبالتالي، لا يوجد هنا ما يشير إلى استخدام تطبيق [TomcatJdbc]؛
- السطر 36: الحصول على اتصال مفتوح؛
- باقي الكود مطابق لكود الفئة [IntroJdbc01]؛
6.6. الخلاصة
يمكن العثور على مزيد من المعلومات حول إدارة قواعد البيانات في الوثيقة [العمل مع قاعدة بيانات علائقية باستخدام نظام Spring].












