3. API'e Giriş JDBC
3.1. Çalışma ortamının kurulması
MySQL5 veritabanıyla çalışacağız.
Aşağıdakilere sahip olmanız gerekir:
- JDK (Java Development Kit) kurulu olmalıdır (bölüm 23.1);
- Maven bağımlılık yöneticisini kurmuş olmalısınız (23.2. paragraf);
- IDE Spring Tool Suite (STS) kurulu olmalı (23.3. paragraf);
- SGBD MySQL5 (23.4. paragraf) ve istemcisi EMS MyManager (23.5. paragraf) kuruldu;
- [http://tahe.developpez.com/java/spring-database] belgesindeki kodları indirdi;
Bundan sonra, MySQL5 yöneticisinin root olduğu ve root şifresini kullandığı varsayılmaktadır. SGBD, MySQL5 ve istemcisi [MyManager]'i başlatın. [MyManager] komutunu kullanarak [dbproduits] ve [1-34] veritabanlarını oluşturuyoruz:
![]() |
- [3]'te, veritabanının adı [dbproduits] olmalıdır;
![]() |
- [8-9]'e, root kullanıcı adı ve root şifresiyle (yukarıdaki ekran görüntüsünde gösterilmeyen);
![]() |
- [14a]'e, şifre yine root'tir (bu, ekran görüntüsünde gösterilmemiştir);
- [15]'te, [dbproduits] veritabanı oluşturuldu;
![]() |
- [20]'te, seçilen veritabanına dikkat edin. Bu, [dbproduits] veritabanı olmalıdır;
![]() |
- [22] olarak değiştirin; dizin <örnekler>/spring-database-config/mysql/databases şeklindedir; burada <örnekler>, indirilen örneklerin bulunduğu dizindir;
- [23]'te, SQL ve [dbproduits.sql] komut dosyalarını seçin. Bu, [dbproduits] veritabanında [PRODUITS] tablosunu oluşturacaktır;
![]() |
![]() |
- [30]'te, [produits] tablosu oluşturuldu;
![]() |
- [33]'e, [produits] tablosunun sütunları;
![]() |
- [34]'e dönüştürüldü; bu tablo başlangıçta boştur;
Şimdi STS ile aşağıdaki projeleri içe aktarın (<exemples>/spring-core klasöründeki projeler için kullanılan adımları izleyin):
![]() |
- [2]'e, [mysql-config-jdbc] projesi [<exemples>/spring-database-config/mysql/eclipse/mysql-config-jdbc] [1] klasöründe bulunacaktır;
Bu proje, aşağıdaki mimarinin JDBC katmanını yapılandırır:
![]() |
Ardından, aşağıdaki üç projeyi yeniden içe aktarın:
![]() |
- [2]'e; projeler [<exemples>/spring-database-config/spring-jdbc] ve [1] klasörlerinde bulunacaktır;
Bu üç proje, [mysql-config-jdbc] adlı Maven projesini kullanan Maven projeleridir. Bu son proje, aşağıdaki Maven artefaktını oluşturur (bkz. pom.xml):
<groupId>dvp.spring.database</groupId>
<artifactId>generic-config-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
Aynı artefakt, [oracle-config-jdbc, db2-config-jdbc, ...] projesi tarafından da oluşturulacaktır. Şu anda STS projesine yüklü olan [spring-generic-jdbc-*] projelerinin, [mysql-config-jdbc] projesini kullandığından emin olmak için:
- aynı anda başka bir [sgbd-config-jdbc] projesinin yüklenmediğinden emin olun. Aksi takdirde anlaşılması zor hatalar ortaya çıkabilir;
- yüklü projelerin Maven yapılandırmasını aşağıdaki şekilde güncelleyin:
![]() |
![]() |
Yapılandırmanızı kontrol etmek için [spring-jdbc-generic-01.IntroJdbc01] ve [1-3] çalıştırma yapılandırmalarını çalıştırın:
![]() |
Aşağıdaki konsol sonuçlarını almalısınız:
Aşağıdaki örneklerde okuyucu şunları yapabilir:
- ya daha önce yüklenen projelerle doğrudan çalışabilir;
- ya da projeleri kendisi oluşturabilir;
3.2. Bir veritabanını kullanma adımları
![]() |
Yukarıdaki mimaride, konsol programı tarafından bir veritabanının çalıştırılması aşağıdaki adımları içerir:
- veritabanının JDBC sürücüsünün yüklenmesi;
- veritabanıyla bağlantı kurulması;
- veritabanına SQL komutunun gönderilmesi ve SQL komutunun sonuçlarının işlenmesi;
- bağlantının kapatılması;
- 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.
3.2.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
}
- 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ün sınıfını içeren [jar] dosyasının projenin Classpath'ine eklenmiş olması gerekir.
3.2.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çılıyor
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ı aktardığı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: [finally] yordamında bağlantı, istisna olsun ya da olmasın, kapatılmalıdır.
3.2.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 normalde ? karakteriyle gösterilen parametreleri vardır. 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şlemi gerçekleştirilmelidir. next yönteminin imzası şöyledir:
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] tablosunda [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]'teki 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:
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:
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ı;
- 18-27. satırlar: bağlantının kapatılması;
3-17. satırlardaki kapatma işlemleri, 18-25. satırlarda bağlantı 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]'i olduğu gibi bırakmanız 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;
3.2.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;
3.2.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 bağlantı kapatılıyordu.
3.3. SGBD ve MySQL5’in JDBC Katmanının Yapılandırılması
Aşağıda, JDBC katmanını yapılandıran [mysql-config-jdbc] projesini inceleyeceğiz:
![]() |
3.3.1. Eclipse projesi
![]() |
3.3.2. Maven Yapılandırması
Projenin [pom.xml] dosyası şu şekildedir:
<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>dvp.spring.database</groupId>
<artifactId>generic-config-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>configuration generic jdbc</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- değişken bağımlılıklar ********************************************** -->
<!-- SGBD'in JDBC sürücüsü -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- sabit bağımlılıklar ********************************************** -->
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<!-- jSON kütüphanesi -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Google Guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>16.0.1</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<!-- Spring Boot Testi -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- günlükler -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
Bu Maven yapılandırmasında, [mysql-config-jdbc] projesine veya bu projeyi temel alacak projelere gerekli olan bir dizi arşiv bir araya getirilmiştir:
- 4-6. satırlar: Proje tarafından oluşturulan Maven artefaktı. Daha önce de belirtildiği gibi, [*-config-jdbc] türündeki tüm projeler aynı artefaktı oluşturur. Bu nedenle, [*-config-jdbc] türündeki iki projenin aynı anda yüklenmemesi gerekir;
- 9-13. satırlar: Bu projenin üst Maven projesi. Spring ekosistemi tarafından kullanılan çok sayıda arşivin sürümlerini tanımlar. Bu sayede, bunlardan türetilen projelerde bu sürümleri belirtmek gerekmez;
- satır 18-21: SGBD ve MySQL5 projelerinin JDBC sürücüsü arşivi. Bu, [spring-jdbc-01] projesi için gerekli tek arşivdir;
- satır 24-27: [tomcat-jdbc] artefaktı, JDBC ve [spring-jdbc-02 à 04] projeleri için gerekli bir arşivi içerir;
- satır 29-36: jSON'in yönetimi için gerekli kütüphaneleri sağlar. Belgedeki neredeyse tüm projelerde kullanılır;
- satır 38-42: Google Guava, koleksiyon yönetimi kütüphanesidir. Belgedeki neredeyse tüm projelerde kullanılır;
- satır 43-52: Spring ve JUnit'i entegre eden testlerin yazılmasını sağlayan kütüphaneler. Belgedeki hemen hemen tüm projelerde kullanılır;
- satır 54-57: Günlük kütüphaneleri. Belgedeki hemen hemen tüm projelerde kullanılmaktadır;
- satır 67-71: [mysql-config-jdbc] projesinin artefaktını yerel Maven deposuna yüklemeye olanak tanıyan eklenti;
3.3.3. [ConfigJdbc] yapılandırma sınıfı
![]() |
[ConfigJdbc] sınıfı şu şekildedir:
package generic.jdbc.config;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
public class ConfigJdbc {
// bağlantı ayarları
public final static String DRIVER_CLASSNAME = "com.mysql.jdbc.Driver";
public final static String URL_DBPRODUITS = "jdbc:mysql://localhost:3306/dbproduits";
public final static String USER_DBPRODUITS = "root";
public final static String PASSWD_DBPRODUITS = "root";
...
// komutlar SQL [jdbc-01, jdbc-02]
public final static String V1_INSERT_PRODUITS_WITH_ID = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?, ?)";
public final static String V1_DELETE_PRODUITS = "DELETE FROM PRODUITS";
//public final static String V1_DELETE_PRODUITS = String.format("DELETE FROM %s", TAB_PRODUITS);
public final static String V1_SELECT_PRODUITS = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
public final static String V1_UPDATE_PRODUITS = "UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?";
public final static String V1_INSERT_PRODUITS_2 = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (100,'X',1,1,'x')";
// emirler SQL [jdbc-03]
public final static String V2_INSERT_PRODUITS = "INSERT INTO PRODUITS(NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?)";
public final static String V2_DELETE_ALLPRODUITS = "DELETE FROM PRODUITS";
public final static String V2_DELETE_PRODUITS = "DELETE FROM PRODUITS WHERE ID=?";
public final static String V2_SELECT_ALLPRODUITS = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
public final static String V2_SELECT_PRODUIT_BYID = "SELECT NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS WHERE ID=?";
public final static String V2_SELECT_PRODUIT_BYNAME = "SELECT ID, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS WHERE NOM=?";
public final static String V2_UPDATE_PRODUITS = "UPDATE PRODUITS SET NOM=?, PRIX=?, CATEGORIE=?, DESCRIPTION=? WHERE ID=?";
...
}
[ConfigJdbc] sınıfı, dört [spring-jdbc-01 à 04] projesinin JDBC katmanını yapılandırmak için kullanılır. Yapılandırmanın büyük bir kısmı [spring-jdbc-04] projesiyle ilgilidir. Bu kısmı, söz konusu projeyi incelerken ele alacağız. Yukarıda yalnızca [spring-jdbc-01 à 03] projelerinin yapılandırması yer almaktadır.
- 14-17. satırlar: MySQL5 ve [dbproduits] veritabanlarına bağlantı parametreleri;
- 20-25. satırlar: [spring-jdbc-01 et 02] projelerinde kullanılan SQL komutları;
- satır 28-34: [spring-jdbc-03] projesinde kullanılan SQL komutları;
Bu SQL komutları, yapısı aşağıdaki gibi olan MySQL5 [dbproduits] veritabanındaki [PRODUITS] tablosunu kullanı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ı;
3.3.4. [Produit] sınıfı
![]() |
[Produit] sınıfı, [PRODUITS] tablosundaki bir satırın görüntüsüdür:
package generic.jdbc.entities.dbproduits;
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
...
}
Daha sonra, iki ürünün eşit olup olmadığını belirlemek için bunları karşılaştırmamız gerekecek. Tüm alanları eşitse, iki ürünün eşit olduğunu söyleyeceğiz. Bunun için, [Produit] sınıfının türetildiği [Object] sınıfındaki [equals] yöntemini yeniden tanımlayacağız:
// eşitlik yöntemi
@Override
public boolean equals(Object o) {
// basit durumlar
if (o == null || o.getClass() != this.getClass()) {
return false;
}
Produit p = (Produit) o;
return this == o
|| (this.id == p.id && this.nom.equals(p.getNom()) && this.categorie == p.categorie
&& Math.abs(this.prix - p.prix) < 1e-6 && this.description.equals(p.description));
}
- 3. satır: [equals] yöntemi, this nesnesiyle karşılaştırması gereken bir o nesnesi alır;
- 5-7. satırlar: iki nesnenin eşit olmadığını hemen söyleyebileceğimiz basit durumlar. [Object].getClass() yöntemi, nesnenin gerçek sınıfını temsil eden [Class] türünde bir örnek sağlar;
- 8. satır: o nesnesi p ürününe dönüştürülür;
- 9. satır: Bir ürüne ait o ve p referansları eşitse, fiziksel olarak aynı üründen söz edilir;
- 9. satır: o ve p, aynı alanlara sahip iki ürüne ait farklı referanslarsa, bunların eşit olduğu söylenir. Fiyatın türü [double] olduğundan ve bilgisayar biliminde gerçek sayıların tam bir temsili bulunmadığından, iki fiyat 10-6'ya kadar yakınsa aynı kabul edilecektir;
Ayrıca, [Object] sınıfının [hasCode] yöntemini yeniden tanımlayacağız:
// hash kodu
@Override
public int hashCode() {
return id + 2 * nom.hashCode() + 3 * categorie + 4 * description.hashCode();
}
[equals] yöntemi bu iki ürünü eşit olarak tanımladıysa, iki ürünün hashCode değerleri aynı olmalıdır. hashCode değeri, nesneleri sözlükler gibi kümelere dağıtmak için kullanılır. Yukarıda, iki ürün aynıysa, hashCode değerleri de aynı olacaktır.
3.3.5. [UncheckedException] istisnası
![]() |
Şu mimariyi ele alalım:
![]() |
- [JDBC] katmanı, [SQLException] türünde istisnalar oluşturur. Bu istisna, en üst katmana, yani buradaki test katmanına ulaşana kadar katmanlar arasında yukarı doğru iletilmelidir;
[DAO] katmanı, [SQLException]’i test katmanına kadar iletmekle yetinebilir. Ancak bu istisna kontrolsüz olduğundan (doğrudan [Exception]'ten kaynaklanmaktadır), bu durum [DAO] katmanındaki [IDao] arayüzünün şu şekilde olması gerektiği anlamına gelir:
public interface IDao {
// ürün ekleme
public List<Produit> addProduits(List<Produit> produits) throws SQLException;
// tüm ürünlerin listesi
public List<Produit> getAllProduits() throws SQLException;
// belirli bir ürün
public Produit getProduitById(int id) throws SQLException;
public Produit getProduitByName(String name) throws SQLException;
// birden fazla ürünü güncelleme
public int updateProduits(List<Produit> produits) throws SQLException;
// tüm ürünleri sil
public int deleteAllProduits() throws SQLException;
// birden fazla ürünü silme
public int deleteProduits(int[] ids) throws SQLException;
}
Ve bu çok can sıkıcıdır, çünkü farklı bir istisna atacak bir sınıf aracılığıyla [IDao] arayüzünü uygulamamızı engeller. Bu sorunu aşmak için, [DAO] katmanı, kontrol edilmeyen bir [DaoException] istisnası ([RuntimeException]'ten türetilmiş) atacak, böylece arayüzün yöntem imzalarında [throws] ifadesini kullanmamız gerekmez. Sonuç olarak, bu arayüz, [DaoException] istisnasından farklı olabilecek bir kontrolsüz istisna atan herhangi bir sınıf tarafından uygulanabilir. Mimari yapımız artık şu şekilde olur:
![]() |
Uygulamanın farklı katmanları için kontrol edilemeyen istisnaların oluşturulmasını kolaylaştırmak amacıyla, bunlar için [UncheckedException] adlı bir üst sınıf oluşturuyoruz:
![]() |
package generic.jdbc.infrastructure;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
// genel istisna sınıfı
// istisna kontrol edilemez
public class UncheckedException extends RuntimeException {
// ID serisi oluşturuldu
private static final long serialVersionUID = -2924871763340170310L;
// özellikler
private int code;
private String trace;
private List<ShortException> exceptions;
// yapıcılar
public UncheckedException() {
super();
}
public UncheckedException(int code, Throwable e, String simpleClassName) {
super(e);
// yerel
this.code = code;
this.exceptions = getErreursForException(e);
// izleme
String fileName = String.format("%s.java", simpleClassName);
StackTraceElement[] traces = e.getStackTrace();
boolean trouve = false;
for (int i = 0; !trouve && i < traces.length; i++) {
StackTraceElement trace = traces[i];
if (fileName.equals(trace.getFileName())) {
this.trace = String.format("[%s,%s,%s]", simpleClassName, trace.getMethodName(), trace.getLineNumber());
trouve = true;
}
}
}
@Override
public String getMessage() {
return this.toString();
}
@Override
public void printStackTrace() {
System.out.println(this);
}
// bir istisnaya ait hata mesajları listesi
private List<ShortException> getErreursForException(Throwable th) {
// istisnanın yığın öğeleri alınır
Throwable cause = th;
List<ShortException> exceptions = new ArrayList<ShortException>();
while (cause != null) {
// geçerli istisnayı alır
exceptions.add(new ShortException(cause.getClass().getName(), cause.getMessage()));
// sonraki istisna
cause = cause.getCause();
}
return exceptions;
}
@Override
public String toString() {
ObjectMapper jsonMapper = new ObjectMapper();
try {
return String.format("[code=%s, trace=%s, exceptions=%s", code, trace, jsonMapper.writeValueAsString(exceptions));
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
// getter ve setter'lar
...
}
- 12. satır: Sınıf, [RuntimeException]'ten türetilmiştir ve dolayısıyla kontrol edilemeyen bir istisna türüdür. Bu sınıf, kontrol edilebilir bir istisnayı (SQLException) kontrol edilemeyen bir istisna türüne (UncheckedException) sarmalamak için kullanılacaktır;
- [UncheckedException] türündeki istisnaları birbirinden ayırt etmek için, bunlara 18. satırdaki özel alan tarafından saklanacak bir kod atanabilir. [UncheckedException] türündeki bir istisnayı yakalayan bir Java kodu, [getCode] yöntemi (80. satır ve sonrası) sayesinde bu hata koduna erişebilir;
- 20. satır: kapsüllenmiş istisnanın yığınındaki hata mesajlarını depolar;
- satır 23-43: [UncheckedException] türünde bir nesneyi oluşturmanın farklı yolları;
- satır 56-67: bir [Throwable] nesnesinden veya türevlerinden, özellikle de [Exception] türünden, satır 20'deki hata listesini oluşturmaya olanak tanıyan özel bir yöntem;
- satır 69-78: [toString] yöntemi, istisnayı temsil eden bir karakter dizisi döndürür. Satır 20'deki hata listesini görüntülemek için jSON kütüphanesini kullanır. Bu kütüphane, projenin Maven bağımlılıklarında bulunur:
<!-- jSON kütüphanesi -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
- 45-48. satırlar: üst sınıf [RuntimeException]'in [getMessage] yöntemini yeniden tanımlar. Bu yöntem burada, sınıfın [toString] imza biçimini döndürür;
- 50-53. satırlar: üst sınıf [RuntimeException]'in [printStackTrace] yöntemini yeniden tanımlar. Burada sınıfın [toString] imzası görüntülenecektir;
[UncheckedException] sınıfı, 20. satırdaki alana, aşağıdaki [ShortException] türüyle tanımlanan istisnalar listesini kaydeder:
package pam.dao.exceptions;
public class ShortException {
// özellikler
private String className;
private String errorMessage;
// oluşturucular
public ShortException() {
}
public ShortException(String className, String errorMessage) {
this.className = className;
this.errorMessage = errorMessage;
}
// getter ve setter'lar
...
}
- 6. satır: Meydana gelen istisnanın sınıf adı;
- 7. satır: ilgili hata mesajı;
[UncheckedException] sınıfının aşağıdaki oluşturucusunu inceleyelim:
public UncheckedException(int code, Throwable e, String simpleClassName) {
super(e);
// yerel
this.code = code;
this.exceptions = getErreursForException(e);
// izleme
String fileName = String.format("%s.java", simpleClassName);
StackTraceElement[] traces = e.getStackTrace();
boolean trouve = false;
for (int i = 0; !trouve && i < traces.length; i++) {
StackTraceElement trace = traces[i];
if (fileName.equals(trace.getFileName())) {
this.trace = String.format("[%s,%s,%s]", simpleClassName, trace.getMethodName(), trace.getLineNumber());
trouve = true;
}
}
}
- 1. satırda parametreler şunlardır:
- [code]: bir hata kodu;
- [e]: kapsanan istisna. [Throwable], [Exception] sınıfının üst sınıfıdır ve doğrudan [Object] sınıfından türetilmiştir. Bu, [throw c;] yazılabilen tüm C sınıflarının üst sınıfıdır; burada c, C sınıfının bir örneğidir;
- [simpleClassName]: e istisnasının tespit edildiği kullanıcı kodunun sınıfının basit adı;
- 4. satır: hata kodu kaydedilir;
- 5. satır: [ShortException] listesi, parametre olarak geçirilen [Throwable e]'ten oluşturulur;
- satır 7-16: istisnanın izleri olarak adlandırılan unsurlar incelenir. İlk istisna, kodun belirli bir yerinde meydana gelir ve ardından istisnanın oluştuğu yöntemi çağıran yönteme doğru yukarı doğru izlenir; bu süreç, bir try / catch bloğu tarafından durdurulana kadar devam eder. Bu yukarı doğru izleme sürecinde, ilk istisna, e istisnasının [e.stackTrace] dizisinde depolanan izler bırakır. Bu izler burada, 8. satırda, parametre olarak geçirilen [Throwable e]'ten elde edilir. [StackTraceElement] türündeki her bir öğe, alanları arasında aşağıdakileri barındıran bir nesnedir:
- [fileName]: istisnanın meydana geldiği Java dosyasının adı;
- [lineNumber]: istisnanın meydana geldiği bu dosyadaki satır numarası;
- [methodName]: istisnanın meydana geldiği bu dosyadaki yöntemin adı;
- 10-16. satırlar, parametre olarak geçirilen istisnanın izleme tablosunda, [trace.fileName==simpleClassName.java] koşulunun ilk oluşumunu arar; burada [simpleClassName], oluşturucunun 3. parametresidir. Buradaki amaç, istisnanın kullanıcı kodunda nerede oluştuğunu kaydetmektir. Kullanıcı kodu, bir istisnayı şu şekilde kapsülleyecektir:
- 13. satır: istisnanın durdurulduğu kullanıcı kodundaki konumu belirten [fileName, methodName, lineNumber] türünde bir karakter dizesi oluşturulur;
Şimdi, önceki oluşturucu tarafından kapsüllenmiş [Throwable th] istisnasının istisna yığınındaki istisna listesini kaydeden kodu inceleyelim:
// bir istisnaya ait hata mesajlarının listesi
private List<ShortException> getErreursForException(Throwable th) {
// istisnanın yığın öğelerini alır
Throwable cause = th;
List<ShortException> exceptions = new ArrayList<ShortException>();
while (cause != null) {
// geçerli istisnayı alır
exceptions.add(new ShortException(cause.getClass().getName(), cause.getMessage()));
// sonraki istisna
cause = cause.getCause();
}
return exceptions;
}
Bir try/catch ile onu durduran yönteme doğru yukarı doğru ilerlerken, ilk istisna e bir istisna içinde kapsüllenmiş olabilir. Bu durumda, onu nihai olarak durduracak yönteme doğru yukarı doğru ilerleyen, bu son istisnadır. Dolayısıyla o da kapsüllenebilir. Sonuç olarak, bir yöntem bir istisna th'yi durdurup onu kullanmaya karar verdiğinde, ilk istisna e'yi istisna yığınının en dibinde bulacaktır. Dolayısıyla, yukarıdaki örnekte [Throwable th] parametresi, istisnalar buzdağının sadece görünen kısmıdır. [th.cause] özniteliği, bu istisnanın kendi içinde kapsüllediği istisnayı belirlemeye olanak tanır. Ve bu şekilde devam eder. Bir istisna e, [e.getCause()==null] şeklindeyse, bu, e'nin ilk istisna olduğu anlamına gelir.
- 8. satır: [Throwable th] istisna yığınınındaki her istisna için iki bilgi kaydedilir:
- [getClass().getName()]: istisnanın tam adı;
- [getMessage()]: ilgili hata mesajı;
3.4. Exemple-01
3.4.1. Projenin mimarisi
![]() |
Bu örnekte, bir konsol programı [JDBC] katman arayüzünü kullanmaktadır.
3.4.2. Eclipse projesi
2.5.2.1. paragrafında belirtilen adımları izleyerek Spring / Maven [spring-jdbc-01] projesini oluşturuyoruz.
![]() |
Bu proje, aşağıdaki [pom.xml] dosyasıyla tanımlanmış bir Maven projesidir:
<?xml version="1.0" encoding="UTF-8"?>
<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>dvp.spring.database</groupId>
<artifactId>spring-jdbc-generic-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-jdbc-generic-01</name>
<description>Demo project for API JDBC</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath /> <!-- depo'dan üst öğeyi arama -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- SGBD'in JDBC yapılandırması -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>generic-config-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- 28-32. satırlar: Proje, az önce incelediğimiz [mysql-config-jdbc] projesinin [generic-config-jdbc] artefaktını kullanmaktadır. Dolayısıyla [spring-jdbc-01] projesi, [mysql-config-jdbc] projesinin tüm öğelerine erişebilir;
Projenin Maven bağımlılıklarını inceleyerek bu son noktayı iki şekilde görebiliriz:
![]() |
- [2]'te, [mysql-config-jdbc] projesinin bu projenin Maven bağımlılıkları arasında yer aldığı görülmektedir. Bu bağımlılıklar projenin Classpath'inde yer aldığından, [mysql-config-jdbc] projesinin de bu Classpath'te olduğu ve dolayısıyla sınıflarının ve arayüzlerinin [spring-jdbc-01] projesinde görünür olduğu anlamına gelir;
Maven projesi [mysql-config-jdbc]’in diğer Maven projeleri tarafından kullanılabilmesi için [Package Explorer] sekmesinde yer alması gerekmez. Yeter ki yerel Maven deposunda mevcut olsun. NetBeans gibi bir IDE projesinin aksine, Eclipse'te bu durum otomatik olarak gerçekleşmez. Bu işlemi zorlamak gerekir:
![]() |
Bu oluşturma işlemini mümkün kılan koşulları 2.3.5. paragrafında incelemiştik. Oluşturma işlemi tamamlandığında, [mysql-config-jdbc] projesini [Package Explorer] sekmesinden kaldırabilirsiniz:
![]() |
- [3] seçeneğini işaretlememelisiniz; bu, projeyi diskten fiziksel olarak siler ve geri alınamaz hale getirir;
Bu işlem, [Package Explorer]'ten çıkarılan projeye bağımlı olan projelerin Maven bağımlılık hesaplamasını yeniden başlatır. Bu, söz konusu projelerin [Maven Dependencies] dalını değiştirir. Örneğin, [spring-jdbc-01] projesi için [Maven Dependencies] dalı şu şekilde değişir:
![]() |
Bu sefer bağımlılık artık bir projeye değil, projenin Maven artefaktına, yani burada [generic-config-jdbc] [1] artefaktına yöneliktir. Bu artefaktın tüm sınıflarına ve arayüzlerine erişebildiğimizi görüyoruz. Daha önce de belirtildiği gibi, bu artefakt tüm [*-config-jdbc] projeleri tarafından oluşturulacaktır. Hataları önlemek için:
- [Package Explorer] sekmesinde her zaman tek bir [*-config-jdbc] projesi bulunduracağız;
- [Package Explorer] sekmesindeki tüm projelerin Maven yapılandırmasını (Alt-F5) güncelleyeceğiz, böylece bu projelerin Maven bağımlılıklarında kullanılan [*-config-jdbc] projesi görünecektir;
3.4.3. Ana sınıfın iskeleti
![]() |
[IntroJdbc01] ana sınıfının iskeleti şu şekildedir:
package spring.jdbc;
import generic.jdbc.config.ConfigJdbc;
import generic.jdbc.entities.dbproduits.Produit;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class IntroJdbc01 {
// sabitler
final static ObjectMapper jsonMapper = new ObjectMapper();
public static void main(String[] args) {
// SGBD'in JDBC sürücüsünün yüklenmesi
try {
Class.forName(ConfigJdbc.DRIVER_CLASSNAME);
} catch (ClassNotFoundException e1) {
doCatchException("Pilote JDBC introuvable", null, e1);
return;
}
// [PRODUITS] tablosu boşaltılıyor
System.out.println(String.format("------------------------------ %s", "Vidage de la table [PRODUITS]"));
delete();
// tablo dolduruluyor
System.out.println(String.format("------------------------------ %s", "Remplissage de la table [PRODUITS]"));
insert();
// tablo okunuyor
System.out.println(String.format("------------------------------ %s", "Affichage de la table [PRODUITS]"));
select();
// güncelleniyor
System.out.println(String.format("------------------------------ %s", "Mise à jour de la table [PRODUITS]"));
update();
// görüntüleme
System.out.println(String.format("------------------------------ %s", "Affichage de la table [PRODUITS]"));
select();
// tabloyu boşalt [PRODUITS]
System.out.println(String.format("------------------------------ %s", "Vidage de la table [PRODUITS]"));
delete();
// görüntüleniyor
System.out.println(String.format("------------------------------ %s", "Affichage de la table [PRODUITS]"));
select();
// INSERTion iki özdeş öğeden oluşur
// INSERTion işlemi başarısız olmalı ve işlem nedeniyle iki öğeden hiçbiri eklenmemelidir
System.out.println(String.format("------------------------------ %s",
"Insertion de deux produits de même clé primaire dans la table [PRODUITS]"));
insert2();
// kontrol ediliyor
System.out.println(String.format("------------------------------ %s", "Affichage de la table [PRODUITS]"));
select();
// bitti
System.out.println(String.format("------------------------------ %s", "Travail terminé"));
}
// ürün listesi
private static void select() {
...
}
// bir nesnenin görüntülenmesi jSON
private static void affiche(Object object) {
...
}
// ürünlerin silinmesi
public static void delete() {
...
}
// ürün ekleme
public static void insert() {
...
}
// aynı birincil anahtarlara sahip 2 ürünün eklenmesi
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) {
}
}
if (connexion != null) {
try {
// bağlantıyı kapat
connexion.close();
} catch (SQLException e3) {
// hata mesajları görüntüleniyor
show("Les erreurs suivantes se sont produites lors de la fermeture de la connexion",
getErreursFromThrowable(e3));
}
}
}
private static void doCatchException(String title, Connection connexion, Throwable th) {
// hata mesajları görüntüleniyor
show(title, getErreursFromThrowable(th));
// işlem iptal edildi
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// hata mesajları görüntüleniyor
show("Erreur lors de l'annulation de la transaction", getErreursFromThrowable(e2));
}
}
private 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;
}
private 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));
}
}
}
- 23-29. satırlar: SGBD'ten JDBC sürücüsünün yüklenmesi. 25. satırda, [mysql-config-jdbc] projesinde tanımlanan [ConfigJdbc.DRIVER_CLASSNAME] sabiti kullanılır;
- 136-147. satırlar: [getErreursFromThrowable] yöntemi, [Throwable] türünde bir nesne içinde kapsüllenmiş hata mesajlarının listesini döndürür; bu nesne, [Exception] sınıfının üst sınıfıdır. Bir istisna, [Throwable].getCause() yöntemi ile elde edilen başka bir istisnayı içerebilir. Böylece, [Throwable] nesnesinde kapsüllenmiş tüm istisnalar gözden geçirilir;
- 149-156. satırlar: [show(String title, List<String> messages)] yöntemi, mesajların önüne [title] metnini ekleyerek mesajları görüntüler;
- satır 122-134: [doCatchException(String title, Connection connexion, Throwable th))] yöntemi, sınıfın yöntemleri tarafından karşılaşılan istisnaları yönetir. Yönetilen istisna, [Throwable th] parametresiyle temsil edilir. Yöntemin amacı şudur:
- [Connection connexion] nesnesinin devam eden işlemini iptal etmek (satır 127-129);
- [Throwable th] istisnasına kapsüllenmiş hata mesajlarını yazmak (satır 124, 132);
- 93-120. satırlar: [doFinally(ResultSet rs, PreparedStatement ps, Connection connexion)] yöntemi, SGBD'e erişim yöntemlerinin [finally] dalını yönetir. Amacı, bağlantı tarafından kullanılan kaynakları serbest bırakmaktır;
3.4.4. Ü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çma
connexion = DriverManager.getConnection(ConfigJdbc.URL_DBPRODUITS , ConfigJdbc.USER_DBPRODUITS, ConfigJdbc.PASSWD_DBPRODUITS);
// işlem başlangıcı
connexion.setAutoCommit(false);
// okuma/yazma modunda
connexion.setReadOnly(false);
// [PRODUITS] tablosu boşaltılıyor
ps = connexion.prepareStatement(ConfigJdbc.V1_DELETE_PRODUITS);
ps.executeUpdate();
// işlemi kaydet
connexion.commit();
} 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ğunu işleme
doFinally(null, ps, connexion);
}
}
- satırda, [ConfigJdbc] sınıfının aşağıdaki sabitleri kullanılmaktadır:
public final static String URL_DBPRODUITS = "jdbc:mysql://localhost:3306/dbproduits";
public final static String USER_DBPRODUITS = "root";
public final static String PASSWD_DBPRODUITS = "";
- satırda, hazırlanan SQL komutu şöyledir:
public final static String V1_DELETE_PRODUITS = "DELETE FROM PRODUITS";
[delete] yöntemi, işlemler kullanı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 sonlandırılması: [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;
Ö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 etme
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));
}
}
3.4.5. Ürün tablosunun içeriğinin oluşturulması
[insert] yöntemi, tablonun içeriğini oluşturur:
public static void insert() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// bağlantı açılıyor
connexion = DriverManager.getConnection(ConfigJdbc.URL_DBPRODUITS , ConfigJdbc.USER_DBPRODUITS, ConfigJdbc.PASSWD_DBPRODUITS);
// işlem başlatılıyor
connexion.setAutoCommit(false);
// okuma/yazma modunda
connexion.setReadOnly(false);
// tablo dolduruluyor
ps = connexion.prepareStatement(ConfigJdbc.V1_INSERT_PRODUITS_WITH_ID);
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();
} 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ğu işleniyor
doFinally(null, ps, connexion);
}
}
- satırda, hazırlanan SQL emri şu şekildedir:
public final static String V1_INSERT_PRODUITS_WITH_ID = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?, ?)";
3.4.6. Ü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(ConfigJdbc.URL_DBPRODUITS , ConfigJdbc.USER_DBPRODUITS, ConfigJdbc.PASSWD_DBPRODUITS);
// işlem başlatılıyor
connexion.setAutoCommit(false);
// salt okunur modda
connexion.setReadOnly(true);
// [PRODUITS] tablosu okunuyor
ps = connexion.prepareStatement(ConfigJdbc.V1_SELECT_PRODUITS);
rs = ps.executeQuery();
System.out.println("Liste des produits : ");
while (rs.next()) {
affiche(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("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// finally bloğu işleniyor
doFinally(rs, ps, connexion);
}
}
- satırda, hazırlanan SQL komutu şöyledir:
public final static String V1_SELECT_PRODUITS = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
[affiche] yöntemi (18. satır) şu şekildedir:
// jSON nesnesini görüntüleme
private static void affiche(Object object) {
try {
System.out.println(jsonMapper.writeValueAsString(object));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
Parametre olarak geçirilen nesnenin jSON temsilini görüntüler (bkz. jSON, paragraf 23.12).
3.4.7. Tablonun 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(ConfigJdbc.URL_DBPRODUITS , ConfigJdbc.USER_DBPRODUITS, ConfigJdbc.PASSWD_DBPRODUITS);
// işlemin başlatılması
connexion.setAutoCommit(false);
// okuma/yazma modunda
connexion.setReadOnly(false);
// tablo güncelleniyor
ps = connexion.prepareStatement(ConfigJdbc.V1_UPDATE_PRODUITS);
// kategori 1
ps.setInt(1, 1);
// yürütme
ps.executeUpdate();
// işlemi kaydet
connexion.commit();
} 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ğu işleniyor
doFinally(null, ps, connexion);
}
}
- satırda, hazırlanan SQL emri şöyledir:
public final static String V1_UPDATE_PRODUITS = "UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?";
3.4.8. İş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ı birincil 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(ConfigJdbc.URL_DBPRODUITS , ConfigJdbc.USER_DBPRODUITS, ConfigJdbc.PASSWD_DBPRODUITS);
// işlem başlatılıyor
connexion.setAutoCommit(false);
// okuma/yazma modunda
connexion.setReadOnly(false);
// 1 satır ekleniyor
ps = connexion.prepareStatement(ConfigJdbc.V1_INSERT_PRODUITS_2);
// işlem yürütülüyor
ps.executeUpdate();
// aynı satır ikinci kez ekleniyor, yani aynı birincil anahtarla
// INSERTion işlemi başarısız olmalı ve işlem nedeniyle her iki öğe de eklenmemelidir
ps.executeUpdate();
// işlemi tamamla
connexion.commit();
} catch (SQLException e1) {
// istisna işlenir
doCatchException("Les erreurs suivantes se sont produites lors de l'ajout de deux produits de même clé primaire",
connexion, e1);
} finally {
// finally bloğu işleniyor
doFinally(null, ps, connexion);
}
}
- satırda, hazırlanan SQL komutu şöyledir:
public final static String V1_INSERT_PRODUITS_2 = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (100,'X',1,1,'x')";
3.4.9. Sonuçlar
[spring-jdbc-generic-01.IntroJdbc01] adlı yürütme yapılandırması çalıştırılır:
![]() |
Aşağıdaki konsol sonuçları elde edilir:
------------------------------ Vidage de la table [PRODUITS]
------------------------------ Remplissage de la table [PRODUITS]
------------------------------ Affichage de la table [PRODUITS]
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"}
------------------------------ Mise à jour de la table [PRODUITS]
------------------------------ Affichage de la table [PRODUITS]
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"}
------------------------------ Vidage de la table [PRODUITS]
------------------------------ Affichage de la table [PRODUITS]
Liste des produits :
------------------------------ Insertion de deux produits de même clé primaire dans la table [PRODUITS]
Les erreurs suivantes se sont produites lors de l'ajout de deux produits de même clé primaire :
- Duplicate entry '100' for key 'PRIMARY'
------------------------------ Affichage de la table [PRODUITS]
Liste des produits :
------------------------------ Travail terminé
- 30. satır: Aynı birincil anahtara sahip iki ürün eklenmeden önce tablo boştur;
- 35. satır: aynı birincil anahtara sahip iki ürün eklendikten sonra tablo boştur. Bu, işlemin rolünü göstermektedir:
- ilk ekleme işlemi başarılı olur. Başarısız olması için hiçbir neden yoktur;
- ikinci ekleme başarısız olur (satır 32). Dolayısıyla, bu iki ekleme aynı işlem içinde olduğu için, bu işlemdeki tüm SQL komutları iptal edilir; buna ilk ekleme de dahildir.
3.4.10. Sonuç
Yukarıdaki kodlarda dikkat çeken nokta, [SQLException] istisnasının yönetiminin kodda büyük bir yer kaplamasıdır. Herhangi bir JDBC işlemi bu istisnayı tetikleyebileceğinden, kodda çok sayıda try / catch bloğu bulunmaktadır.
3.5. Exemple-02
[javax.sql.DataSource] türünde bir veri kaynağı kullanarak önceki uygulamayı yeniden ele alacağız:

[org.apache.tomcat.jdbc.pool.DataSource] sınıfı tarafından uygulanan bir veri kaynağı 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
3.5.1. Projenin mimarisi
![]() |
Bu örnekte, bir konsol programı [JDBC] katmanının arayüzünü kullanır.
3.5.2. Eclipse projesi
Yeni Eclipse projesi, önceki [1-6] projesini kopyalayarak elde edilebilir:
![]() |
![]() |
Ardından proje, [6]'ten [7]'e güncellenir:
3.5.3. Maven Yapılandırması
[7] projesi, aşağıdaki [pom.xml] dosyasıyla tanımlanan bir Maven projesidir:
<?xml version="1.0" encoding="UTF-8"?>
<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>dvp.spring.database</groupId>
<artifactId>spring-jdbc-generic-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-jdbc-generic-02</name>
<description>Demo project for API JDBC</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath /> <!-- depo'dan üst öğeyi arama -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- SGBD'in JDBC yapılandırması -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>generic-config-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- 28-33. satırlar: [mysql-config-jdbc] projesine ilişkin Maven bağımlılığı;
[mysql-config-jdbc] projesi, Maven bağımlılıklarında [javax.sql.DataSource] türünde bir veri kaynağının uygulamasını sağlayan kütüphaneyi barındırır (bkz. paragraf 3.3.2):
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
3.5.4. Spring Yapılandırması
![]() |
[AppConfig] adlı Spring yapılandırma sınıfı şu şekildedir:
package spring.jdbc;
import generic.jdbc.config.ConfigJdbc;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({ generic.jdbc.config.ConfigJdbc.class })
public class AppConfig {
// veri kaynağı
@Bean
public DataSource dataSource() {
// TomcatJdbc veri kaynağı
DataSource dataSource = new DataSource();
// erişim yapılandırması JDBC
dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
dataSource.setUsername(ConfigJdbc.USER_DBPRODUITS);
dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITS);
dataSource.setUrl(ConfigJdbc.URL_DBPRODUITS);
// başlangıçta açık bağlantılar
dataSource.setInitialSize(5);
// sonuç
return dataSource;
}
}
- 10. satır: [AppConfig] bir Spring yapılandırma sınıfıdır;
- 11. satır: [mysql-config-jdbc] projesinde tanımlanan [generic.jdbc.config.ConfigJdbc.class] yapılandırma sınıfının içe aktarılması. Bu, bu yapılandırma dosyasında tanımlanan tüm bean'lere erişilebildiği anlamına gelir;
- satır 14-27: Veri kaynağını tanımlayan Spring bean'i;
- 17. satır: Henüz yapılandırılmamış veri kaynağının oluşturulması;
- 19-22. satırlar: Veri kaynağının veritabanına bağlanmasını sağlayan bilgiler;
- 24. satır: 5 bağlantılık bir havuz oluşturur. Burada sadece bir tanesine ihtiyacımız var. Asla birden fazla eşzamanlı bağlantı olmaz;
3.5.5. Ana sınıf
[IntroJdbc02] ana sınıfı şu şekildedir:
package spring.jdbc;
import generic.jdbc.config.ConfigJdbc;
import generic.jdbc.entities.dbproduits.Produit;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class IntroJdbc02 {
// eşleme jSON
final static ObjectMapper jsonMapper = new ObjectMapper();
// veri kaynağı
private static DataSource dataSource;
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = null;
try {
// Spring bağlamının alınması
ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// veri kaynağının alınması
dataSource = ctx.getBean(DataSource.class);
// [PRODUITS] tablosu boşaltılıyor
System.out.println(String.format("------------------------------ %s", "Vidage de la table [PRODUITS]"));
delete();
...
// bitti
System.out.println(String.format("------------------------------ %s", "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);
// salt okunur modda
connexion.setReadOnly(true);
// [PRODUITS] tablosu okunuyor
ps = connexion.prepareStatement(ConfigJdbc.V1_SELECT_PRODUITS);
rs = ps.executeQuery();
System.out.println("Liste des produits : ");
while (rs.next()) {
affiche(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("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// finally bloğu işleniyor
doFinally(rs, ps, connexion);
}
}
...
- 25. satır: veri kaynağı. Bunun, bir arayüz olan [javax.sql.DataSource] (13. satır) türünde olduğuna dikkat edilmelidir;
- 31. satır: Spring nesnelerinin örneklenmesi;
- satır 32: 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 49: açık bir bağlantının alınması. [IntroJdbc02] sınıfındaki çeşitli yöntemler, veritabanına bu şekilde bağlanmaktadır. Kodun geri kalanı, [IntroJdbc01] sınıfındakiyle aynıdır;
3.5.6. Testler
[spring-jdbc-generic-02.IntroJdbc02] adlı yürütme yapılandırması çalıştırılır:
![]() |
Daha önce elde edilen sonuçlarla aynı sonuçlar elde edilir (3.4.9. paragraf).
3.6. Exemple-03
3.6.1. Projenin mimarisi
![]() |
Bu örnekte, veri erişim yöntemleri bir katmanda izole edilmiştir [dao]. Bu yöntemler bir test ile test edilecektir JUnit.
3.6.2. Eclipse projesi
Eclipse projesi [spring-jdbc-03], önceki proje gibi oluşturulmuş ve aşağıdaki şekilde tamamlanmış bir Spring / Maven projesidir:
![]() | ![]() |
Farklı paketlerin rolleri şunlardır:
- [spring.jdbc.config]: Spring projesinin yapılandırması;
- [spring.jdbc.dao]: [DAO] katmanının uygulaması;
- [spring.jdbc.infrastructure]: [DaoException] kontrolsüz istisnasını uygular;
3.6.3. Maven yapılandırması
Maven projesi, aşağıdaki [pom.xml] dosyası ile yapılandırılır:
<?xml version="1.0" encoding="UTF-8"?>
<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>dvp.spring.database</groupId>
<artifactId>spring-jdbc-generic-03</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-jdbc-generic-03</name>
<description>Demo project for API JDBC</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath /> <!-- depo'dan üst öğeyi ara -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- SGBD yapılandırması JDBC -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>generic-config-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
Bu yapılandırma, [spring-jdbc-02] projesininkiyle aynıdır. Özellikle, [mysql-config-jdbc] projesinin Maven bağımlılığını kullanır (28-32. satırlar).
3.6.4. [DAO] katmanının arayüzü
![]() |
[DAO] katmanı, aşağıdaki [IDao] arayüzünü sunar:
package spring.jdbc.dao;
import java.util.List;
import spring.jdbc.entities.Produit;
public interface IDao {
// ürün ekleme
public List<Produit> addProduits(List<Produit> produits);
// tüm ürünlerin listesi
public List<Produit> getAllProduits();
// belirli bir ürün
public Produit getProduitById(int id);
public Produit getProduitByName(String name);
// birden fazla ürünü güncelle
public int updateProduits(List<Produit> produits);
// tüm ürünleri sil
public int deleteAllProduits();
// birden fazla ürünü silme
public int deleteProduits(int[] ids);
}
3.6.5. [DaoException] sınıfı
[DaoException] sınıfı, 3.3.5. paragrafta sunulan [UncheckedException] sınıfını genişletmekle yetinir:
![]() |
package spring.jdbc.infrastructure;
public class DaoException extends UncheckedException {
private static final long serialVersionUID = 1L;
// üreticiler
public DaoException() {
super();
}
public DaoException(int code, Throwable e, String className) {
super(code, e, className);
}
}
3.6.6. Spring projesinin yapılandırılması
![]() |
Spring projesini yapılandıran [AppConfig] sınıfı, 11. satır hariç, [spring-jdbc-02] örneğindeki Spring yapılandırma dosyasıyla aynıdır:
package spring.jdbc.config;
import generic.jdbc.config.ConfigJdbc;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = { "spring.jdbc.dao" })
public class AppConfig {
// veri kaynağı
@Bean
public DataSource dataSource() {
// veri kaynağı TomcatJdbc
DataSource dataSource = new DataSource();
// erişim yapılandırması JDBC
dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
dataSource.setUsername(ConfigJdbc.USER_DBPRODUITS);
dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITS);
dataSource.setUrl(ConfigJdbc.URL_DBPRODUITS);
// başlangıçta açık bağlantılar
dataSource.setInitialSize(5);
// sonuç
return dataSource;
}
}
- 11. satır: [spring.jdbc.dao] paketi, bu yapılandırma dosyasında tanımlananların dışında başka Spring bileşenleri olup olmadığını bulmak için taranacaktır;
3.6.7. [DAO] katmanının uygulanması
![]() |
![]() |
Hatırlanacağı üzere (paragraf 3.6.4), [DAO] katmanı aşağıdaki [IDao] arayüzünü uygular:
package spring.jdbc.dao;
import generic.jdbc.entities.dbproduits.Produit;
import java.util.List;
public interface IDao {
// ürün ekle
public List<Produit> addProduits(List<Produit> produits);
// tüm ürünlerin listesi
public List<Produit> getAllProduits();
// belirli bir ürün
public Produit getProduitById(int id);
public Produit getProduitByName(String name);
// birden fazla ürünün güncellenmesi
public int updateProduits(List<Produit> produits);
// tüm ürünleri sil
public int deleteAllProduits();
// birden fazla ürünü silme
public int deleteProduits(int[] ids);
}
[Dao1, Dao2] sınıflarının her ikisi de bu arayüzü uygular. [Dao2] sınıfı, [Dao1] sınıfının bir varyantıdır ve yeni bir sözdizimi özelliği getirir. Biz, [Dao1] sınıfı üzerinde duracağız. Bu sınıfın iskeleti şu şekildedir:
package spring.jdbc.dao;
import generic.jdbc.config.ConfigJdbc;
import generic.jdbc.entities.dbproduits.Produit;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import spring.jdbc.infrastructure.DaoException;
@Component("dao1")
public class Dao1 implements IDao {
// sınıf adı
private String simpleClassName = getClass().getSimpleName();
// veri kaynağı
@Autowired
protected DataSource dataSource;
// üretici
public Dao1() {
System.out.println("building Dao1...");
}
// ------------------------------- arayüz
@Override
public List<Produit> getAllProduits() {
...
}
@Override
public Produit getProduitById(int id) {
...
}
@Override
public Produit getProduitByName(String name) {
...
}
@Override
public List<Produit> addProduits(List<Produit> produits) {
....
}
@Override
public int updateProduits(List<Produit> produits) {
...
}
@Override
public int deleteAllProduits() {
...
}
@Override
public int deleteProduits(int[] ids) {
...
}
// ---------------------------------------- yerel yöntemler
// finally yönetimi
protected DaoException doFinally(ResultSet rs, PreparedStatement ps, Connection connexion, int code,
DaoException daoException) {
...
}
// catch işleme
protected DaoException doCatchException(Connection connexion, Throwable th, int code, DaoException daoException) {
...
}
- 20. satır: [Dao] sınıfı, [dao1] adlı bir Spring bileşenidir. Bu ad isteğe bağlıdır. Ad belirtilmediğinde, sınıf adının ilk harfi küçük harfe dönüştürülerek kullanılır;
- 24. satır: sınıf adı. [Dao] adını sabit olarak yazmaktan kaçınılır; böylece bu alanı yeniden tanımlamaya gerek kalmadan sınıfın adını değiştirme imkanı sağlanır ve alan her zaman geçerli kalır;
- satır 26-27: [AppConfig] yapılandırma sınıfında tanımlanan [tomcat-jdbc] veri kaynağının eklenmesi;
- satır 36-68: [IDao] arayüzünün uygulanması;
- satır 78-80: Farklı yöntemlerin catch'sinin merkezi yönetimi;
- satır 72-75: farklı yöntemlerin finally'inin merkezi yönetimi;
Farklı yöntemlerin catch'i şu şekilde yönetilir:
// catch yönetimi
protected DaoException doCatchException(Connection connexion, Throwable th, int code) {
// işlem iptali
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
e2.printStackTrace();
}
// daoException
return new DaoException(code, th, simpleClassName);
}
- 2. satır: Yöntem, [protected] olarak tanımlanmıştır; bu sayede alt sınıflar, yöntem kamuya açık olmasa da onu kullanabilir. Yöntem aşağıdaki parametreleri alır:
- [Connection connexion]: SGBD ile bağlantı - belki de null;
- [Throwable th]: Meydana gelen ve [DaoException] türünde kapsüllenecek istisna;
- [int code]: Yöntem yeni bir [DaoException] oluşturursa kullanılacak bir hata kodu;
- 4-7. satırlar: Bu yöntemin temel işlevi, 1. parametre olarak geçirilen bağlantıyla ilişkili işlemi iptal etmektir;
- satır 8-10: İşlemin iptal edilmesi başarısız olursa, istisna izi konsola yazılır. 12. satırda bir istisna atılacağı için başka pek bir şey yapılamaz;
Farklı yöntemlerin finally değeri şu şekilde yönetilir:
// son işlem yönetimi
protected DaoException doFinally(ResultSet rs, PreparedStatement ps, Connection connexion, int code,
DaoException daoException) {
// kapatma ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// kapatma [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// bağlantı kapatma
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// mümkünse hata kaydedilir
if (daoException == null) {
daoException = new DaoException(code, e3, simpleClassName);
}
}
}
// sonuç
return daoException;
}
- 2. satır: Bu yöntem de [protected] olarak tanımlanmıştır. Aşağıdaki parametreleri alır:
- [ResultSet rs]: [SELECT] işlemi gerçekleştirilmişse, olası [ResultSet] – belki de null;
- [PreparedStatement ps]: Yürütülen [PreparedStatement] – belki de null;
- [Connection connexion]: SGBD ile bağlantı - belki de null;
- [int code]: Yöntem yeni bir [DaoException] oluşturursa kullanılacak bir hata kodu;
- [DaoException daoException]: finally'ten önce bir istisna meydana gelmişse olası [DaoException] – belki de null;
- 21-30. satırlar: Bu yöntemin temel amacı bağlantıyı kapatmaktır (23. satır);
- 24-29. satırlar: bu kapatma işlemi sırasında bir istisna oluşursa, bize aktarılan [DaoException daoException] parametresinin durumuna bakılır: eğer [daoException == null] ise, parametre olarak aktarılan kodla yeni bir [DaoException] oluşturulur;
- satır 32: eski veya yeni [DaoException] sonucu olarak döndürülür;
[Dao] sınıfındaki tüm yöntemleri değil, sadece birkaçını tanıtacağız. Hepsi birbirine benziyor.
3.6.7.1. [getProduitById] yöntemi
[getProduitById] yöntemi, birincil anahtarı [id] veya null parametresine eşit olan ürünü döndürür, aksi takdirde;
@Override
public Produit getProduitById(int id) {
// bağlantı kaynakları
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
// başlangıçta istisna yok
DaoException daoException = null;
// aranan ürün
Produit produit = null;
try {
// bağlantı açılıyor
connexion = dataSource.getConnection();
// işlem başlangıcı
connexion.setAutoCommit(false);
// salt okunur modda
connexion.setReadOnly(true);
// [PRODUITS] tablosu okunuyor
ps = connexion.prepareStatement(ConfigJdbc.V2_SELECT_PRODUIT_BYID);
ps.setInt(1, id);
rs = ps.executeQuery();
if (rs.next()) {
produit = new Produit(id, rs.getString(1), rs.getInt(2), rs.getDouble(3), rs.getString(4));
}
// işlemi kaydet
connexion.commit();
// varsayılan moda geri dönülüyor
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// istisna işleniyor
daoException = doCatchException(connexion, e1, 112);
} finally {
// finally bloğu işleniyor
daoException = doFinally(rs, ps, connexion, 113, daoException);
}
// istisna mı?
if (daoException != null) {
throw daoException;
}
// sonuç
return produit;
}
- 10. satır: iade edilecek ürün null olarak belirlenir;
- satır 19: SQL [ConfigJdbc.V2_SELECT_PRODUIT_BYID] emri şöyledir:
public final static String V2_SELECT_PRODUIT_BYID = "SELECT NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS WHERE ID=?";
- 22-24. satırlar: [ResultSet]'in bir satırı varsa, iade edilecek ürünü oluşturmak için bu satır kullanılır; aksi takdirde iade edilecek ürün null olarak kalır;
- 41. satır: ürün iade edilir;
- 8. satır: Yöntemin [DaoException] istisnası, null olarak başlatılır;
- satır 31: [doCatchException] yöntemi, [DaoException] istisnasını oluşturur;
- satır 34: [doFinally] yönteminin [daoException] parametresi ya null,'tir ya da [doCatchException] yöntemi tarafından oluşturulan istisnadır. [doFinally] yöntemi:
- bağlantıyı kapatmayı başarırsa bu parametreyi olduğu gibi bırakır;
- bağlantıyı kapatamazsa ve daha önce bir [DaoException] oluşmuşsa bu parametreyi olduğu gibi bırakır;
- bağlantıyı kapatamazsa ve daha önce [DaoException] işlevi çağrılmamışsa yeni bir [DaoException] oluşturur;
- satır 37-39: yerel istisna [daoException], null'e eşit değilse, istisna tetiklenir; aksi takdirde istenen sonuç döndürülür (satır 41);
3.6.7.2. [deleteProduits] yöntemi
[deleteProduits] yöntemi, birincil anahtarları parametre olarak verilen ürünleri siler. Silinen ürün sayısını döndürür.
@Override
public int deleteProduits(int[] ids) {
// bağlantı kaynakları
PreparedStatement ps = null;
Connection connexion = null;
// başlangıçta istisna yok
DaoException daoException = null;
// güncellenen ürün sayısı
int nbProduits = 0;
try {
// bağlantı açılıyor
connexion = dataSource.getConnection();
// işlem başlangıcı
connexion.setAutoCommit(false);
// okuma/yazma modunda
connexion.setReadOnly(false);
// ürünler siliniyor
ps = connexion.prepareStatement(ConfigJdbc.V2_DELETE_PRODUITS);
for (int id : ids) {
// ayarlar
ps.setInt(1, id);
// işlem
nbProduits += ps.executeUpdate();
}
// işlemi kaydet
connexion.commit();
// varsayılan moda geri dön
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// istisna işleniyor
daoException = doCatchException(connexion, e1, 171);
} finally {
// finally bloğu işleniyor
daoException = doFinally(null, ps, connexion, 172, daoException);
}
// istisna mı?
if (daoException != null) {
throw daoException;
}
// sonuç
return nbProduits;
}
- 18. satırda, SQL [ConfigJdbc.V2_DELETE_PRODUITS] komutunun içeriği şöyledir:
public final static String V2_DELETE_PRODUITS = "DELETE FROM PRODUITS WHERE ID=?";
- 18-24. satırlar: ürün silme kodu. SQL emrinin 1 kez hazırlandığı (18. satır) ve n kez yürütüldüğü (19-24. satırlar) görülmektedir. [PreparedStatement] nesnesinin önemi budur;
- 23. satır: [PreparedStatement].executeUpdate() yöntemi, güncelleme işleminden etkilenen satır sayısını döndürür;
- satır 41: güncellenen ürünlerin sayısı döndürülür;
3.6.7.3. [updateProduits] yöntemi
[updateProduits] yöntemi, parametre olarak verilen ürünleri veritabanında günceller. Güncellenen ürün sayısını döndürür.
@Override
public int updateProduits(List<Produit> produits) {
// bağlantı kaynakları
PreparedStatement ps = null;
Connection connexion = null;
// başlangıçta istisna yok
DaoException daoException = null;
// güncellenen ürün sayısı
int nbProduits = 0;
try {
// bağlantı açıldı
connexion = dataSource.getConnection();
// işlemin başlangıcı
connexion.setAutoCommit(false);
// okuma/yazma modunda
connexion.setReadOnly(false);
// [PRODUITS] tablosu güncelleniyor
ps = connexion.prepareStatement(ConfigJdbc.V2_UPDATE_PRODUITS);
for (Produit produit : produits) {
// parametreler
ps.setString(1, produit.getNom());
ps.setDouble(2, produit.getPrix());
ps.setInt(3, produit.getCategorie());
ps.setString(4, produit.getDescription());
ps.setInt(5, produit.getId());
// işlem yürütülüyor
nbProduits += ps.executeUpdate();
}
// işlemi kaydet
connexion.commit();
// varsayılan moda geri dönülüyor
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// istisna işleniyor
daoException = doCatchException(connexion, e1, 131);
} finally {
// finally bloğu işleniyor
daoException = doFinally(null, ps, connexion, 132, daoException);
}
// istisna mı?
if (daoException != null) {
throw daoException;
}
// sonuç
return nbProduits;
}
- 18. satır: SQL [ConfigJdbc.V2_UPDATE_PRODUITS] komutunun içeriği şöyledir:
public final static String V2_UPDATE_PRODUITS = "UPDATE PRODUITS SET NOM=?, PRIX=?, CATEGORIE=?, DESCRIPTION=? WHERE ID=?";
- 19-28. satırlar: ürün güncelleme kodu;
3.6.7.4. [addProduits] yöntemi
[addProduits] yöntemi, parametre olarak verilen ürünleri veritabanına ekler. Bu ürünleri birincil anahtarlarıyla birlikte döndürür (veritabanına eklenmeden önce ürünlerin birincil anahtarı yoktur).
@Override
public List<Produit> addProduits(List<Produit> produits) {
// bağlantı kaynakları
PreparedStatement ps = null;
Connection connexion = null;
// başlangıçta istisna yok
DaoException daoException = null;
try {
// bağlantı açılıyor
connexion = dataSource.getConnection();
// okuma/yazma modunda
connexion.setReadOnly(false);
// işlem başlangıcı
connexion.setAutoCommit(false);
// [PRODUITS] tablosuna öğeler ekleniyor
String generatedColumns[] = { ConfigJdbc.TAB_PRODUITS_ID };
ps = connexion.prepareStatement(ConfigJdbc.V2_INSERT_PRODUITS, generatedColumns);
for (Produit produit : produits) {
// parametreler
ps.setString(1, produit.getNom());
ps.setLong(2, produit.getCategorie());
ps.setDouble(3, produit.getPrix());
ps.setString(4, produit.getDescription());
// komutun yürütülmesi
ps.executeUpdate();
// birincil anahtar oluşturuldu
ResultSet generatedKeys = ps.getGeneratedKeys();
if (generatedKeys.next()) {
produit.setId(generatedKeys.getInt(1));
} else {
throw new RuntimeException(String.format("Le produit de nom [%s] n'a pas récupéré de clé primaire",
produit.getNom()));
}
}
// işlem onaylandı
connexion.commit();
// varsayılan moda dönüş
connexion.setAutoCommit(true);
} catch (SQLException | RuntimeException e1) {
// istisna işleniyor
daoException = doCatchException(connexion, e1, 151);
} finally {
// finally bloğu işleniyor
daoException = doFinally(null, ps, connexion, 152, daoException);
}
// istisna mı?
if (daoException != null) {
throw daoException;
}
// sonuç
return produits;
}
- 16. satırda, SQL [ConfigJdbc.V2_INSERT_PRODUITS] komutunun sırası şöyledir:
public final static String V2_INSERT_PRODUITS = "INSERT INTO PRODUITS(NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?)";
Yukarıda, bir ürünün eklenmesi komutu [ID] birincil anahtarını içermemektedir. MySQL veritabanının birincil anahtarı [AUTOINCREMENT] özniteliğine sahip olduğundan, SGBD her ekleme işlemi için bir birincil anahtar oluşturacaktır. Burada bu anahtarı geri alma sorunu ortaya çıkar. Bu önemli bir noktadır, çünkü ürünler üzerindeki işlemler birincil anahtarlar aracılığıyla gerçekleştirilir. Dolayısıyla bu anahtarları bilmek gerekir;
- 17-33. satırlar: ürün ekleme döngüsü;
- 16. satır: [prepareStatement] yönteminin özel bir biçimi. İkinci parametre olan [generatedColumns], ekleme işleminden sonra değerlerini almak istediğimiz sütun adlarından oluşan bir dizidir. 16. satırda, [id] sütununun değerini almak istediğimizi belirtmişiz. Burada dikkat edilmesi gereken nokta, bir tablonun sütun adlarının büyük/küçük harf duyarlı olmamasına rağmen, SGBD PostgreSQL'in bu adın küçük harflerle yazılmasını istemesidir. Bu, bir SGBD kodunu başka bir SGBD koduna taşırken tipik olarak karşılaşılan türden bir sorundur;
- 24. satır: veritabanına bir satır ekleniyor;
- 26. satır: [ResultSet]'te 16. satırda belirtilen sütunların değer listesi alınır. Burada, 1 ekleme için [ResultSet]'te 1 satır olacak ve bu satırda birincil anahtarı içeren tek bir sütun bulunacaktır;
- satır 28: SGBD tarafından oluşturulan birincil anahtarı alınır;
- 29-32. satırlar: Oluşturulan birincil anahtar elde edilemezse, 38-40. satırlarda bir [DaoException] içine kapsüllenecek olan bir [RuntimeException] başlatılır;
3.6.8. [Dao2] sınıfı
![]() |
![]() |
[Dao2] sınıfı, try-with-resource(resource) adlı bir sözdizimi kullanan [Dao1] sınıfının bir varyantıdır:
- [resource], [java.lang.AutoCloseable] arayüzünü uygulayan bir kaynaktır. [close] yöntemi ile serbest bırakılan tüm kaynaklar buna dahildir. Bu sözdizimi, 4. satırda [resource] kaynağının kapatılmasını sağlar. Bu sayede, kapatma işlemini gerçekleştirmek için ayrı bir [finally] cümlesi yazmaya gerek kalmaz;
Örnek olarak, [Dao2] sınıfındaki [getAllProduits] yöntemini ele alalım:
@Override
public List<Produit> getAllProduits() {
// olası istisna
DaoException daoException = null;
// ürün listesi
List<Produit> produits = new ArrayList<Produit>();
try (Connection connexion = dataSource.getConnection()) {
// işlemin başlangıcı
connexion.setAutoCommit(false);
// salt okunur modda
connexion.setReadOnly(true);
// [PRODUITS] tablosu okunuyor
try (PreparedStatement ps = connexion.prepareStatement(ConfigJdbc.V2_SELECT_ALLPRODUITS)) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
produits.add(new Produit(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getDouble(4), rs.getString(5)));
}
}
// işlem sonu
connexion.commit();
// varsayılan moda geri dönüş
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// işlem iptal ediliyor
daoException = doRollback(connexion, e1, 111);
}
} catch (SQLException e2) {
// istisna işleniyor
if (daoException == null) {
daoException = new DaoException(112, e2, simpleClassName);
}
}
// istisna mı?
if (daoException != null) {
throw daoException;
}
// sonuç
return produits;
}
- 7. satır: [Connection] kaynağıyla try bloğu. 27. satırda, bu kaynağın kapatıldığından emin oluruz;
- 13. satır: [PreparedStatement] kaynağıyla try bloğu. 23. satırda, bu kaynağın kapatıldığı garanti edilir;
- satır 14: [ResultSet] kaynağıyla try. Satır 19'da, bunun kapatıldığından emin olunur;
- 25. satır: İşlem şu şekilde iptal edilir:
private DaoException doRollback(Connection connexion, Throwable e1, int code) {
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e) {
e.printStackTrace();
}
// istisna oluşturma
return new DaoException(code, e1, simpleClassName);
}
Sonuç olarak, okunması daha kolay bir kod elde ettik.
3.6.9. Test katmanının uygulanması
3.6.9.1. Test sınıfları
![]() |
![]() |
- [JUnitTestDao1] testi, [Dao1] sınıfına ait bir JUnit testidir;
- [JUnitTestDao2] testi, [Dao2] sınıfına ait bir JUnit testidir;
- [AbstractJUnitTestDao], önceki iki test sınıfının üst sınıfıdır;
- [MainTestDao1], [Dao1] sınıfının bir test konsol sınıfıdır;
- [MainTestDao2], [Dao2] sınıfının bir test konsol sınıfıdır;
- [AbstractMainTestDao], önceki iki sınıfın üst sınıfıdır. Bu sınıf, daha önce tanıtılan [IntroJdbc01, IntroJdbc02] konsol sınıflarının kodunu içerir; bu nedenle bu konsol sınıflarını incelemeyeceğiz;
[JUnitTestDao1] sınıfı şöyledir:
package spring.jdbc.tests;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import spring.jdbc.config.AppConfig;
import spring.jdbc.dao.IDao;
@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTestDao1 extends AbstractJUnitTestDao {
// katman [DAO]
@Autowired
@Qualifier("dao1")
private IDao dao;
@Override
IDao getDao() {
return dao;
}
}
- 12-13. satırlardaki açıklamalar 2.5.5. paragrafında sunulmuştur. Bunlar, bir Junit testinin Spring bağlamına ve bean'lerine kolayca erişmesini sağlar. Bu bağlam, 2.4.3. paragrafında incelenen [AppConfig] sınıfı (12. satır) tarafından yapılandırılır;
- 14. satır: Sınıf, birazdan tanıtacağımız [AbstractJUnitTestDao] sınıfını genişletmektedir. JUnit test yöntemleri bu sınıfta yer almaktadır;
- satır 17-19: [dao1] adlı bean (satır 18) enjekte edilir (satır 17). Dolayısıyla burada enjekte edilen, [Dao1] sınıfının bir örneğidir;
- satır 21-24: [getDao] yöntemi, üst sınıftaki aynı isimli yöntemi yeniden tanımlar;
Sonuç olarak, bu sınıfın amacı, test edilmesi gereken [DAO] katmanına ilişkin bir referansı üst sınıfa döndürmektir; burada bu referans, [Dao1] sınıfının bir örneğidir. Aynı şekilde, [JUnitTestDao2] sınıfı, üst sınıf olan [AbstractJUnitTestDao]'e [Dao2] sınıfının bir örneğini döndürür.
[AbstractJUnitTestDao] sınıfı, JUnit sınıfının bir test sınıfıdır:
package spring.jdbc.tests;
import generic.jdbc.entities.dbproduits.Produit;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.BeansException;
import spring.jdbc.dao.IDao;
import spring.jdbc.infrastructure.DaoException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public abstract class AbstractJUnitTestDao {
// katman [DAO]
abstract IDao getDao();
// jSON eşlemesi
final static ObjectMapper jsonMapper = new ObjectMapper();
@Before
public void clean() {
// her testten önce tabanı temizliyoruz
log("Vidage de la base de données", 1);
getDao().deleteAllProduits();
}
@Test
public void getProduits() throws JsonProcessingException {
...
}
@Test
public void getProduitBy() {
...
}
@Test
public void doInsertsInTransaction() {
...
}
@Test
public void updateProduits() {
...
}
@Test
public void deleteProduits() {
....
}
@Test
public void perf1() {
...
}
@Test
public void perf2() {
...
}
@Test
public void perf3() {
....
}
// -------------- özel yöntemler
...
}
- 19. satırda, [AbstractJUnitTestDao] sınıfı soyuttur;
- 22. satır: Test edilecek [DAO] katmanına referans almamızı sağlayan [getDao] soyut yöntemi. Bu yöntem, alt sınıflar tarafından uygulanır;
- 25. satır: Ürünlerin jSON değerini konsolda görüntülememizi sağlayacak bir jSON eşleştiricisi;
- 27-32. satırlar: Her testten önce (27. satır), [PRODUITS] tablosu boşaltılır;
3.6.9.2. Özel yöntem [fill]
Özel [fill] yöntemi, ürünleri [PRODUITS] tablosuna eklemek için kullanılır.
private List<Produit> fill(int nbProduits) {
log("Remplissage de la base de données", 1);
// ürün listesi oluşturulur
List<Produit> produits = new ArrayList<Produit>();
for (int i = 0; i < nbProduits; i++) {
int n = i + 1;
// int id, String isim, int kategori, double fiyat, String açıklama
produits.add(new Produit(0, String.format("NOM%s", n), n / 5 + 1, 100 * (1 + (double) i / 100), String.format(
"DESC%s", n)));
}
// veritabanına kaydedilir - ürünler birincil anahtarlarıyla alınır
produits = getDao().addProduits(produits);
// ürünleri daha kolay bulabilmek için bir ürün sözlüğü oluşturulur
// sözlüğün anahtarı, veritabanındaki ürünün birincil anahtarıdır
for (Produit produit : produits) {
mapProduits.put(produit.getId(), produit);
}
// ürünleri döndürüyoruz
return produits;
}
- 1. satır: [fill] yöntemi, boş olduğu varsayılan [PRODUITS] tablosuna [nbProduits]'i ekler;
- 3-10. satırlar: Aşağıdaki biçimde bir ürün listesi oluşturulur:
new Produit(0, String.format("NOM%s", n), n / 5 + 1, 100 * (1 + (double) i / 100), String.format("DESC%s", n)));
şeklinde bir ürün listesi oluşturulur. Bu liste, Ürün(int id, String isim, int kategori, double fiyat, String açıklama) oluşturucusunu kullanır. İlk parametrenin değeri [id] ([PRODUITS] tablosunun birincil anahtarı) önemsizdir, çünkü 10. satırdaki [addProduits] yöntemi bunu veritabanına eklemez ve SGBD'in kendi değerini oluşturmasına izin verir;
- 12. satır: Ürün listesi veritabanında kalıcı hale getirilir. Bu listedeki her bir ürün, yeni bir [id] birincil anahtarı ile zenginleştirilir. [addProduits] yöntemi, [produits] parametresini sonuç olarak döndürür. Dolayısıyla sonucu almamış olabilirdik;
- 15-17. satırlar: Ürünleri bir sözlüğe ekliyoruz:
// ürün sözlüğü
private Map<Integer, Produit> mapProduits = new HashMap<Integer, Produit>();
Sözlüğün anahtarı, ürünün birincil anahtarıdır ve buna karşılık gelen değer ise ürünün kendisidir;
- 19. satır: ürün listesi döndürülür;
3.6.9.3. [getProduits] testi
Bu test şu şekildedir:
@Test
public void getProduits() throws JsonProcessingException {
// doldurma
fill(10);
// ürün listesi
log("Liste des produits", 2);
List<Produit> produits = getDao().getAllProduits();
affiche(produits);
// Alınan liste ile kalıcı listenin aynı olup olmadığı kontrol edilir
for (Produit produit : produits) {
Produit found = mapProduits.get(produit.getId());
Assert.assertEquals(found, produit);
mapProduits.remove(found.getId());
}
// tüm başlangıçtaki ürünler sözlükten silinmiş olmalıdır
Assert.assertEquals(0, mapProduits.size());
}
}
- 4. satır: Veritabanına 10 ürün eklenir;
- 7. satır: Bu işlem tamamlandıktan sonra, veritabanındaki tüm ürünleri görüntülemek isteniyor;
- 8. satır: Ürünler görüntülenir. Amaç, ürünlerin başarıyla kaydedildiğini ve bir birincil anahtara sahip olduğunu doğrulamaktır;
- satır 10-13: Bulunan ürünlerin, veritabanına kaydedilenlerle aynı olduğunu ve [mapProduits] sözlüğünde bulunabildiğini kontrol ediyoruz;
- 11. satır: Veritabanından getirilen ürünle aynı birincil anahtara sahip olan ürünü sözlükten alıyoruz. Bu, kalıcı hale getirilen ürünlerin birincil anahtar aldığını gösterir;
- 12. satır: İki ürünün aynı olduğundan emin olunur. [Produit] sınıfının bir [equals] yöntemi tanımladığını hatırlatırız (bkz. 3.3.4. paragraf);
- 13. satır: Bulunan öğe sözlükten silinir;
- satır 16: başlangıçtaki ürün sözlüğünün gerçekten boş olduğu kontrol edilir; bu, başlangıçtaki bu ürünlerin hepsinin veritabanından getirilen ürün listesinde mevcut olduğu anlamına gelir;
8. satırdaki [affiche] yöntemi, aşağıdaki özel yöntemdir:
// ürün listesinin görüntülenmesi
private <T> void affiche(List<T> elements) throws JsonProcessingException {
for (T element : elements) {
System.out.println(jsonMapper.writeValueAsString(element));
}
}
- 2. satır: [affiche] yöntemi, genel bir yöntemdir. Bu yöntem, sözdizimsel olarak <T> olarak gösterilen bir T türü ile parametrelenmiştir. Eğer T1 ve T2 olmak üzere iki tür ile parametrelenmiş olsaydı, <T1,T2> şeklinde yazılırdı. T türüyle parametreleştirilen m yönteminin sözdizimi şöyledir:
m yönteminin kodunda T türündeki veriler bulunur. C sınıfının c örneğinin m yöntemi şu şekilde çağrılabilir:
burada T1, m yönteminin biçimsel türü T'nin yerine geçecek olan etkin türdür. Çoğu zaman derleyici, m yönteminin argümanlarına göre T1 türünü çıkarabilir. Bu nedenle, yukarıdaki ifade çoğu zaman şu şekilde basitleştirilir:
[affiche] yöntemine geri dönelim. Bu yöntem, T türündeki öğelerin bir listesini görüntüler. Bu, 4. satırda kullanılan jSON eşleştiricisinin her tür nesnenin jSON temsilini oluşturabilmesi sayesinde mümkündür. Bu örnekte, kullanılan tek T türü [Produit] türü olacaktır.
[affiche] yöntemi şu şekilde de yazılabilirdi:
// ürün listesinin görüntülenmesi
private void affiche(Object o) throws JsonProcessingException {
System.out.println(jsonMapper.writeValueAsString(o));
}
Etkin parametre bir ürün listesi olduğundan, 3. satır bu listenin jSON temsilini yazardı. Bu, her bir öğenin temsilini tek tek yazmakla aynı şey değildir.
[getProduits] testi ile elde edilen çıktı şöyledir:
3.6.9.4. [getProduitBy] testi
Bu testin çıktısı şöyledir:
@Test
public void getProduitBy() {
// doldurma
fill(10);
log("getProduitBy", 1);
Produit produit = getDao().getProduitByName("NOM3");
Produit produit2 = getDao().getProduitById(produit.getId());
Assert.assertNotNull(produit2);
Assert.assertEquals(produit2.getNom(), produit.getNom());
Assert.assertEquals(produit2.getId(), produit.getId());
}
- 6. satır: [IDao] arayüzünün [getProduitByName] yöntemi, [NOM3] adlı ürünü geri getirmek için kullanılır;
- 7. satır: Ardından, [IDao] arayüzünün [getProduitById] yöntemi, bu kez birincil anahtarıyla tanımlanan aynı ürünü getirmek için kullanılır;
- 8-10. satırlar: [produit2] ve [produit]'in aynı özelliklere sahip olduğu kontrol edilir;
3.6.9.5. [doInsertsInTransaction] testi
Bu test şu şekildedir:
@Test
public void doInsertsInTransaction() {
log("Ajout de deux produits de même nom", 1);
// ekleme işlemi gerçekleştiriliyor
List<Produit> inserts = new ArrayList<Produit>();
inserts.add(new Produit(0, "x", 1, 1.0, ""));
inserts.add(new Produit(0, "x", 1, 1.0, ""));
boolean erreur = false;
try {
getDao().addProduits(inserts);
} catch (DaoException daoException) {
erreur = true;
}
// kontroller
Assert.assertTrue(erreur);
List<Produit> produits = getDao().getAllProduits();
Assert.assertEquals(0, produits.size());
}
- 5-7. satırlar: [x] adında iki üründen oluşan bir liste oluşturulur;
- 10. satır: Bu iki ürün, boş olan [PRODUITS] tablosuna eklenir ([clean] yöntemi, [@Before] ile işaretlenmiştir). İlk ekleme işlemi gerçekleşecek, ancak ikinci ekleme gerçekleşmeyecektir çünkü [PRODUITS] tablosunda ürün adları üzerinde tekillik kısıtlaması vardır. Dolayısıyla bir istisna oluşmalıdır. Bu istisna 15. satırda test edilmektedir;
- [IDao] arayüzündeki tüm yöntemler bir işlem içinde gerçekleştirildiğinden, ikinci eklemenin başarısız olması işlemin tamamını, dolayısıyla ilk eklemeyi de iptal edecektir. Sonuç olarak, [PRODUITS] tablosuna hiçbir ekleme yapılmamalıdır;
- 16-17. satırlar: [PRODUITS] tablosunda bulunan ürünlerin listesini isteyerek ve bu listenin boş olduğunu kontrol ederek bu noktayı doğrularız;
3.6.9.6. [updateProduits] Testi
Bu test şu şekildedir:
@Test
public void updateProduits() {
// doldurma
fill(10);
log("Mise à jour du prix des produits de catégorie 1", 1);
// ürünleri alıyoruz
List<Produit> produits = getDao().getAllProduits();
// kategori 1'deki ürünleri güncelleme
List<Produit> updated = new ArrayList<Produit>();
int nbUpdated = 0;
for (Produit produit : produits) {
if (produit.getCategorie() == 1) {
// int id, String isim, int kategori, double fiyat, String açıklama
updated
.add(new Produit(produit.getId(), produit.getNom(), 1, produit.getPrix() * 1.1, produit.getDescription()));
nbUpdated++;
}
}
int nbProduits = getDao().updateProduits(updated);
// kontroller
// Assert.assertEquals(nbUpdated, nbProduits); -- DB2 ile çalışmıyor
for (Produit produit : updated) {
Produit produit2 = getDao().getProduitById(produit.getId());
Assert.assertEquals(produit2.getPrix(), produit.getPrix(), 1e-6);
}
}
- 4. satır: veritabanına 10 ürün eklenir;
- 7. satır: Ürünleri geri alınır;
- 9-18. satırlar: 1 numaralı kategorideki ürünlerin fiyatları %10 artırılır;
- 19. satır: bu değişiklikler veritabanına kaydedilir;
- satır 22-25: güncelleme için kullanılan ürün listesi bellekte taranır. Her bir ürün için, veritabanında aynı birincil anahtara sahip ürün aranır ve fiyat güncellemesinin başarıyla gerçekleştirilip gerçekleştirilmediği kontrol edilir;
- 19. satır: [updateProduits] işlemi ile güncellenen ürün sayısı alınır;
- 21. satır: Bu sayının beklenen değerle aynı olup olmadığı kontrol edilir. Bu test, SGBD ve DB2 hariç tüm SGBD işlemleri için başarılıdır. Bu nedenle bu satır yorum satırı olarak işaretlenmiştir;
3.6.9.7. [deleteProduits] testi
Bu test şu şekildedir:
@Test
public void deleteProduits() {
// doldurma
fill(10);
log("deleteProduits", 1);
// ürün listesi
List<Produit> produits = getDao().getAllProduits();
// iki ürünün silinmesi
Produit produit0 = produits.get(0);
Produit produit5 = produits.get(5);
int nbDeleted = getDao().deleteProduits(new int[] { produit0.getId(), produit5.getId() });
// kontroller
// Assert.assertEquals(2, nbDeleted); -- DB2 ile çalışmıyor
Assert.assertNull(getDao().getProduitById(produit0.getId()));
Assert.assertNull(getDao().getProduitById(produit5.getId()));
Assert.assertEquals(produits.size() - 2, getDao().getAllProduits().size());
}
- 4. satır: Veritabanına 10 ürün eklenir;
- 7-11. satırlar: veritabanındaki tüm ürünleri alırız ve 0 ve 5 numaralı konumlarda bulunan ürünleri veritabanından sileriz;
- satır 14-16: İki ürünün artık veritabanında bulunmadığını ve veritabanındaki ürün sayısının iki azaldığını kontrol ediyoruz;
- 13. satırdaki test, SGBD ve DB2 ile başarısız oluyor. Diğer SGBD ile başarılı oluyor;
3.6.9.8. Performans testleri
Testlere, yalnızca SGBD'in performansını değerlendirmek amacıyla üç yöntem dahil edilmiştir:
@Test
public void perf1() {
// doldurma
fill(10000);
}
@Test
public void perf2() {
// doldurma
fill(10000);
// değişiklik
List<Produit> produits = getDao().getAllProduits();
// 1. kategoridekiler güncelleniyor
List<Produit> updated = new ArrayList<Produit>();
for (Produit produit : produits) {
// int id, String isim, int kategori, double fiyat, String açıklama
updated.add(new Produit(produit.getId(), produit.getNom(), 1, produit.getPrix() * 1.1, produit.getDescription()));
}
getDao().updateProduits(updated);
}
@Test
public void perf3() {
// verileri doldurma
fill(10000);
// silme
List<Produit> produits = getDao().getAllProduits();
// birincil anahtarlar
int[] keys = new int[produits.size()];
for (int i = 0; i < keys.length; i++) {
keys[i] = produits.get(i).getId();
}
getDao().deleteProduits(keys);
}
- 1-5. satırlar: 10.000 ürünün eklenmesi;
- satır 8-20: 10.000 ürünün eklenmesi ve ardından birincil anahtarları aracılığıyla bu ürünlerin değiştirilmesi;
- satır 23-34: 10.000 ürünün eklenmesi ve ardından birincil anahtarları aracılığıyla silinmesi;
[JUnitTestDao1] ve [JUnitTestDao2] testlerini çalıştırmak için aşağıdaki çalıştırma yapılandırmaları kullanılabilir:
![]() | ![]() |
[JUnitTestDao1] testinin sonuçları şunlardır:
![]() |
[1]'te [JUnitTestDao1]'in sonuçları, [2]'te ise [JUnitTestDao2]'in sonuçları yer almaktadır. Aralarında önemli bir fark yoktur. [1]'te:
- test başarılıdır;
- 10.000 ürünün eklenmesi 3,15 saniye sürer;
- 10.000 ürünün eklenmesi ve ardından değiştirilmesi 4,80 saniye sürer;
- 10.000 ürünün eklenmesi ve ardından silinmesi 4,40 saniye sürer;
- dolayısıyla en zaman alıcı işlem ekleme;



















































