Skip to content

6. [Cours]: API'e Giriş JDBC

Anahtar kelimeler: ilişkisel veritabanları, API JDBC, SQLException.

6.1. Support

[support / chap-06] klasörü, bu bölümdeki Eclipse projelerini içerir.

6.2. Architecture

JDBC katmanı (Java DataBase Connectivity), veritabanlarına evrensel erişim sağlayan bir arayüzdür. Bu katman, [DAO] katmanına her zaman aynı arayüzü sunar. SGBD değiştirilirse, sadece JDBC sürücüsünü değiştirmek yeterlidir. [DAO] katmanı değişmez.

6.3. Bir veritabanını işletmenin adımları

Yukarıdaki mimaride, konsol programı tarafından bir veritabanının çalıştırılması aşağıdaki adımları içerir:

  1. veritabanının JDBC sürücüsünün yüklenmesi;
  2. veritabanıyla bağlantı kurulması;
  3. veritabanına SQL komutunun gönderilmesi ve SQL komutunun sonuçlarının işlenmesi;
  4. bağlantının kapatılması;
  1. adım yalnızca bir kez gerçekleştirilir. 2-4. adımlar tekrar tekrar gerçekleştirilir. Bağlantının açık bırakılmadığına dikkat edilmelidir. Artık ihtiyaç kalmadığında bağlantı kapatılır.

6.3.1. 1. adım - JDBC sürücüsünün belleğe yüklenmesi

Kod


        // sürücünün yüklenmesi JDBC
        try {
            Class.forName(nom de la classe du pilote JDBC);
        } catch (ClassNotFoundException e1) {
             // istisna işleme
}
  1. satırdaki işlemin amacı, veritabanından JDBC sürücüsünü belleğe yüklemektir. Bu işlemin yalnızca bir kez yapılması yeterlidir. Ancak tekrarlanması hata oluşturmaz. JDBC sürücüsünün sınıfı, projenin Classpath'inde aranır. Bu nedenle, Eclipse projesinde, JDBC sürücü sınıfını içeren [jar] dosyasının projenin Classpath'ine eklenmiş olması gerekir.

6.3.2. 2. adım - bağlantı açma

JDBC sürücüsü yerleştirildikten sonra, BD ile bir bağlantı açması istenir:

Kod


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 {
            // bağlantı açma
            connexion = DriverManager.getConnection(url, user, passwd);
...
        } catch (SQLException e1) {
            // istisna işleniyor
            ...
        } finally {
         // bağlantıyı kapat
         if (connexion != null) {
            try {
                connexion.close();
            } catch (SQLException e2) {
                // istisnayı işleme
                ...
            }
         }
}
  • 3-7. satırlar: JDBC arayüzünün uygulama sınıflarının tümü [java.sql] paketindedir. Ayrıca, hata durumunda hepsi [SQLException] türünde bir istisna atar (satır 19, 27). Bu istisna, [Exception] sınıfından türetilmiştir ve “kontrollü” bir istisnadır: bunu yönetmek için bir try/catch bloğu eklemek zorunludur; alternatif olarak, istisnayı yönetmemek ve yöntemin [throws SQLException] ile yöntem imzasını tamamlayarak istisnayı dışarı çıkarmasını belirtmek de mümkündür;
  • 17. satır, [DriverManager.getConnection], üç parametre bekleyen statik bir yöntemdir:
    • [url]: Veritabanındaki URL. Bu, kullanılan BD'e bağlı bir karakter dizisidir. MySQL için bu, [jdbc:mysql://localhost:3306/nom_de_la_bd] biçimindedir;
    • [user]: bağlantının sahibi;
    • [passwd]: şifresi;
  • 24-30. satırlar: istisna olsun ya da olmasın bağlantının kapatılması için [finally] cümlesinde bağlantının kapatılması gerekir.

6.3.3. 3. adım - SQL ve [SELECT] komutlarının verilmesi

Bağlantı kurulduktan sonra, SQL komutları verilebilir. [SELECT] okuma komutlarının yönetilme şekli, [UPDATE, INSERT, DELETE] güncelleme işlemleri için kullanılan yöntemden farklıdır. Öncelikle SQL ve [SELECT] komutlarıyla başlayalım:

Kod


Connection connexion = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // bağlantı açma
            connexion = DriverManager.getConnection(url, user, passwd);
            // işlem başlangıcı
            connexion.setAutoCommit(false);
            // salt okunur modda
            connexion.setReadOnly(true);
            // [PRODUITS] tablosu okunuyor
            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)));
            }
            // işlemi kaydet
            connexion.commit();
        } catch (SQLException e1) {
            // istisna işleniyor
             doCatchException(connexion,e1);
        } finally {
            // finally bloğu işleniyor
            doFinally(rs, ps, connexion);
        }

    private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
....
}
  • 8. ve 10. satırlar: salt okunur modda (10. satır) bir işlem açılır (8. satır). Bir işlem, ya hepsi başarılı olur ya da hepsi başarısız olur şekilde bir dizi SQL emrinden oluşur. Dolayısıyla, N adet SQL emri içeren bir işlemde, I+1 numaralı emir başarısız olursa, önceki I emir iptal edilir. Okuma işlemi için işlem açılması gerekli değildir. Bununla birlikte, salt okunur bir işlem açmak, bazı SGBD emirlerinin belirli optimizasyonlar yapmasına olanak sağlayabilir;
  • 12. satır: bir [PreparedStatement] kullanımı. Bir [PreparedStatement]'in parametreleri normalde ? karakteriyle gösterilir. Burada ise yoktur. Bir [PreparedStatement], SGBD tarafından hazırlanan bir emirdir. Bu hazırlık bir maliyete sahiptir ve yalnızca bir kez yapılır. Ardından bu hazırlanan emir, SGBD tarafından, "?" ile gösterilen formal parametrelerin yerini alacak farklı etkin parametrelerle yürütülür. Tüm sütunları almak için * notasyonunu kullanmak yerine, istenen sütunları isimlendirmek daha tercih edilebilir. Sütunların adlarını belirterek, daha sonra SELECT sorgusundaki konumlarından değerlerini elde edebiliriz;
  • 13. satır: [PreparedStatement] sorgusunun yürütülmesi. [ResultSet] türünde bir nesne elde edilir;

[ResultSet] türündeki bir nesne, bir tabloyu, yani satır ve sütunlardan oluşan bir kümeyi temsil eder. Herhangi bir anda, tabloda yalnızca “geçerli satır” olarak adlandırılan bir satıra erişilebilir. [ResultSet]’in ilk oluşturulduğu sırada geçerli satır yoktur. Bunu elde etmek için [ResultSet.next()] işlemini gerçekleştirmek gerekir. next yönteminin imzası şöyledir:

    boolean next()

Bu yöntem, [ResultSet]'in bir sonraki satırına geçmeye çalışır ve başarılı olursa true'i, aksi takdirde false'i döndürür. Başarılı olması durumunda, bir sonraki satır yeni geçerli satır olur. Önceki satır kaybolur ve geri dönüp onu kurtarmak mümkün olmaz.

[ResultSet] tablosunda, yürütülen [SELECT] sorgusunda belirtilen labelCol1, labelCol2,... adlı sütunlar bulunur. Aşağıdaki sorgu ile:

SELECT ID as myId, NOM as myNom, CATEGORIE as myCategorie, PRIX as myPrix, DESCRIPTION as myDescription FROM PRODUITS
  • [ID] sütunu, [ResultSet] tablosunda [myId] adlı sütuna aktarılacaktır;
  • [NOM] sütunu, [ResultSet] tablosundaki [myNom] adlı sütuna aktarılacaktır;
  • ...

Yukarıda, [myCol] tanımlayıcıları sütun etiketleri olarak adlandırılır. Bu etiketler yoksa, [ResultSet]'in sütun adları SGBD'e bağlıdır. [SELECT] tek bir tablo üzerinde işlem yaptığında, sütun etiketleri varsayılan olarak SELECT tarafından istenen sütun adları olacaktır. Sorun, [SELECT]'in birden fazla tablo üzerinde çalışması ve bu tablolarda aşağıdaki örnekte olduğu gibi aynı sütun adlarının bulunması durumunda ortaya çıkar:

SELECT PRODUITS.NOM, CATEGORIES.NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID

[PRODUITS] tablosunun, [CATEGORIES] tablosuna, [Produits] ilişkisi ile temsil edilen bir yabancı anahtara sahip olduğunu varsayalım.CATEGORIE_ID --> [CATEGORIES].ID ve [PRODUITS] ile [CATEGORIES] tablolarının her ikisinde de [NOM] adlı bir alan bulunmalıdır. Bu durumda, [ResultSet]'te [PRODUITS.NOM] ve [CATEGORIES.NOM] sütunlarına verilen adlar, SGBD'e bağlıdır. SGBD ile veri aktarımı için burada sütun etiketleri kullanılmalıdır ve şu şekilde yazılmalıdır:


SELECT PRODUITS.NOM as p_NOM, CATEGORIES.NOM as c_NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID

[ResultSet] dosyasının geçerli satırındaki çeşitli alanları işlemek için aşağıdaki yöntemler kullanılabilir:

Type getType("labelColi") 

bu, geçerli satırdaki “labelColi” adlı sütunu ve dolayısıyla bu etikete sahip [SELECT] sütununu elde etmek içindir. Type, coli alanının türünü belirtir. Aşağıdaki [getType] yöntemleri kullanılabilir: getInt, getLong, getString, getDouble, getFloat, getDate, ... Sütun adını kullanmak yerine, yürütülen [SELECT] sorgusundaki konumunu kullanabilirsiniz:

Type getType(i) 

burada i, istenen sütunun indeksidir (i>=1).

  • 15-17. satırlar: BD sorgusunda okunan değerlerin alınması;
  • 19. satır: İşlem onaylanır (diğer bir deyişle, taahhüt edilir). Bu, işlemi sonlandırır ve SGBD'in işlem için ayırdığı kaynakları serbest bırakır;
  • satır 25: [finally]'te kaynaklar serbest bırakılır. Bu işlem, aşağıdaki [doFinally] yöntemini çağırır:

private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
        // ResultSet kapatılıyor
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e1) {

            }
        }
        // kapatma [PreparedStatement]
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e2) {

            }
        }
        if (connexion != null) {
            try {
                // bağlantıyı kapat
                connexion.close();
            } catch (SQLException e3) {
                 // istisnayı işleme
            }
        }
    }
  • 3-9. satırlar: [ResultSet]'in kapatılması;
  • satır 11-17: [PreparedStatement]'in kapatılması;
  • satır 18-27: bağlantının kapatılması;

3-17. satırlardaki kapatma işlemleri, 18-25. satırlarda bağlantının kapatıldığı için gereksiz görünebilir. Aslında, bazı durumlarda bunlar gereksiz değildir ve [http://stackoverflow.com/questions/4507440/must-jdbc-resultsets-and-statements-be-closed-separately-although-the-connection] olarak bırakılması tavsiye edilir.

  • 22. satır: istisna, aşağıdaki [doCatchException] yöntemi ile işlenir:

    private static void doCatchException(Connection connexion, Throwable th) {
        // işlemi iptal et
        try {
            if (connexion != null) {
                connexion.rollback();
            }
        } catch (SQLException e2) {
            // istisnayı işleme
        }
}
  • 4-6. satırlar: İşlem iptal edilir. Bu, işlemi sonlandırır ve SGBD, işlem için ayrılan kaynakları serbest bırakabilir;

6.3.4. 3. adım - SQL ve [INSERT, UPDATE, DELETE] komutlarının verilmesi

SQL ve [INSERT, UPDATE, DELETE] komutları güncelleme işlemleridir: veritabanını değiştirirler ancak herhangi bir satır döndürmezler. Döndürülen tek bilgi, güncelleme işleminden etkilenen satır sayısıdır.

Kod


Connection connexion = null;
        PreparedStatement ps = null;
        try {
            // bağlantı açma
            connexion = DriverManager.getConnection(url, user, passwd);
            // işlemi başlat
            connexion.setAutoCommit(false);
            // okuma/yazma modunda
            connexion.setReadOnly(false);
            // tablo güncelleniyor
            ps = connexion.prepareStatement("UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?");
            // kategori 1
            ps.setInt(1, 10);
            // yürütme
            int nbLignes=ps.executeUpdate();
            // işlemi kaydet
            connexion.commit();
        } catch (SQLException e1) {
            // istisna işleniyor
            doCatchException(connexion, e1);
        } finally {
            // finally bloğu işleniyor
            doFinally(null, ps, connexion);
        }
    }
  • 9. satır: bağlantı okuma ve yazma için kullanılır;
  • 11. satır: 1 parametreli (? ile gösterilen) bir [PreparedStatement]. Birden fazla parametre olabilir. Parametreler 1'den başlayarak numaralandırılır;
  • 13. satır: Tek parametreye değeri atanır. [setType]'in ilk parametresi, [PreparedStatement] içindeki parametrenin konumudur (1, 2, ...) ve ikincisi ise ona atanan değerdir. [setInt, setLong, setFloat, setDouble, setString, setDate, ...] yöntemleri kullanılabilir;
  • 15. satır: SELECT emirleri için ayrılmış olan [executeQuery] yöntemi yerine [executeUpdate] yöntemi kullanılır. Yöntem, işlemden etkilenen satır sayısını döndürür. 0 olabilir.
  • 17. satır: İşlem onaylanır;

6.3.5. 4. adım - bağlantının kapatılması

Çoklu kullanıcı ortamında bir bağlantı mümkün olduğunca çabuk kapatılmalıdır, çünkü SGBD komutu sınırlı sayıda açık bağlantıyı kabul eder. Önceki örneklerde, istisna olsun ya da olmasın bağlantının kapatılması için SQL işlemlerinin [finally] cümlesinde kapatılıyordu.

6.4. Örnek bir proje

6.4.1. Destek

[support / chap5] klasörü, bu bölümdeki [1, 2] Eclipse projelerini içerir. [database] klasörü, bu bölümdeki MySQL örnek veritabanını oluşturmaya yarayan SQL komut dosyasını içerir ([1, 3]).

6.4.2. Kullanılan veritabanı

Aşağıdaki örneklerde şu MySQL veritabanı kullanılmaktadır:

 
  • [ID]: AUTO_INCREMENT modunda birincil anahtar (birincil anahtar belirtilmezse, SGBD tarafından oluşturulur);
  • [NOM]: ürün adı - benzersiz;
  • [CATEGORIE]: ürünün kategori numarası;
  • [PRIX]: ürünün fiyatı;
  • [DESCRIPTION]: ürün açıklaması;

Bunu, [WampServer] aracıyla şu şekilde oluşturacağız: [1-9]:

6.4.3. Eclipse Projesi

  

Proje, aşağıdaki [pom.xml] dosyasıyla tanımlanan bir Maven projesidir:


<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. satırlar: JDBC sürücüsü, SGBD MySQL5;
  • 13-17. satırlar: jSON (Javascript Object Notation) formatını işleyebilen bir kütüphane (bkz. paragraf 22.6). Bunu, veritabanındaki ürünleri jSON biçiminde görüntülemek için kullanacağız;

6.4.4. Ürün sınıfı

[Produit] sınıfı şu şekildedir:


package istia.st.jdbc;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Produit {

    // alanlar
    private int id;
    private String nom;
    private int categorie;
    private double prix;
    private String description;

    // yapıcılar
    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;
    }

    // alıcı ve ayarlayıcılar
    ...

    // String'e dönüştürme
    public String toString() {
        try {
            return new ObjectMapper().writeValueAsString(this);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }
}
  • 34. satır: Ürünün jSON dizesini görüntülemek için jSON kütüphanesi kullanılır. Bu, aşağıdakine benzer bir görüntü sağlar:
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"}

Yukarıdaki [toString] yönteminin avantajı, sınıfa alanlar eklenip çıkarılsa bile, [toString] yönteminin her zaman geçerli kalmasıdır. Ayrıca, alanların kendisi nesneler ise (listeler, tablolar, sözlükler, kullanıcı nesneleri), jSON kütüphaneleri bunları da jSON dizesine dönüştürebilir;

6.4.5. [Static] sınıfı

[Static] sınıfı, ana sınıfta sıkça kullanılan kodları yöntemler halinde bir araya getirir:


package istia.st.jdbc;

import java.util.ArrayList;
import java.util.List;

public class Static {

    public static List<String> getErreursFromThrowable(Throwable th) {
        // istisnanın hata mesajları listesi alınır
        List<String> erreurs = new ArrayList<String>();
        while (th != null) {
            // throwable'ın hata mesajı
            erreurs.add(th.getMessage());
            // throwable'ın nedenine geçilir
            th = th.getCause();
        }
        // sonuç
        return erreurs;
    }
    
    public static void show(String title, List<String> messages){
        // başlık
        System.out.println(String.format("%s : ",title));
        // mesajlar
        for(String message : messages){
            System.out.println(String.format("- %s",message));
        }
    }
}
  • 8-19. satırlar: [Exception] sınıfının üst sınıfı olan [Throwable] türündeki bir nesne içinde kapsüllenmiş hata listesini elde etmeyi sağlar;
  • 21-28. satırlar: bir mesaj listesini ekrana görüntüler;

Bu kod, ana sınıfta yer alabilir çünkü burada onu kullanan tek sınıf budur. Burada, diğer sınıfların da bu koda ihtiyaç duyabileceği daha geniş bir durum ele alınmaktadır.

6.4.6. Ana sınıfın iskeleti


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 {

    // sabitler
    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) {
        // MySQL'ten JDBC sürücüsünün yüklenmesi
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e1) {
            doCatchException("Pilote JDBC introuvable", null, e1);
            return;
        }
        // [PRODUITS] tablosu boşaltılıyor
        delete();
        // tablo dolduruluyor
        insert();
        // tablo okunuyor
        select();
        // güncelleniyor
        update();
        // görüntüleme
        select();
        // iki özdeş öğenin eklenmesi
        // işlem nedeniyle ekleme başarısız olmalı ve iki öğeden hiçbiri eklenmemelidir
        insert2();
        // kontrol ediliyor
        select();
        // bitti
        System.out.println("Travail terminé");
    }

    // ürün listesi
    private static void select() {
...
    }

    // ürün silme
    public static void delete() {
...
    }

    // ürün ekleme
    public static void insert() {
...
    }

    // 2 ürün ekleniyor
    public static void insert2() {
...
    }

    // bazı ürünlerin güncellenmesi
    public static void update() {
..
    }

    private static void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
        // kapatma ResultSet
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e1) {

            }
        }
        // kapatma [PreparedStatement]
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e2) {

            }
        }
        // bağlantıyı kapat
        if (connexion != null) {
            try {
                connexion.close();
            } catch (SQLException e3) {
                // hata mesajları görüntüleniyor
                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) {
        // hata mesajları görüntüleniyor
        Static.show(title, Static.getErreursFromThrowable(th));
        // işlem iptal edildi
        try {
            if (connexion != null) {
                connexion.rollback();
            }
        } catch (SQLException e2) {
            // hata mesajları görüntüleniyor
            Static.show(title, Static.getErreursFromThrowable(e2));
        }
    }
}

6.4.7. Ürün tablosunun içeriğinin silinmesi

[delete] yöntemi, tablonun içeriğini siler:


// ürünlerin silinmesi
    public static void delete() {
        Connection connexion = null;
        PreparedStatement ps = null;
        try {
            // bağlantı açılıyor
            connexion = DriverManager.getConnection(url, user, passwd);
            // işlem başlangıcı
            connexion.setAutoCommit(false);
            // [PRODUITS] tablosu boşaltılıyor
            ps = connexion.prepareStatement(delete);
            ps.executeUpdate();
            // işlemi kaydet
            connexion.commit();
            // varsayılan moda geri dön
            connexion.setAutoCommit(true);
        } catch (SQLException e1) {
            // istisna işleniyor
            doCatchException("Les erreurs suivantes se sont produites à la suppression du contenu de la table", connexion, e1);
        } finally {
            // finally bloğu işleniyor
            doFinally(null, null, connexion);
        }
    }

Bu örnekte işlemler kullanılmaktadır. Bir işlem, SQL emirlerini gruplandırmaya olanak tanır; bu emirlerin tümü başarılı olmalı veya tümü iptal edilmelidir. Bilinmesi gereken dört işlem vardır:

  • işlemin başlatılması: [connexion.setAutoCommit(false)];
  • işlemin başarıyla tamamlanması: [connexion.commit()]. Bu durumda, işlem sırasında BD üzerinde gerçekleştirilen tüm işlemler onaylanır;
  • işlemin başarısızlıkla sonuçlanması: [connexion.rollback()]. Bu durumda, işlem sırasında BD üzerinde gerçekleştirilen tüm işlemler iptal edilir;
  • API'in varsayılan modu olan [auto-commit] moduna geri dönüş: JDBC : [connexion.setAutoCommit(true)]. Bu modda, her bir SQL emri bir işlem konusu olur. Dolayısıyla, iki ekleme yapıldığında ve ikincisi başarısız olursa:
    • [AutoCommit=true] modunda, ilk ekleme kalır (1. AutoCommit tarafından onaylanmıştır);
    • [AutoCommit=false] modunda ise ilk ekleme işlemi iptal edilir;

Örneklerimizde, bir istisna oluştuğunda her seferinde [doCatchException] yönteminde işlemi iptal ediyoruz:


    private static void doCatchException(String title, Connection connexion, Throwable th) {
        // hata mesajlarını görüntüleme
        Static.show(title, Static.getErreursFromThrowable(th));
        // işlemi iptal et
        try {
            if (connexion != null) {
                connexion.rollback();
            }
        } catch (SQLException e2) {
            // hata mesajları görüntüleniyor
            Static.show("Erreur lors de l'annulation de la transaction", Static.getErreursFromThrowable(e2));
        }
}

6.4.8. Ürün tablosunun içeriğinin oluşturulması

[insert] yöntemi, tablonun içeriğini oluşturur:


// ürünler ekleniyor
    public static void insert() {
        Connection connexion = null;
        PreparedStatement ps = null;
        try {
            // bağlantı açılıyor
            connexion = DriverManager.getConnection(url, user, passwd);
            // işlem başlangıcı
            connexion.setAutoCommit(false);
            // tablo dolduruluyor
            ps = connexion.prepareStatement(insert);
            for (int i = 0; i < 10; i++) {
                // hazırlık
                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));
                // yürütme
                ps.executeUpdate();
            }
            // işlemi kaydet
            connexion.commit();
            // varsayılan moda dönüş
            connexion.setAutoCommit(true);
        } catch (SQLException e1) {
            // istisna işleniyor
            doCatchException("Les erreurs suivantes se sont produites à la création du contenu de la table", connexion, e1);
        } finally {
            // finally bloğunu işleme
            doFinally(null, null, connexion);
        }
    }

6.4.9. Ürün tablosunun içeriğinin görüntülenmesi

[select] yöntemi, tablonun içeriğini görüntüler:


    // ürün listesi
    private static void select() {
        Connection connexion = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // bağlantı açılıyor
            connexion = DriverManager.getConnection(url, user, passwd);
            // işlem başlangıcı
            connexion.setAutoCommit(false);
            // [PRODUITS] tablosu okunuyor
            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)));
            }
            // işlemi kaydet
            connexion.commit();
            // varsayılan moda geri dönülüyor
            connexion.setAutoCommit(true);
        } catch (SQLException e1) {
            // istisna işleniyor
            doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
        } finally {
            // finally bloğu işleniyor
            doFinally(null, null, connexion);
        }
}

6.4.10. Tablo içeriğinin güncellenmesi

[update] yöntemi, belirli ürünleri günceller:


// bazı ürünlerin güncellenmesi
    public static void update() {
        Connection connexion = null;
        PreparedStatement ps = null;
        try {
            // bağlantı açılıyor
            connexion = DriverManager.getConnection(url, user, passwd);
            // işlem başlangıcı
            connexion.setAutoCommit(false);
            // tablo güncelleniyor
            ps = connexion.prepareStatement(update);
            // kategori 1
            ps.setInt(1, 1);
            // işlem yürütülüyor
            ps.executeUpdate();
            // işlemi kaydet
            connexion.commit();
            // varsayılan moda dönüş
            connexion.setAutoCommit(true);
        } catch (SQLException e1) {
            // istisna işleniyor
            doCatchException("Les erreurs suivantes se sont produites à la mise à jour du contenu de la table", connexion, e1);
        } finally {
            // finally bloğunu işleme
            doFinally(null, null, connexion);
        }
    }

6.4.11. İşlemin rolü

[insert2] yöntemi, tabloya aynı birincil anahtara sahip iki ürünü ekler; bu ise mümkün değildir. Bir işlem içinde olduğumuzdan, ilk ekleme işlemi iptal edilecektir.


// aynı biri ncil anahtarlara sahip 2 ürünün eklenmesi
    public static void insert2() {
        Connection connexion = null;
        PreparedStatement ps = null;
        try {
            // bağlantı açılıyor
            connexion = DriverManager.getConnection(url, user, passwd);
            // işlem başlatılıyor
            connexion.setAutoCommit(false);
            // 1 satır ekleniyor
            ps = connexion.prepareStatement(insert2);
            // işlem yürütülüyor
            ps.executeUpdate();
            // aynı satır ikinci kez ekleniyor, dolayısıyla aynı birincil anahtarla
            // işlem nedeniyle ekleme başarısız olmalı ve her iki öğe de eklenmemelidir
            ps.executeUpdate();
            // işlemi tamamla
            connexion.commit();
            // varsayılan moda geri dönülür
            connexion.setAutoCommit(true);
        } catch (SQLException e1) {
            // istisna işlenir
            doCatchException("Les erreurs suivantes se sont produites lors de l'ajout", connexion, e1);
        } finally {
            // finally bloğu işleniyor
            doFinally(null, null, connexion);
        }
    }

6.4.12. Sonuçlar

[main] yönteminin çalıştırılması sonucunda aşağıdaki sonuçlar elde edilir:

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] türünde bir veri kaynağının kullanılması

[javax.sql.DataSource] türünde bir veri kaynağı kullanarak önceki uygulamayı tekrar ele alacağız:

Image

[org.apache.tomcat.jdbc.pool.DataSource] sınıfı tarafından uygulanan bir veri kaynağını kullanacağız. Bu sınıf, bir bağlantı havuzu, yani açık bağlantı kümesi kullanır:

  • havuz oluşturulduğunda, veritabanına belirli sayıda bağlantı açılır. Bu sayı yapılandırılabilir;
  • Java kodu bir bağlantı açtığında, bu bağlantı havuzdan sağlanır;
  • Java kodu bir bağlantıyı kapattığında, bu bağlantı havuza geri verilir;

Sonuç olarak, bağlantılar yalnızca bir kez açılır ve bu da veritabanına erişim performansını artırır. Veri kaynağı, bir Spring yapılandırma sınıfında tanımlanacaktır

6.5.1. Eclipse projesi

  

Proje, aşağıdaki [pom.xml] dosyasıyla tanımlanan bir Maven projesidir:


<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 kütüphanesi -->
        <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. satırlar: Spring bağımlılığı;
  • 27-31. satırlar: veri kaynağını sağlayan kütüphaneye bağımlılık;

Spring yapılandırma sınıfı [AppConfig] şu şekildedir:


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 {

    // sabitler
    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() {
        // veri kaynağı TomcatJdbc
        DataSource dataSource = new DataSource();
        // erişim yapılandırması JDBC
        dataSource.setDriverClassName(DRIVER_CLASSNAME);
        dataSource.setUsername(USER);
        dataSource.setPassword(PASSWD);
        dataSource.setUrl(URL);
        // başlangıçta açık bir bağlantı
        dataSource.setInitialSize(1);
        // sonuç
        return dataSource;
    }
}
  • 11-19. satırlar: Daha önce [IntroJdbc01]'te tanımlanan sabitler, [AppConfig]'e taşınmıştır;
  • 31-34. satırlar: Veri kaynağını tanımlayan Spring bean'i;
  • 24. satır: henüz yapılandırılmamış veri kaynağının oluşturulması;
  • satır 26-29: Veri kaynağının veritabanına bağlanmasını sağlayan bilgiler;
  • 31. satır: 1 bağlantılık bir havuz oluşturulur. Burada daha fazlasına gerek yoktur. Asla birden fazla eşzamanlı bağlantı olmaz;

6.5.2. Ana sınıf

[IntroJdbc02] ana sınıfı şu şekildedir:


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 {
    // veri kaynağı
    private static DataSource dataSource;

    public static void main(String[] args) {
        // Spring bağlamının alınması
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        // veri kaynağının alınması
        dataSource = ctx.getBean(DataSource.class);
        // [PRODUITS] tablosu boşaltılıyor
        delete();
...
        // bitti
        ctx.close();
        System.out.println("Travail terminé");
    }

    // ürün listesi
    private static void select() {
        Connection connexion = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // bağlantı açılıyor
            connexion = dataSource.getConnection();
            // işlem başlangıcı
            connexion.setAutoCommit(false);
...
        } catch (SQLException e1) {
            // istisna işleniyor
            doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
        } finally {
            // finally bloğu işleniyor
            doFinally(null, null, connexion);
        }
    }
...
}
  • 14. satır: veri kaynağı. Bunun, bir arayüz olan [javax.sql.DataSource] türünde olduğuna dikkat edin;
  • 18. satır: Spring nesnelerinin örneklenmesi;
  • 20. satır: veri kaynağına ilişkin bir referans alınması. Hiçbir aşamada gerçekte kullanılan sınıfın adı geçmediğine dikkat edilmelidir. Dolayısıyla burada, [TomcatJdbc] uygulamasının kullanıldığına dair hiçbir ipucu yoktur;
  • satır 36: açık bir bağlantının alınması;
  • kodun geri kalanı [IntroJdbc01] sınıfındakiyle aynıdır;

6.6. Conclusion

Veritabanı yönetimi hakkında daha fazla bilgi, [Exploiter une base relationnelle avec l'écosystème Spring] belgesinde bulunabilir.