Spring ve JPA

Daha önce Hibernate kullanarak nasıl REST servislerinin oluşturulacağı ile ilgili bir yazı dizisi yazmıştım. Ama bildiğiniz üzere doğrudan hibernate kullanmak yerine, java ee standartı olan JPA (Java Persistance API)’da kullanabilirsiniz. Bu yazımda size JPA ile spring’i nasıl entegre edebileceğini anlatmaya çalışacağım. Yazıyı yazarken JPA’nın nasıl kullanıldığını bildiğinizi varsayıyorum. En azından Entity tanımlarınızı yapabilmeli ve DAO katmanınızı yazabilmelisiniz.

Daha önce ki spring yazısında olduğu gibi burada Araba yapısı üzerinden gidelim. Bu örnek boyunca bir araba modeli oluşturup onu JPA entity’si haline nasıl getireceğimize bakacağız. Ardından DAO ve Service katmanını yazacağız. Bu katmanları yazarken Transaction yönetimini nasıl yaptığımızı ve spring’in bize bu konuda ne gibi kolaylıklar sağladığını göreceğiz. Bildiğiniz üzere daha önce yazdığım yazılarda hep transaction’ları biz yönetiyorduk. Günümüzdeki middleware kütüphaneleri düşünülürse artık buna hemen hemen hiç gerek kalmadı. Fakat yine de hala ihtiyaç olduğunuz durumlar olursa kendiniz yönetebiliyorsunuz.

Projenin Yaratılmasıspring-jpa

Projemizi her zaman olduğu gibi maven kullanarak oluşturucağız. Ardından projemizi Eclipse ortamına taşıyıp geliştirmelerimizi yapacağız. Projemiz basit bir Konsol uygulaması olacak. Fakat context kullanarak uygulama geliştireceğimiz için, context’imizi web uygulamasına yüklerseniz web, OSGi uygulamasına yüklerseniz OSGi üzerinde çalışacaktır. Temelde Araba projesinin veritabanına kaydedilmesi üzerine olacak. Object-First bir yaklaşım izleyeceğiz. Yani biz nesnelerimizi yazacağız, nesnelerimizden veritabanımız oluşturulacak. Diğer alternatifimiz ise DB-First çalışmak. Bu daha önceki REST yazı dizisinde anlattığımız şekilde işliyor. Öncelikle veritabanının tasarımı yapılıyor, veritabanı gerekli normalizasyonlardan geçiyor ardından Java nesneleri üretilip JPA bağlantıları yapılıyor. Dediğim gibi biz Object-First çalışacağız, veritabanı ile uğraşmayacağız böylelikle bu tutorial daha kolay (yani umubudum bu yönde) olacak. Veritabanı olarak H2 kullanacağız. Ama projemizi sanki üç aşaması (Dev, UAT ve Prod) varmış ve üç aşamada da farklı veritabanı(H2, MySql, PostgreSQL) varmış gibi tasarlayacağız. Böylelikle tek parametre değişikliğiyle ortamımızı(environment) değiştirmiş olacağız. Veritabanımız nesnelerimize göre sıfırdan yaratılacağı için tablolarla falan uğraşmamız gerekmeyecek.

Maven

Projeden kısaca bahsettiğimize göre Maven ile projeyi oluşturmaya başlayabiliriz. Maven ile, maven-archetype-quickstart  archetpye’ını kullanarak projemizi oluşturuyoruz. Benim örneği yaparken kullandığım diğer bilgiler aşağıdaki gibidir.

  • Group Id: com.bahadirakin
  • Artifact Id:spring-jpa

Projemiz oluştuğuna göre, projemizin bağımlılılarını ekleyebilir ve nasıl build olacağını belirleyebiliriz. Bunun için spring-jpa klasörünün altındaki pom.xml dosyasını aşağıdaki şekilde değiştiriyoruz.

pom.xml

pom.xml dosyamız ile ilgili dikkat etmemiz gereken kısımlar;

  • Gördüğünüz üzere tüm veritabanı bağımlılıklarını ekledik. Bu bağımlılıklar ortam değişikliğinde işimize yarayacak. Her ortam için farklı build çıkmaktansa gerekli Driver bağımlılıklarını vermek daha mantıklı.
  • JPA implementasyonu olarak her zamanki gibi Hibernate kullanıyoruz.

Projemizi oluşturduğumuza göre artık geliştirmeye başlayabiliriz. Buradan sonra aşağıdaki sırada ilerleyeceğiz;

  1. Veritabanı ayarlarının yapılması: Burada veritabanı bağlantı bilgilerini, JPA tanımlamalarını, Transcation yönetimini anlatacağım. Aynı şekilde farklı ortamlar(environment) için farklı ayarları (Dev, UAT, Prod) burada tanımlayacağız.
  2. Model, DAO ve Servislerin Tanımlanması: Burada JPA modelimizi, basitçe tanımlanmış DAO ve Servis yapılarından bahsedeceğiz.
  3. ApplicationContext ve Uygulama: Burada applicationContext.xml dosyasını tanımlayacağız. JPA ve Servisler ile ilgili son bağlantıları yapıp, main fonksiyonunu içeren sınıfı yazacağız.

Veritabanı Ayarlarının Yapılması

Şimdi projemizi oluşturduğumuza göre veritabanı tanımlamalarımızı ve ortam(environment) tanımlamalarımızı yapabiliriz. Öncelikle  src/main/resources altına environment diye bir klasör tanımlıyoruz. Sonra  3 farklı ortamımızın olacağından bahsettik. Bu ortamları ve gerekli dosyalarını aşağıdaki şekilde tanımlıyoruz.

  1. DEV: Geliştirme ortamımız. H2 veritabanına bağlanacak. environment klasörünün altına persistance-dev.properties isminde bir dosya yaratıyoruz.
  2. UAT: Kullanıcı kabul testlerinin yapıldığı ortam. Bu ortamın veritabanınında MySql olduğunu varsayalım. Bunun için de yine environment klasörünün altına persistance-uat.properties isminde bir dosya yaratıyoruz.
  3. Prod: Canlı ortamımız. Bu ortamın veritabanı da PostgreSql ve ilgili dosyasının adı da persistance-prod.properties olsun.

Şimdi bu üç dosyanın içeriğine bakalım.

persistance-dev.properties

persistance-uat.properties

persistance-prod.properties

Yazıya başlarken belirttiğim gibi daha önce JPA kullandığınızı varsaydığımı belirtmiştim. Bu sebepten bunların detayına girmeyeceğim. Fakat yinede detaylı bilgi almak isterseniz bu adresten öğrenebilirsiniz.

Gördüğünüz üzere sadece DEV ortamı “create” modunda çalıştırılmış. Bu sebepten uygulama başlatıldığında veritabanı sıfırdan yaratılacak. Yine bu parametreden dolayı uygulama projemizin altında bulunan bir import.sql dosyası görürse, bu dosyanın içerisinde ki SQL cümleciklerini çalıştıracaktır. Ben de bundan yararlanmak için projenin  src/main/resources klasörünün altına import.sql diye bir dosya oluşturdum ve içeriğini aşağıdaki gibi ayarladım.

import.sql

Buradan zaten Car nesnemizi ileride nasıl tanımlayacağımızı çıkarabilirsiniz.

Şimdi tüm ortamlarımızı ve bunların özelliklerini ayarladığımıza göre sıra persistenceContext.xml dosyamızı oluşturmaya geldi. Normal bir JPA projesinde persistence.xml dosyasını src/main/resources/META-INF klasörünün içerisine koyarsınız. Fakat biz parametrik hale getirdiğimizden hepsinin spring tarafından yönetilmesini tercih ediyoruz. Bu sebepten persistence.xml içerisinde olabilecek bilgileri properties dosyalarında tutuyoruz. Bu bilgileride spring içerisinde okuyup spring içerisinden EntityManager nesnelerimizi yaratacağız. Bizim projemizde persistenceContext.xml spring context dosyasını src/main/resources altına yaratıyoruz.

persistenceContext.xml

Şimdi burada dikkatinizi çekmek istediğim bir çok konu var.

  • Öncelikle 12. ve 23. satıra odaklanmanızı istiyorum. Bu iki satırda properties dosyasını okuyoruz ve spring’e bu properties dosyasını spring xml dosyalarında kullanmasını söylüyoruz.
  • Buradaki diğer bir ayrıntıda, properties dosyasının içerisinde tanımladığımız değerleri Java komut satırı parametresi olarak override edebileceğimiz. Örneğin properties dosyasında tanımladığımız jdbc.driverClassName değerini -Djdbc.driverClassName parametresi kullanarak override edebiliriz.
  • Şimdi ise 17. satıra odaklanalım. Burada hangi properties dosyasını yükleyeceğimize karar veriyoruz. Bu satırda ${environment:dev} şeklinde verilen ifadenin tercümesi şu şekilde, environment diye bir parametreye bak. Eğer environment diye bir parametre verilmediyse “dev” değerini kullan. Örneğin biz sisteme -Denvironment=prod parametresini geçersek prod ortamı için -Denvironment=uat geçersek uat ortamı için uygulama ayağa kalkacak. Buda uygulamanın farklı veritabanlarına bağlanması demek.
  • 27. satırda ise klasik bir datasource tanımlaması yapıyoruz. Ben datasource’u spring context tarafından c3p0 ile yönetmeyi tercih ediyorum. Ayrıntılı bilgi için bu makaleye bakabilirsiniz. Fakat ortamınıza göre isterseniz jndi’da kullanabilirsiniz.
  • 44. satırda EntityManagerFactory’mizi yaratıyoruz. Bu persistence.xml ile yaratılan nesnenin aynısı. Fakat bizde persistence.xml dosyası olmadığından burada kendimiz yaratıyoruz.
  • 50. satırda persistence.xml yerine kullandığımız properties dosyasının referansını veriyoruz.
  • 54. satırda ise modellerimizi yani entity nesnelerimizi nerede bulacağını belirtiyoruz.
  • Son olarak 63. satırda ise transaction’ları nasıl yöneteceğimizi seçiyoruz. Burada annotasyon ile yöneteceğimizi belirttik. İstersek AOP ile de yönetebilirdik. Fakat bence annotasyon ile yönetmek çok daha okunaklı.

Model, DAO ve Servislerin Tanımlanması

Evet persistenceContext.xml dosyamızı yarattığımıza göre tüm veritabanı ayarlarımızı yaptık demektir. Şimdi ise modellerimizi, DOA ve servislerimizi yaratmaya başlayabiliriz. persistenceContext.xml içerisinde modellerimizin com.bahadirakin.model paketinde bulunacağını belirtmiştik. Öncelikle bu paketi oluşturalım. Ardından ise içerisine Car.java sınıfını aşağıdaki gibi oluşturalım.

Car.java

Burada çok yeni bişey yok. Bildiğiniz Entity tanımlamaları. Bundan dolayı çok üzerinde durmayacağım.

Modelimizi tanımladığımıza göre DAO katmanını tanımlamaya geçebilirim. DAO katmanını tanımlarken çok fazla soyutlamaya gitmeyeceğim. Eğer soyutlama ile ilgili detaylı bilgi almak istiyorsanız bu yazıma bakabilirsiniz. Öncelikle dao arayüzümüzü tanımlayalım. Bunun için com.bahadirakin.dao paketini yaratıp içerisine ICarDao arayüzünü oluşturuyoruz.

ICarDao.java

Gördüğünüz üzere olabildiğince basit bir DAO sınıfımız var. Bu örnek için bize yeterli olacaktır. Şimdi ise yine com.bahadirakin.dao paketinin altına implementasyonunu, CarDao‘yu yaratıyoruz.

CarDao.java

Burada dikkat etmenizi istediğim kısımlar şu şekilde;

  • Veritabanı ayarlarını yaparken, persistenceContext.xml içerisinde transactionları annotation ile yöneteceğimizi belirtmiştik. Bu sebepten burada annotasyon kullanabiliyoruz.
  • @Transaction annotasyonu ile birlikte verdiğimiz value parametresi yine persistenceContext.xml içerisindeki tanımlamalardan geliyor.
  • @Transaction annotasyonu tüm sınıfa verilebileceği gibi metod başınada tanımlanabilir. Metod başına tanımlama, class başına olan tanımlamayı ezecektir.
  • @Transaction içerisinde tanımlı olan propagation, transaction’ın ne şekilde işleneceğini belirler. Bu tanımlamada, bu sınıftaki bir metod kullanılmadan önce transaction’ın açılmış olması gerektiği belirtilmiştir. Trasnaction’ımızı servis katmanında açacağız.
  • @PersistenceContext annotasyonu içerisinde verdiğimiz “entityManagerFactory” bilgisi yine persistenceContext.xml içerisinde tanımlanmıştır.

Transaction ve Propagation

Transaction annotasyonu ile birlikte verilen propagation’ın alabileceği değerler ve anlamları aşağıdaki gibidir.

DeğerAnlamı
REQUIREDEğer açık bir transaction varsa onu kullanır. Eğer yoksa yeni bir transaction yaratır
SUPPORTSEğer açık bir transaction varsa onu kullanır. Yoksa yenisini oluşturmaz, trasnactionalsız devam eder. Eğer metodunuz içerisinde transaction gerektirecek (Insert,Update,Delete vs) bir operasyon yoksa sorunsuz şekilde çalışmaya devam eder.
MANDATORYEğer açık bir trasnaction varsa onu kullanır. Yoksa exception fırlatır.
REQUIRES_NEWEğer açık bir trasnaction varsa onu beklemeye alır. Her durumda yeni bir trasnaction yaratır ve metod bitiminde trasnaction’ı kapatıp beklettiği transaction’ı devreye alır.
NOT_SUPPORTEDEğer açık bir transaction varsa onu beklemeye alır. Metodu transactionsız çalıştırır. Metod bitiminde beklettiği transaction’ı devreye alır.
NEVERMetodu transactionsız olarak çalıştırır. Eğer aktif bir transaction varsa exception fırlatır.
NESTEDNested transaction desteği olan JTA ortamlarında, aktif olan transaction içerisinde başka bir transaction yaratır. Uygulamamız konsol da çalıştığından bu özelliği desteklememektedir.

Dao’muzu yarattığımıza göre şimdi servislerimizi yaratabiliriz. Yine servis katmanımızda çok kaliteli bir katman yapısı olmayacak. Şimdilik amaca yönelik diyebiliriz. Öncelikle com.bahadirakin.service paketimizi oluşturalım ve içerisine ICarService arayüzümüzü tanımlayalım.

ICarService.java

Arayüz kısmımızda çok anlatılacak bir durum yok. Basit bir arayüz sınıfı. Yine aynı paketin içerisine bu sefer CarService impelemtasyon sınıfımızı yaratalım ve içerisini aşağıdaki gibi yapalım.

CarService.java

Burada yine @Transactional annotasyonumuzu kullandık. Fakat DAO’dan farklı olarak, REQUIRED olarak işaretledik. Böylelikle servis sınıfı içerisinde bir method çağrıldığında yeni bir Transaction’ın yaratılacağını garantilemiş oldu.

ApplicationContext ve Uygulama

Tüm katmanlarımızı tanımladığımıza ve veritabanı ayarlarımızı yaptığımıza göre, tüm bunları birbirine bağlayabilir ve uygulamamızı test edebiliriz. Öncelikle katmanlarımızla servislerimizi birbirine bağlayalım. Bunun için src/main/resources klasörünün altına applicationContext.xml dosyamızı yaratıyoruz ve içeriğini aşağıdaki gibi ayarlıyoruz.

applicationContext.xml

Gördüğünüz gibi persistenceContext.xml dosyamızı kolaylıkla ekledik ve gerekli DAO ve servis tanımlamalarımızı yaptık. Şimdi projemiz maven ile ilk oluştuğunda oluşan com.bahadirakin paketinin atlındaki App.java dosyasına gidiyoruz ve içeriğini aşağıdaki ile değiştiriyoruz.

App.java

Burada basitçe kayıtlı olan tüm Car nesnelerini veritabanından çekiyoruz ve ekrana basıyoruz. Eğer benim yukarıda paylaştığım import.sql dosyasını kullanırsanız  25 tane araba nesnesinin çekilmesi ve konsola basılması gerekiyor.

Songitcat

Dev ortamı için veritabanına başarılı şekilde bağlanabildik. Diğer iki ortam için uygulamanızı çalıştırmak isterseniz -Denvironment=uat ya da -Denvironment=prod parametrelerini kullanabilirsiniz. Her ne kadar uat ve prod testlerini yapmamış olsam da çok zorlanacağınızı düşünmüyorum. Maksimum jdbc.url parametresini değiştirmeniz yeterli olacaktır. Projenin tamamına aşağıdaki git adresinden erişebilirsiniz.

End Of Line