Java Persistence API (JPA) ile Programlama – I

Aşağıdaki yazı Java Dergisi’nin 2. sayısında yayınlanmıştır. Küçük değişikliklerle burada tekrar yayınlıyorum.

Giriş

Üç yazıdan oluşacak “JPA Programlama” isimli yazı dizisinin bu ilk kısmında, önce nesne-ilişkisel uymsuzluğunu ele alıp hızlıca tarihi olarak bu uyumsuzluğu gidermek için ortaya çıkan çözümlerden bahsedeceğiz Sonrasında Java’nın bu konudaki en son bileşeni olan JPA’ya (Java Persistence API) giriş yapacağız. Bu yazıda, JPA’in en temel özelliklerini, basit nesne-ilişkisel eşleştirmesini, EntityManager API’sini ve EntityTransaction nesnesiyle işlem (transcation) yönetimini ele alacağız. Sonraki iki yazıda daha karmaşık, MxN gibi, nesne-ilişkisel eşleştirmelerini ve diğer eşleştirme özelliklerini, nesne sorgulama (Object Query) yeteneklerini ve bir uygulama sunucusunun bulunduğu ortamda (managed) JPA davranışı ile JPA 2.0’ın getirdiklerini ele alacağız.

Nesne-İlişkisel Eşleştirme

Nesne-merkezli diller ile geliştirme yapanlar, tabii olarak düşünce sistemlerini nesne kavramı etrafında örerler. Yazılımda çalışma zamanında (run-time) üretilen nesnelerin taşıdıkları bilgilerin daha sonra kullanılmak üzere kalıcı hale getirilmesinin en tabii yöntemi, olsa olsa bir nesne-merkezli veri tabanı yönetim sistemi (NMVTYS) (object-oriented database management system, OODBMS) kullanmaktır. Fakat gerçekte durum biraz farklıdır; burada bahsedilmesi gerekmeyen çok değişik nedenden dolayı veriyi kalıcı kılmanın en tabii yolu olarak ilişkisel veri tabanı yönetim sistemleri (İVTYS) (Relational Database Management Systems, RDBMS)  günümüzde en yaygın kullanılan olmayı başarmışlardır.

Uygulamadaki sınıflar/nesneler ile aralarındaki ilişkilerin ifade ediliş şekli, İVTYS’deki tablolar ve aralarındaki ilişkilerin ifade edilişinden pek çok bakımdan farklılık arzeder. Bu iki sistem, kapsülleme (encapsulation), tip, erişim, güvenlik vb. pek çok temel konuda, değişik çıkış noktalarına sahip olmalarından dolayı, farklı yaklaşımlar benimsemişlerdir. Bu durum nesne-ilişkisel uyumsuzluğu (object-relational mismatch) olarak adlandırılır. Nesnelerin tablolarda kalıcı hale getirilmeleri için uygulamadaki sınıfların veri tabanındaki tablolara, sınıflarda tanımlanan nesne özelliklerinin yani nesne değişkenlerinin (instance/object variables) de bu tablolardaki sütunlarla/kolonlarla eşleştirilmesi gerekmektedir. Buna da “nesne-ilişkisel eşleştirme” (NİE) (Object-Relational Mapping, ORM) denir.

Nesne-merkezli diller ile geliştirilen uygulamalarda nesne-ilişkisel uyumsuzluğunu tekrar tekrar yaşayan programcıların bu problemi aşmak için geliştirdikleri çözüm, İVTYS önüne konulacak bir katmanla, İVTYSnin NMVTYSe dönüştürülmesidir. Yani İVTYS ile uygulama arasına konulacak böyle bir katmanın iki arayüzü olacaktır: Birisi SQL’ı kullanarak İVTYS ile haberleşen arayüz, diğeri ise bir API üzerinden nesnelerle alakalı YOGS işlemlerini yapmaya izin veren arayüz. Dolayısıyla bu katman, uygulama programcılarını, arkadaki İVTYSinin ilişikisel yapısından uzak tutacak ve onlara, nesneleri sanki NMVTYSinde kalıcı kılıyormuş gibi program yazma imkanı sağlayacaktır.

Java NİE Araçlarının Kısa Tarihi

Kurumsal Java yani Java EE’nin, o zamanki adıyla J2EE’nin ilk sürümü olan Aralık 1999 tarihli 1.1 versiyonunda kurumsal Java bileşenlerinin (Enterprise JavaBean ya da EJB) iki türünden birisi olan entity bean, bir NİE aracı idi ve Java ile geliştirilen uygulamaların veri katmanını halledecek şekilde geliştirilmişti. Büyük bir hüsnü kabul ile karşılanmasına rağmen EJB’ler ve özellikle de entity beanleri kısa sürede hayal kırıklığı yarattı. Aynı zamanda birer uzak/dağıtık (remote ya da distributed) nesne olan EJBler, gerek işin doğasının zaten çok zor olması gerek ise aşırı akademik yaklaşımdan dolayı kullanıcılarına öğrenme, uygulama ve özellikle de çalışma zamanındaki performans ve ölçeklenirlik açısından ciddi sıkıntılar yaşattı. Her ne kadar Sun iki sene sonra, 2001 güzünde, EJB 2.0’ı yayınlayıp, özellikle performans ve ölçeklenirlik konusunda belli ilerlemeler sağladıysa da entity beanlere olan güvenin azalmış olması gerek Sun’ı gerek ise Java programcılarını veri katmanınında kullanacakları NİE aracı konusunda alternatifler aramaya itti. Bu süreçte Castor (http://www.castor.org), iBatis (http://ibatis.apache.org/), Hibernate (http://www.hibernate.org) ve 1990’lı yılından bu yana Smalltalk dünyasında ciddi bir tecrübe kazanan TopLink gibi, bazıları açık kaynak kodlu bağımsız NİE araçları ortaya çıktı. Bu araçlar Javacılar tarafından büyük bir kabul ile uygulamaya kondu. Entity beanlerdeki sıkıntının farkında olan Sun 2000’li yılların başında konu ile alakalı JDO (Java Data Objects) isimli bir çözümü dünyaya duyurdu ama değişik sebeplerden dolayı JDO da yaygın bir kullanım alanı bulamadı.

JPA (Java Persistence API)

Yukarıda bahsi geçen bütün bu bağımsız NİE araçları ile alakalı en temel problem hepsinin farklı bir yaklaşıma dolayısıyla da bir API’ye sahip olmalarıydı. Bu yüzden bu çözümler birbirlerinin yerine geçebilir durumda değillerdi. Bu süreçte Sun, NİE problemine bir başka çözüm önermek üzere yola çıktı ve eski tecrübeleri ile Toplink ve Hibernate gibi başarılı araçların birikimlerini bir araya getirerek JPA (Java Persistence API) isimli yeni bir bileşen yayınladı. Ana teması “daha kolay geliştirme” olan Java EE 5’in içindeki EJB 3.0’ın (http://jcp.org/en/jsr/detail?id=220) bir parçası olarak 2006 yılında Javacılara sunulan JPA 1.0, büyük bir ilgiyle karşılalandı. Bağımsız NİE üreticilerinin pek çoğu, örneğin Hibernate ve TopLink, JPA uyumlu ürünlerini peşi sıra piyasaya çıkardılar. (Dolayısıyla Hibernate ve TopLink gibi ürünlerle hem JPA hem de kendi özel (native) API’leri üzerinden programlama yapılabilmektedir. Dergimizin bu sayısında Hibernate’in özel yani orijinal (native) API’sini kullanarak nasıl program yazacağınızı anlatan başka bir yazı da mevcuttur.) Bu durum en başından bu yana tanım (specification) tabanlı ilerleyen Java’nın yapısına çok daha uygundu ve Javacılar artık standart bir API üzerinden pek çok farklı NEI ürününü kullanabilir hale gelmişlerdi. 2009 Aralık’ında yayınlanan Java EE’nin 6. sürümünün bir parçası olarak yayınlanan JPA’in 2.0 sürümü, 1.0 sürümüne yaptığı eklerle JPA’yi çok daha güçlü hale getirdi.

JPA özellikle entity beanlere kıyasla çok daha basit ve rahat öğrenilen ve kullanılan bir tabiatta olacak şekilde geliştirildi. JPA öncelikle POJO (Plain Old Java Objects) modeline sahiptir. Ayrıca JPA hem Java SE hem de Java EE ortamlarında kullanılabilmektedir. Nesneler arasındaki bire-çoklu ve çoka-çoklu ilişkiler yanında miras (inheritance) ve çok şekillilik (polymorphism) gibi özellikleri de destekleyen JPA, ayar bilgilerini programcıya ister XML’de isterse de Java notlarında (annotation) ifade etme imkanı da tanımaktadır. Yapılmayan ayarlar konusunda varsayılan (default) davranışlara da sahip olan JPA, Javacılara son derece basit bir API üzerinden nesnelerini kalıcı kılma imkanı da vermektedir. Dizinin bu kısmında JPA’nin temel özellikllerine odaklanıp Java SE ortamında kullanımını ele alacağız.

Entity

JPA’yı kullanarak veri tabanında kalıcı kılınacak olan nesneye entity denir. Bir nesnenin entity olması için gerek ve yeter şart o nesnenin kendisinden üretileceği sınıfın ya @Entity notuyla notlandırılması ya da XML ayarlarında entity olarak ifade edilmesidir. (Biz bu yazıda genel olarak notlu ayarı temel alacağımızdan okuyucu, aksi söylenmedikçe bütün ayarların notlarla ifade edildiğini varsaymalıdır.) Entity bir iç sınıf (inner class), bir interface ya da enum tipi veya final olmamalıdır ve public ya da protected olan bir argumansız yapılandırıcısı (no-arg constructor) olmalıdır.

Entitynin veri tabanında saklanancak olan kalıcı durumu (persistent state), tamamen nesne değişkenlerinden oluşacaktır. Bu yüzden kalıcı durumun parçası olacak nesne değişkenleri final olmamalıdır. Nesne değişkenleri private olabilir hatta olmalıdır ve kalıcı olan nesnenin istemcileri (client), nesnenin değişkenlerine get/set metotları ile ulaşmalıdır. Entitylerin kalıcılığının minimum sayıda alandan (nesne değişkeni) oluşması gerektiği düşünüldüğünde, veri tabanında kalıcı olması gerekmeyen değişkenler @Transient ile notlandırılmasının gerekir.

Bir sınıfın entity olması için teorik olarak zorunlu olmamakla birlikte konulmadığında pratikte pek çok sıkıntı çıkardığı için gerekli olarak belirttiğimiz bir diğer not ise @Id’dir. Bu not, notlandırdığı alanın, içinde bulunduğu nesnenin kimlik (identity) bilgisini ifade etmesini ve veri tabanında da bir anahtar alana (primary key) karşı gelmesini sağlamaktadır.

Yukarıdaki bilgiler ışığında aşağıdaki gibi bir sınıf JPA tarafından veri tabanında kalıcı hale getirilecektir:

package customer.p1;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Customer1 {

@Id
private long custId;
private String firstName;
private String lastName;

public Customer1(){}

public Customer1(long custId, String firstName, String lastName) {
this.custId = custId;
this.firstName = firstName;
this.lastName = lastName;
}
// set ve get metotları
}

(Örnek kodlar burada Eclipse projesi olarak bulunmaktadır. Tek yapmanız gereken Java Dergisi JPA-1.zip dosyasını açıp içindeki “Java Dergisi JPA-1” isimli projeyi import etmenizdir.)

Notlandırma ya da XML tabanlı ayarlarda, yapılmayan ayarla alakalı JPA’in varsayılan davranışa sahip olduğunu söylemiştik. Bu durumda JPA çalışma zamanı yapıları, veri tabanında @Entity ile notlandırılmış sınıfla aynı isme sahip bir tablo arayacak ve tablonun sütunlarının da sınıfın kalıcı alanlarıyla aynı isme sahip olmasını bekleyecektir. Özellikle geliştirme ve test amaçlı olarak hızlıca yazılan uygulamalarda JPA’in veri tabanında şema (schema) oluşturma yeteneğinden faydalanılır. Böyle bir ayar durumunda ayrıntısını daha sonra göreceğimiz EntityManager isimli nesne ilk oluşturulduğunda, JPA tamamen varsayılan ayarları kullanarak veri tabanında @Entity olarak notlandırılan sınıfların isimlerini kullanarak aynı isimde tablolar oluşturur ve tabloların sütunlarını da entity sınıflarının kalıcı olan alanlarının isimleriyle aynı yapar ve ve bu sütunlara uygun tipler atar. Gerçek hayatta ya zaten hali hazırda bir şemamız vardır ya da projemizde nesne modelini oluştururken bir taraftan da veri modelimizi oluşturuyor oluruz. Bu durumda JPA’in varsayılan ayarlarını kullanmak yerine notları kullanarak sınıflar ve sınıfların alanları ile tablolar ve tabloların sütunları arasında detaylı eşleştirme yaparız.

Yukarıda kodu verilen Customer1 entitysinin veri tabanında varsayılan ayarlarla da olsa kalıcı hale getirilmesi için yapmamız gereken diğer iki şey ise bir JPA ürününü indirip bu projemiz için ayarlamak ve bir veri tabanını kullanılabilir hale getirmektir. Bu yazımızda JPA ürünü olarak Hibernate ve veri tabanı olarak ta Oracle XE kullanacağız. Oracle XE ile ilgili gerekli kısa bilgiye şu adresten ulaşabilirsiniz.

JPA Ayarları

JPA sağlayıcısı olarak kullanacağımız Hibernate’in en son sürümü olan 3.5.2’yi http://www.hibernate.org adresinden indirebileceğiniz gibi derginin CD’sinden de edinebilirsiniz. Hibernate’i projenizde kullanabilmek için şu jar dosyalarının classpathde olmasına dikkat edin: hibernate3.jar, hibernate-jpa-2.0-api-1.0.0.Final.jar, hibernate-distribution-3.5.2-Final\lib\required klasöründeki diğer 6 jar dosyası ve http://www.slf4j.org/dist/ adresinden indirebileceğiniz slf4j-1.5.4.zip içindeki slf4j-jdk14-1.5.4.jar dosyası.

JPA, bir JPA ürünü ve kullanılacak veri tabanıyla ilgili olarak yapılacak ayarları classpathde META-INF klasörünün altında persistence.xml isimli bir dosyada olmasını bekler. Biz de projemizde böyle bir dosya oluşturup içine Hibernate ve kullanacağımız Oracle XE veri tabanıyla alakalı en temel bilgileri aşağıdaki gibi verebiliriz:

org.hibernate.ejb.HibernatePersistence customer.p1.Customer1

Yukarıdaki dosyada öncelikle “customer1” isimli bir kalıcılık birimi (persistence unit) tanımlanmıştır. Bir persistence.xml dosyasında farklı isimlerde birden fazla kalıcılık birimi tanımlayabilir ve programlarımızdan bu birimlere isimleriyle ulaşabiliriz. Ayrıca tanımladığımız bu kalıcılık biriminin işlem (transaction) tipi ise “RESOURCE_LOCAL” olarak verilmiş durumda. “RESOURCE_LOCAL” işlem tipi, JPA’in işlemleri herhangi bir JTA altyapısı kullanmadan yöneteceğini ifade eder. Bu tipin alternatifi “JTA”dir ve bu durumda JPA kullanabileceği bir JTA ürünü arar ki en temelde böyle bir durum, bir uygulama sunucusunun varlığı halinde kullanılır. Biz bu yazımızda JPA’in sadece Java SE ortamında nasıl kullanıldığınından bahsedeceğimiz için işlem tipimiz daima “RESOURCE_LOCAL” olacaktır.

persistence.xml dosyasındaki her kalıcılık birimi için bir JPA sağlayıcı (provider) belirtilmelidir. Burada Hibernate’in ilgili sınıfını belirttik. Hemen peşinden ise hangi sınıfların kalıcı olacağı tam ismiyle (fully-qualified name) listelenir. Bu liste zorunlu olmamakla birlikte hangi sınıfların kalıcı olduğunun listesinin bir yerde tutulmasının faydaları düşünüldüğünde iyi bir uygulama olarak aklımızda kalmalıdır. Dosyada daha sonra özellikler (properties) bloğu gelir. Burada kullanılan JPA sağlayıcısının kabul ettiği özellikler listelenir. Yukarıdaki dosyada 8 tane Hibernate özelliği verilmiş durumdadır:

  • hibernate.dialect: Hibernate haberleşeceği veri tabanını bilmek ister.
  • hibernate.connection.driver_class: Bu özellik kullanılacak veri tabanı sürücüsünün classını ifade eder.
  • hibernate.connection.url, hibernate.connection.username ve hibernate.connection.password kullanılacak veri tabanı ve şemasına ulaşım bilgilerini içerir.
  • hibernate.hbm2ddl.auto: Bu özelliklik Hibernate’e veri tabanındaki şemayla alakalı nasıl bir tavır takınacağını öğretmek içindir. Yukarıdaki dosyada bu ayar “create” olduğu için Hibernate ilk EntityManager nesnesi oluşturulduğunda veri tabanına gidip varsayılan değerlerle bir şema oluşturacaktır. Şemanızı ilk defa oluşturduktan sonra bu özelliğin değerini “update”e değiştirebilirsiniz.
  • hibernate.show_sql ve hibernate.format_sql de Hibernate’in çalışma zamanında ürettiği SQL cümlelerini formatlı bir şekilde görmenizi sağlayacaktır.

package customer.p1;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class Test {
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("customer1");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
Customer1 c = new Customer1(1, "Ali", "Telli");
tx.begin();
em.persist(c);
tx.commit();
em.close();
}
}

Test kodunda, önce Persistence sınıfının üzerindeki bir static metotu çağırıp ona kalıcılık biriminin ismini geçerek EntityManagerFactory nesnesini oluşturduk. Persistence sınıfı JPA’in çalışma zamanı ortamını bir kalıcılık birimi için ayağa kaldırmak için yüklenmesi gerekli ilk (bootstrap) sınıftır. Bu sınıftan elde ettiğimiz EntityManagerFactory, tek nesneli (singleton) bir arayüz (interface) olup, üzerinde entitylerle ilgili YOGS (yani yaz, oku, güncelle ve sil (create, read, update, delete, CRUD)) metodlarını çağıracağımız EntityManager nesnelerinin fabrikasıdır (factory). EntityManagerFactory, oluşturulurken Persistence sınıfına geçilen kalıcılık birim isminden persistence.xml dosyasındaki bilgileri kullanarak veri tabanına ve ilgili entitylere ulaşan EntityManager nesnelerini oluşturur. EntityManager nesnesi ise üzerinde YODS işlemlerini yaptığımız, sorgular (query) ürettiğimiz ve kendisiyle en fazla haşır neşir olacağımız arayüzdür. EntityManager nesnesinin EntityManagerFactory nesnesinden alındığı noktadan close metotuyla kapatıldığı noktaya kadar olan alana persistent context ya da Türkçesiyle kalıcılık bağlamı denir. Kalıcılık bağlamı, temel olarak EntityManager tarafından oluşturulan ve içinde entitylerin hayat döngülerinin (life cycle) yönetildiği bir alandır öyle ki bu alanda her entityden sadece bir tane nesne bulunur. Yukarıdaki örnekte EntityManager üzerindeki persist metotunu çağırıp ona Customer1 nesnesini geçerek içinde sadece bir Customer1 nesnesinin olduğu bir işlemsel (transactional) kalıcılık bağlamı oluşturduk. Bu test sınıfını çalıştırdığınızda, Hibernate konsolunuza bir sürü bilgi basarak veri tabanınızda Customer1 tablosu oluşturacak ve 1 nolu “Ali Telli” nesnesini bu tabloya yazacaktır. Hibernate’in konsolunuza bastığı bilgiler arasında bu çalışmada kullandığı SQL ifadelerini de görebileceksiniz.

Yukarıda bahsedilen yapılar javax.persistence paketinde bulunup aralarındaki ilişkiler aşağıdaki çizimde tasvir edilmişlerdir.

JPA Objects

Tekrar Entity

Entity olan sınıfların veri tabanındaki tablolarla eşleştirilmesi, sınıfın tablo ile, kalıcı alanlarının da tablodaki sütunlar ile notların kullanılarak eşleştirilmesi anlamına gelmektedir. Entitynin varsayılan bir tablo yerine ismini vereceğemiz bir tablo ile eşleştirilmesi @Table, entity içindeki nesne değişkenlerinin de ismi verilen bir sütunla eşleştirilmesi de @Column notuyla olur. Dolayısıyla yukarıda en basit haliyle veri tabanına eşleştirdiğimiz Customer1 sınıfımızdaki entity tanımını, yeni bir pakette Customer2 olarak değiştirip aşağıdaki satırları eklediğimizde


@Entity@Table(name="CUSTOMERS")
public class Customer2{...}

customer.p2 paketindeki Customer2 sınıfının CUSTOMERS isimli bir tabloyla eşleştirilmesini sağlarız. @Table notunun 4 tane özelliği olmasına rağmen çoğu zaman yeterli olan sadece name özelliğini kullandığımıza dikkat edin.

@Column notunun ise 10 tane özelliği vardır. En çok kullanılan 4 özelliği, sütun ismi için name, uzunluk için length, sütundaki değerlerin tekil (unique) olup olmadığını göstermek için unique, ve yine bu sütuna null değeri geçilip geçilemeyeceğini ifade etmek için nullable’dır. Bu değerleri kullanarak örneğin firstName değişkenini notlandırırsak, aşağıdaki satırlara ulaşırız:

@Column(name="FIRSTNAME", length=50, unique=false, nullable=false)
private String firstName;

Bir entity sınıfındaki eşleştireceğiniz ilk alan daima o sınıftan oluşturulan nesnelerin ayırt edici özelliği olan ve @Id ile notlandırılan kimlik (identity) alanı olmalıdır. Bazen kimlik bilgisinin, veri tabanı gibi bir yapı tarafından ardışıl olarak üretilmesini isteriz. Bu durumda @GeneratedValue notu ile bu not içinde @GenerationType notunu kullanıp, JPA sağlayıcının ve veri tabanının özelliklerine göre mümkün olan kimlik bilgisi üretme stratejilerinden en uygun olanını seçebiliriz. @GeneratedValue notu isteğe bağlı iki özellik alır: String tipinde bir üretici ile GenerationType enum tipinde bir üretim tipi. GenerationType’ın, AUTO, IDENTITY, SEQUENCE ve TABLE olmak üzere 4 nesnesi vardır. Örneğin Customer2 sınıfının custId alanını, üretim şeklini Hibernate’e bırakarak aşağıdaki gibi notlandırabiliriz:

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "CUSTOMERID") private long custId;

Hibernate bu durumda Oracle XE’de HIBERNATE_SEQUENCE isimli bir dizi (sequence) oluşturup Customer2 sınıfının kimlik bilgisini buradan üretmektedir. Bu durumda Test sınıfımızda artık Customer2 nesnelerini oluştururken kimlik bilgisi geçmemeliyiz, aksi taktirde hata alırız.

EntityManager

EntityManager’in, oluşturduğu kalıcılık bağlamındaki nesnelerin hayat döngüsünü yönettiğinden bahsetmiştik. Şu ana kadarki örneklerimizde EntityManager’in sadece persist metotunu kullandık. EntityManager, veri tabanına açılan bir oturumdur (session), yapı itibariyla Java’nın web katmanındaki HttpSession nesnesine benzer ya da Hibernate’in orijinal API’sinin Session nesnesiyle tamamen aynı işi yapar. Dolayısıyla EntityManager’i olabildiğince kısa aralıklar içinde kullanmak, yani bu nesnenin ömrünü olabildiğince kısa tutmak gereklidir. Zaten EntityManager nesnesini kullanmanın iki yolu olabilir: Ya nesne değişkeni ya da metot değişkeni (local) olarak. Nesne değişkeni olarak kullandığınızda bu nesnenin pek çok metotta aynı anda kullanılması söz konusudur. Buradaki problem EntityManager’in thread-safe olmamasıdır. EntityManagerFactory thread-safedir, dolayısıyla bu nesneyi, nesne değişkeni olarak kullanıp, entitylerle alakalı YODS işlemlerini yapacağımız metotlarda, bu nesne üzerinden yerel bir EntityManager nesnesi almak daha doğru bir yaklaşım olacaktır. Bu durumda EntityTransaction nesnesini de kullanarak EntityManager nesnesi üzerindeki metotları çağırıp, sonra da close ile EntityManager nesnesini kapatarak programımızı yazarız.

Aşağıda bu sınıfın üzerinde çağırabileceğimiz nesne hayat döngüsünü düzenleyen metotlar ve kullanımları sıralanmıştır:

  • void persist(Object entity) Yeni bir entityyi veri tabanında kalıcı kılar ve nesneyi yönetilir (managed) hale getirilir.
  • Object find(Class entityClass, Object id) Geçilen entityClass’ın geçilen id’ye sahip nesnesini veri tabanından getirir.
  • Object merge(Object entity) Koparılmış (detached) haldeki nesneye yapılan değişiklikleri, veri tabanındaki entityle birleştirir. Bu metot geriye birleştirilmiş entityi döndürür.
  • void remove(Object entity) Entityyi veri tabanından siler.
  • void refresh(Object entity) Entitynin veri tabanındaki halini getirip kalıcılık bağlamındakinin üzerine yazar.
  • boolean contains(Object entity) Geçilen nesnenin kalıcılık ortamında entity olarak olup olmadığını boolean olarak geri döndürür.
  • void clear() Kalıcılık ortamını temizler ve ortamda bulunan nesneleri koparılmış (detached) hale getirir.
  • void flush() Kalıcılık ortamınındaki entitylere yapılan değişiklikleri veri tabanına gönderir (synchronize).

Bu metotların kullanımını gösteren metotlar, projedeki customer.p2.Test.java sınıfındadır.

EntityManager tarafından persist ile kalıcı kılınan ya da find ile veri tabanından getirilen entitylerin durumu (state) yine EntityManager tarafından takip edilir. Böyle bir entityye “yönetilen” (managed) denir. Yönetilen nesnelere yapılan değişiklikler veri tabanına EntityManager tarafından yansıtıldığı için ayrıca güncelleme gibi bir metot çağırmaya gerek yoktur.

İşlem (Transaction) Yönetimi

Yukarıda EntityManager’in metotlarını incelemeden de anlaşılacağı gibi entityler işlem içerisinde kalıcı hale getirilirler (persist), güncellenirler (merge) ve silinirler (remove). Ayrıca yönetilen entitylere yapılan güncellemeler, EntityManager nesnesi tarafından takip edilir ve ne zaman bu nesne üzerinden ulaşılan EntityTransaction nesnesi begin ile başlatılıp commit ile bitirilirse yukarıdaki üç metotun sonucu ve kalıcılık bağlamındaki tüm güncellemeler veri tabanına yansıtılır.
EnetityManager nesnesi üzerindeki metotları çağırırken ya da başka bir sebeple ortaya çıkan durumlardan dolayı entityler üzerindeki değişiklikleri EntityTransaction üzerinde commit’i çağırarak veri tabanına göndermek yerine rollback’i çağırarak ile geri almak isteyebilirsiniz. rollback en son begin’den sonraki entitylere yapılan değişiklikleri geli alır, veri tabanına yansıtmaz.

Entity Hayat Döngüsü (Life Cycle)

Bir entity şu dört durumdan birinde olabilir: yeni (new), yönetilen (managed), silinmiş (removed) ve koparılmış (detached). (Bkz. lifeCycle())
Bir entity ilk yaratıldığında yeni (new) durumundadır. Bu durumda olan entitynin veri tabanıyla bir ilgisi yoktur, yani henüz veri tabanına yazılmamıştır. EntityManager’in persist metotuyla veri tabanında kalıcı hale gelir ve durumu yönetilen (managed) olur. (Bkz. createACustomer()) Yönetilen entity var olan kalıcılık ortamında bulunur ve durumu EntityManager tarafından takip edilir. Bu nesneye yapılan değişiklikler EntityTransaction nesnesine yapılan ilk commit çağrısında veri tabanına yansılıtır ve nesne yönetilen olarak kalmaya devam eder. (Bkz. createAndManageCustomer())

Veri tabanındaki bir entity EntityManager‘in find metotu ya da daha ileride göreceğimiz Query nesneleriyle veri tabanında getirildiğinde yine yönetilen durumuna girer. Bu durumdayken EntityManager’in remove metotuyla silinmiş hale gelir. Tekrardan veri tabanına yazılması için persist metotuna geçilmesi gereklidir. (Bkz. removeACustomer())

EntityManager üzerinde çağırılacak close ya da clear metotlarıyla da kalıcılık bağlamındaki bütün nesneler koparılmış hale gelirler ve durumlarındaki değişiklikler artık EntityManager tarafından takip edilmezler. Bu tür entitylerin durumlarını veri tabanında güncellemek ve tekrar yönetilen hale döndürmek için merge metotunu kullanırız. merge metotu, uzun işlem (long transaction) denen ve entitynin genelde içinde bulunduğu katmandan başka katmanlara gidip, orada değiştirildikten sonra değişikliklerin veri tabanına yansıtılması için geri gönderildiğinde kullanılır. Örneğin veri tabanında getirdiğiniz entity bir JSP sayfasında kullanıcıya görüntülenecek ve kullanıcının bazı alanları değiştirip kaydetmesine izin verilecekse, bunu yapmanın iki yolu olabilir: Ya EntityManager nesnesini JSP içinde alıp üzerindeki metotları kullanarak entitynizi değiştireceksiniz ki bu JSP içinde veri katmanıyla ilgili iş yapmak anlamına gelecektir ve popüler olan MCV (Model-View-Controller) tasarım desenine (design pattern) aykırı bir durum oluşturacaktır. Ya da entitynizi veri tabanında değiştirecek EntityManager nesnesini arka tarafta yani veri katmanında tutacaksınız ve entitynizi JSP sayfasına değiştirilmek üzere göndereceksiniz ve bunu yaptığınız yerde de EntityManager nesnesi üzerinde close metotunu çağıracaksınız. Bu durumda entityniz koparılmış olacaktır. Dolayısıyla JSP’de değiştirilen entity, veri katmanına tekrar gönderildiğinde, yeni bir EntityManager nesnesi oluşturup bir işlem içerisinde bu kez merge metotunu çağırarak güncelleyeceksiniz. Bu ikinci yöntem çok daha sağlıklı olup uzun işlem olarak adlandırılır. (Bkz. detachACustomer())

Bundan Sonrası

Buraya kadar, JPA kullanarak en basit sınıf eşleştirmesinin nasıl yapılacağını gördük. En basit, çünkü bu sınıfta tanımlanan değişkenler ya basit (primitive) değişkenler ya da veri tabanına otomatik olarak eşleştirilen String gibi nesneler. Henüz birden fazla nesne tanımlayıp, örneğin Order, Item gibi, aralarındaki daha karmaşık, örneğin M*N gibi ilişkileri kullanmadık. Önümüzdeki yazılarda bu türden daha karmaşık eşleştirmeler yanında, sorgu (query) nesnesini, Java EE ortamında JPA kullanımını ve diğer konuları ele alacağız.

Toplam görüntülenme sayısı: 8920