Skip to content

6. [Cours]: مقدمه‌ای بر API JDBC

واژه‌های کلیدی: پایگاه‌های دادهٔ رابطه‌ای، API JDBC، SQLException.

6.1. Support

پوشه [support / chap-06] شامل پروژه‌های اکلیپس برای این فصل است.

6.2. Architecture

لایه JDBC (ارتباطات جاوا DataBase) یک رابط دسترسی جهانی به پایگاه داده است. این لایه همیشه همان رابط را به لایه [DAO] ارائه می‌دهد. اگر SGBD را تغییر دهید، کافی است درایور JDBC را تغییر دهید. لایه [DAO] بدون تغییر باقی می‌ماند.

6.3. مراحل مربوط به کار با یک پایگاه داده

در معماری نشان داده شده در بالا، عملیات پایگاه داده برنامه کنسول شامل مراحل زیر است:

  1. بارگذاری درایور JDBC از پایگاه داده؛
  2. ایجاد اتصال به پایگاه داده؛
  3. ارسال یک فرمان SQL به پایگاه داده و پردازش نتایج فرمان SQL؛
  4. بستن اتصال؛

مرحلهٔ ۱ تنها یک‌بار اجرا می‌شود. مراحل ۲ تا ۴ به‌طور مکرر اجرا می‌شوند. توجه داشته باشید که اتصالات باز نمی‌مانند؛ به محض اینکه دیگر مورد نیاز نباشند، بسته می‌شوند.

6.3.1. مرحله ۱ – بارگذاری درایور JDBC در حافظه

کد


        // بارگذاری درایور JDBC
        try {
            Class.forName(nom de la classe du pilote JDBC);
        } catch (ClassNotFoundException e1) {
             // مدیریت استثنا
}

عملیات در خط ۳ به منظور بارگذاری درایور JDBC از پایگاه داده به حافظه است. این عملیات تنها یک بار نیاز به اجرا دارد. با این حال، تکرار آن باعث خطا نمی‌شود. کلاس درایور JDBC در مسیر کلاس (classpath) پروژه جستجو می‌شود. بنابراین، در پروژهٔ Eclipse، فایل [jar] که حاوی کلاس درایور JDBC است، باید در مسیر کلاس‌های پروژه گنجانده شده باشد.

6.3.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) {
                // پردازش استثنا
                ...
            }
         }
}
  • خطوط ۳–۷: کلاس‌هایی که رابط JDBC را پیاده‌سازی می‌کنند، همگی در پکیج [java.sql] قرار دارند. علاوه بر این، در صورت بروز خطا، همگی یک استثنای [SQLException] را پرتاب می‌کنند (خطوط ۱۹ و ۲۷). این استثنا از کلاس [Exception] مشتق شده و به عنوان یک «استثنای کنترل‌شده» شناخته می‌شود: شما باید یا از یک بلوک try/catch برای رسیدگی به آن استفاده کنید یا به طور جایگزین، تصمیم بگیرید که به آن رسیدگی نکنید و با افزودن [throws SQLException] به امضای متد، نشان دهید که متد اجازه می‌دهد استثنا propagate (منتشر) شود؛
  • خط 17: [DriverManager.getConnection] یک متد استاتیک است که سه پارامتر می‌گیرد:
    • [url]: URL از پایگاه داده. این یک رشته کاراکتری است که به BD مورد استفاده بستگی دارد. برای MySQL، این شکل را به خود می‌گیرد [jdbc:mysql://localhost:3306/nom_de_la_bd];
    • [user]: مالک اتصال؛
    • [passwd]: رمز عبور آنها؛
  • خطوط ۲۴–۳۰: اتصال باید در بند [finally] بسته شود تا صرف‌نظر از اینکه خطا رخ دهد یا خیر، بسته شود.

6.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) {
....
}
  • خطوط ۸ و ۱۰: باز کردن یک تراکنش (خط ۸) در حالت فقط خواندنی (خط ۱۰). یک تراکنش توالی‌ای از دستورات SQL است که یا همگی موفق می‌شوند یا همگی ناموفق. بنابراین، در یک تراکنش شامل N دستور SQL، اگر دستور (I+1)ام شکست بخورد، آنگاه I دستور قبلی برگشت داده (roll back) می‌شوند. برای یک عملیات خواندن، تراکنش الزامی نیست. با این حال، ایجاد یک تراکنش فقط-خواندن ممکن است به برخی از دستورات SGBD اجازه دهد تا بهینه‌سازی‌های خاصی را انجام دهند؛
  • خط ۱۲: استفاده از یک [PreparedStatement]. یک [PreparedStatement] معمولاً دارای پارامترهایی است که با کاراکتر «؟» نشان داده می‌شوند. در اینجا، هیچ‌کدام را ندارد. یک [PreparedStatement] یک عبارت آماده (prepared statement) است که توسط SGBD تولید می‌شود. این آماده‌سازی هزینه در بر دارد و تنها یک بار انجام می‌شود. سپس این عبارت آماده توسط SGBD با پارامترهای واقعی مختلف که جایگزین پارامترهای فرمال «؟» می‌شوند، اجرا می‌گردد. توجه داشته باشید که ترجیحاً بهتر است ستون‌های مورد نظر را به جای استفاده از نشانه * برای بازیابی همه ستون‌ها، با نام مشخص کنید. با مشخص کردن نام ستون‌ها، می‌توان مقادیر آن‌ها را بر اساس موقعیتشان در پرس‌وجوی SELECT بازیابی کرد؛
  • خط ۱۳: اجرای [PreparedStatement]. یک شیء از نوع [ResultSet] بازیابی می‌شود؛

یک شیء از نوع [ResultSet] نمایانگر یک جدول، یعنی مجموعه‌ای از سطرها و ستون‌ها است. در هر لحظه، ما فقط به یک سطر از جدول دسترسی داریم که به آن سطر جاری گفته می‌شود. هنگامی که [ResultSet] برای اولین بار ایجاد می‌شود، هیچ سطر جاری وجود ندارد. برای به‌دست آوردن آن، باید یک عملیات [ResultSet.next()] انجام شود. امضای متد next به شرح زیر است:

    boolean 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] به عنوان برچسب‌های ستون (column labels) شناخته می‌شوند. در صورت عدم وجود این برچسب‌ها، نام‌های ستون در [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]، روش‌های زیر در دسترس هستند:

Type getType("labelColi") 

برای بازیابی ستونی به نام «labelColi» از سطر جاری، و در نتیجه ستون دارای آن برچسب در [SELECT]. Type به نوع فیلد coli اشاره دارد. می‌توان از متدهای زیر [getType] استفاده کرد: getInt, getLong, getString, getDouble, getFloat، getDate، ... به جای استفاده از نام ستون، می‌توانید از موقعیت آن در پرس‌وجوی اجراشده [SELECT] استفاده کنید:

Type getType(i) 

که در آن i نمایه ستون مورد نظر است (i ≥ 1).

  • خطوط ۱۵–۱۷: بازیابی مقادیری که از BD خوانده شده‌اند؛
  • خط ۱۹: تراکنش اعتبارسنجی می‌شود (که به آن commit نیز گفته می‌شود). این کار آن را خاتمه داده و منابعی را که SGBD برای آن تخصیص داده بود، آزاد می‌کند؛
  • خط ۲۵: منابع در [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) {
                 // رسیدگی به استثنا
            }
        }
    }
  • خطوط ۳–۹: بسته‌شدن [ResultSet];
  • خطوط ۱۱–۱۷: بستن [PreparedStatement];
  • خطوط ۱۸–۲۷: بستن اتصال؛

بستن‌های موجود در خطوط ۳ تا ۱۷ با توجه به اینکه اتصال در خطوط ۱۸ تا ۲۵ بسته می‌شود، اضافی به نظر می‌رسند. در واقع، در برخی موارد آن‌ها اضافی نیستند و توصیه می‌شود آن‌ها را به صورت [http://stackoverflow.com/questions/4507440/must-jdbc-resultsets-and-statements-be-closed-separately-although-the-connection] باقی بگذارید.

  • خط ۲۲: استثنا توسط متد زیر مدیریت می‌شود: [doCatchException];

    private static void doCatchException(Connection connexion, Throwable th) {
        // لغو تراکنش
        try {
            if (connexion != null) {
                connexion.rollback();
            }
        } catch (SQLException e2) {
            // پردازش استثنا
        }
}
  • خطوط ۴–۶: تراکنش رول‌بک می‌شود. این کار آن را خاتمه می‌دهد و در این صورت SGBD قادر خواهد بود منابع تخصیص‌یافته به خود را آزاد کند؛

6.3.4. مرحله ۳ – صدور دستورات 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=?");
            // دسته‌بندی ۱
            ps.setInt(1, 10);
            // اجرا
            int nbLignes=ps.executeUpdate();
            // تثبیت تراکنش
            connexion.commit();
        } catch (SQLException e1) {
            // پردازش استثنا
            doCatchException(connexion, e1);
        } finally {
            //بلوک finally اجرا می‌شود
            doFinally(null, ps, connexion);
        }
    }
  • خط ۹: اتصال هم برای خواندن و هم برای نوشتن استفاده می‌شود؛
  • خط ۱۱: یک [PreparedStatement] با ۱ پارامتر (که با ؟ نشان داده شده است). ممکن است چندین پارامتر وجود داشته باشد. شماره‌گذاری آن‌ها از ۱ شروع می‌شود؛
  • خط ۱۳: مقدار آن به پارامتر واحد تخصیص داده می‌شود. اولین پارامتر [setType] موقعیت پارامتر در داخل [PreparedStatement] (۱، ۲، ...) و دومی مقداری است که به آن تخصیص داده شده است. می‌توان از متدهای [setInt, setLong, setFloat, setDouble, setString, setDate, ...] استفاده کرد؛
  • خط 15: از متد [executeUpdate] استفاده می‌شود، نه [executeQuery] که برای دستورات SELECT رزرو شده است. این متد تعداد خطوط تحت تأثیر عملیات را برمی‌گرداند. این ممکن است 0 باشد.
  • خط ۱۷: تراکنش commit می‌شود؛

6.3.5. مرحله ۴ – بستن اتصال

در یک محیط چندکاربره، یک اتصال باید در اسرع وقت بسته شود، زیرا یک تراکنش 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. پروژه اکلیپس

  

این پروژه یک پروژه 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>
  • خطوط ۸–۱۲: درایور JDBC برای SGBD و MySQL5؛
  • خطوط ۱۳–۱۷: کتابخانه‌ای قادر به پردازش jSON (نمایش شیء در جاوااسکریپت) (بخش ۲۲.۶ را ببینید). از این برای نمایش محصولات از پایگاه داده در قالب jSON استفاده خواهیم کرد؛

6.4.4. کلاس محصول

کلاس [Produit] به شرح زیر است:


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;

    // سازنده‌ها
    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;
    }

    // گیرنده و تنظیم‌کننده
    ...

    // به رشته
    public String toString() {
        try {
            return new ObjectMapper().writeValueAsString(this);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }
}
  • خط ۳۴: کتابخانه 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) {
            // پیام خطای throwable
            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));
        }
    }
}
  • خطوط ۸–۱۹: لیست خطاها را که در یک شیء از نوع [Throwable] جای گرفته است، بازیابی می‌کند، که این نوع، کلاس مادر [Exception] است؛
  • خطوط ۲۱–۲۸: فهرستی از پیام‌ها را روی صفحه نمایش می‌دهد؛

این کد می‌تواند در کلاس اصلی باشد زیرا در این مورد، تنها کلاسی است که از آن استفاده می‌کند. با این حال، ما سناریوی گسترده‌تری را در نظر می‌گیریم که در آن کلاس‌های دیگر ممکن است به این کد نیاز داشته باشند.

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);
            // دسته‌بندی ۱
            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] نتایج زیر را تولید می‌کند:

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"}
{"id":3,"nom":"NOM3","categorie":1,"prix":102.0,"description":"DESC3"}
{"id":4,"nom":"NOM4","categorie":1,"prix":103.0,"description":"DESC4"}
{"id":5,"nom":"NOM5","categorie":2,"prix":104.0,"description":"DESC5"}
{"id":6,"nom":"NOM6","categorie":2,"prix":105.0,"description":"DESC6"}
{"id":7,"nom":"NOM7","categorie":2,"prix":106.0,"description":"DESC7"}
{"id":8,"nom":"NOM8","categorie":2,"prix":107.0,"description":"DESC8"}
{"id":9,"nom":"NOM9","categorie":2,"prix":108.0,"description":"DESC9"}
{"id":10,"nom":"NOM10","categorie":3,"prix":109.0,"description":"DESC10"}
Liste des produits : 
{"id":1,"nom":"NOM1","categorie":1,"prix":110.0,"description":"DESC1"}
{"id":2,"nom":"NOM2","categorie":1,"prix":111.0,"description":"DESC2"}
{"id":3,"nom":"NOM3","categorie":1,"prix":112.0,"description":"DESC3"}
{"id":4,"nom":"NOM4","categorie":1,"prix":113.0,"description":"DESC4"}
{"id":5,"nom":"NOM5","categorie":2,"prix":104.0,"description":"DESC5"}
{"id":6,"nom":"NOM6","categorie":2,"prix":105.0,"description":"DESC6"}
{"id":7,"nom":"NOM7","categorie":2,"prix":106.0,"description":"DESC7"}
{"id":8,"nom":"NOM8","categorie":2,"prix":107.0,"description":"DESC8"}
{"id":9,"nom":"NOM9","categorie":2,"prix":108.0,"description":"DESC9"}
{"id":10,"nom":"NOM10","categorie":3,"prix":109.0,"description":"DESC10"}
Les erreurs suivantes se sont produites lors de l'ajout : 
- Duplicate entry '100' for key 'PRIMARY'
Liste des produits : 
{"id":1,"nom":"NOM1","categorie":1,"prix":110.0,"description":"DESC1"}
{"id":2,"nom":"NOM2","categorie":1,"prix":111.0,"description":"DESC2"}
{"id":3,"nom":"NOM3","categorie":1,"prix":112.0,"description":"DESC3"}
{"id":4,"nom":"NOM4","categorie":1,"prix":113.0,"description":"DESC4"}
{"id":5,"nom":"NOM5","categorie":2,"prix":104.0,"description":"DESC5"}
{"id":6,"nom":"NOM6","categorie":2,"prix":105.0,"description":"DESC6"}
{"id":7,"nom":"NOM7","categorie":2,"prix":106.0,"description":"DESC7"}
{"id":8,"nom":"NOM8","categorie":2,"prix":107.0,"description":"DESC8"}
{"id":9,"nom":"NOM9","categorie":2,"prix":108.0,"description":"DESC9"}
{"id":10,"nom":"NOM10","categorie":3,"prix":109.0,"description":"DESC10"}
Travail terminé

6.5. استفاده از یک منبع داده از نوع [DataSource]

اکنون با استفاده از یک منبع داده از نوع [javax.sql.DataSource] به برنامه قبلی بازمی‌گردیم:

Image

ما از یک منبع داده استفاده خواهیم کرد که توسط کلاس [org.apache.tomcat.jdbc.pool.DataSource] پیاده‌سازی شده است. این کلاس از یک استخر اتصال، یعنی مجموعه‌ای از اتصالات باز، استفاده می‌کند:

  • هنگامی که استخر ایجاد می‌شود، تعدادی اتصال به پایگاه داده باز می‌شوند. این تعداد قابل تنظیم است؛
  • وقتی کد جاوا یک اتصال را باز می‌کند، این اتصال توسط پول فراهم می‌شود؛
  • وقتی کد جاوا یک اتصال را می‌بندد، آن به استخر بازگردانده می‌شود؛

در نهایت، اتصالات تنها یک بار باز می‌شوند که این امر عملکرد دسترسی به پایگاه داده را بهبود می‌بخشد. منبع داده در یک کلاس پیکربندی Spring تعریف خواهد شد

6.5.1. پروژه اکلیپس

  

این پروژه یک پروژه 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>
        <!-- اسپرینگ -->
        <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>
  • خطوط ۲۱–۲۵: وابستگی به Spring;
  • خطوط ۲۷–۳۱: وابستگی به کتابخانه‌ای که منبع داده را فراهم می‌کند؛

کلاس پیکربندی 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;
    }
}
  • خطوط ۱۱–۱۹: ثابت‌هایی که قبلاً در [IntroJdbc01] تعریف شده بودند به [AppConfig] منتقل شده‌اند؛
  • خطوط ۳۱–۳۴: بیان اسپرینگ که منبع داده را تعریف می‌کند؛
  • خط 24: ایجاد منبع داده که هنوز پیکربندی نشده است؛
  • خطوط ۲۶–۲۹: اطلاعاتی که امکان اتصال منبع داده به پایگاه داده را فراهم می‌کند؛
  • خط ۳۱: یک استخر با ۱ اتصال ایجاد می‌کند. در اینجا به بیش از این نیازی نداریم. هرگز چندین اتصال همزمان وجود ندارد؛

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);
        }
    }
...
}
  • خط ۱۴: منبع داده. توجه داشته باشید که از نوع [javax.sql.DataSource] است که یک رابط (interface) است؛
  • خط ۱۸: نمونه‌سازی اشیاء Spring؛
  • خط ۲۰: به دست آوردن یک مرجع به منبع داده. توجه داشته باشید که کلاسی که در واقع استفاده می‌شود هرگز ذکر نشده است. بنابراین، در اینجا هیچ چیزی دال بر این نیست که یک پیاده‌سازی از نوع [TomcatJdbc] در حال استفاده است؛
  • خط ۳۶: دریافت یک اتصال باز؛
  • بقیه کد با کد کلاس [IntroJdbc01] یکسان است؛

6.6. Conclusion

اطلاعات بیشتر در مورد مدیریت پایگاه داده را می‌توان در سند [Exploiter une base relationnelle avec l'écosystème Spring] یافت.