1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 1 1. Bölüm Java’da Nesne Kalıcılığının Temelleri 1. Bölüm Java’da Nesne Kalıcılığının Temelleri ............................................................... 1 1.1. Giriş ............................................................................................................................. 2 1.2. Nesne Kalıcılığı Nedir? ................................................................................................. 3 1.3. Nesne-­‐İlişkisel Uyumsuzluğu ........................................................................................ 5 1.4. Kalıcı Nesneler ve İş Nesneleri ..................................................................................... 7 1.5. Veri Erişimi .................................................................................................................. 8 1.5.1. Veri Erişim Nesnesi (VEN) Tasarım Şablonu ............................................................... 11 1.5.2. JDBC ya da Java Database Connectivity ..................................................................... 13 1.5.3. Nesne Veri Tabanları .................................................................................................. 19 2.5.3.a. Db4O ....................................................................................................................... 20 1.6. Çözüm: Nesne-­‐İlişkisel Eşleştirme ............................................................................... 22 1.7. NİE Araçlarının Kısa Tarihi ........................................................................................... 24 1.8. JPA ............................................................................................................................. 26 1.8.1. JPA 2.0 ........................................................................................................................ 27 1.8.2. JPA 2.1 ........................................................................................................................ 27 1.9. Bölüm Özeti ................................................................................................................ 30 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 2 1.1. Giriş Veri, Bilgi Teknolojileri dünyasının en önemli kavramıdır. Verinin toplanması, iletilmesi, işlenip bilgi haline getirilmesi ve raporlanmasının olmazsa olmaz bir ihtiyaç haline geldiği günümüzde veriyi saklama yani depolama oldukça önem kazanmıştır. Hemen her türlü yazılım sisteminin hayatımıza girmesi, webin teknolojiden çok sosyal bir olgu olarak dünyamızda yer alması, sabit ve hareketli görüntü, ses gibi farklı tipte verilerin yaygınlık kazanması, büyük veri (big data) isminde yeni bir teknolojik problem alanı ortaya çıkardı. Sonucunda bilgiyi toplayan işleyen, raporlayan ve saklayan (depolayabilen) uygulamalar geliştirmek, yazılım geliştirmenin (software development) en önemli hedeflerinden biri haline geldi. Elektronik ortama aktarılan veriyi işleyen uygulamalar geliştirirken karar verilmesi gereken ilk ve en önemli konu, verinin uygulamanın yazıldığı programlama ortamında ne şekilde ifade edileceğidir. Bu noktada farklı zamanlarda farklı kararlar alındı. Veriler ilk başlarda, bilimsel karakterdeydi ve sadece tam ve ondalık sayı ile ifade edilirken sonrasında ihtiyaçlar arttıkça karakter, metin (String), doğru-­‐yanlış değerli gibi daha farklı veri yapılarıyla ifade edilmeye başlandı. Benzer şekilde karmaşıklaşan ihtiyaçlar sonrasında soyut veri tiplerini (abstract data types) ortaya çıkardı. Uygulamaların gittikçe günlük hayatımızda devamlı karşılaştığımız iş modellerini ve aldığımız hizmetleri otomatikleştirmesi, veriyi teknik bir konu olmaktan çıkardı. Böylece veri, tekil ve atomik olmaktan çıktı, sarmalanmış (encapsulated) bir yapıda daha soyut hale geldi. Bu şekilde soyut veri yapıları (abstract data structures) ortaya çıktı. Verinin daha soyut bir yapıya sahip olurken yani daha farklı bir ifadeyle “data” seviyesinden “information” seviyesine çıkarken, iş süreçleri (business processes) ve onları yönlendiren iş kuralları (business rules) da karmaşıklaştı. Bu durum, veri soyutlamasını bir üst seviyeye daha çıkardı ve nesneler gündeme geldi. Yani veriler, kendisini kullanan süreçler ve kurallar ile birlikte soyutlanmaya başladı. Daha ilkel bir anlayışla, davranışa sahip olan soyut veri yapısı olarak da görülebilecek olan nesneleri kullanan nesne-­‐tabanlı (object-­‐based) diller hızlıca nesne-­‐merkezli (object-­‐ 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 3 oriented) dillere dönüştü. Nesne-­‐merkezli diller temelde bize iki şey kazandırdı: İlki, gerçek dünyadaki kavramları ve aralarındaki ilişkileri doğru ve daha anlaşılır bir şekilde ifade etmek için farklı nesne desenleri (patterns) kurgulamamıza imkan sağladı. Böylece karmaşıklığı (complexity) yönetebilme imkanı kazandık. İkinci olarak da nesne-­‐merkezli diller yazılımın doğasında var olan değişimi yönetmemize yardımcı oldu. Bu amaçla bu diller, nesnelerin davranışsallığını dinamik olarak ifade etmeye yarayan dinamic-­‐binding gibi mekanizmalarla, çok şekilli (polymorphic) referanslar oluşturabilmemize imkan verdi. Bu sayede nesneler arasındaki ilişkileri daha soyut olarak ifade edebilir hale geldik. 1.2. Nesne Kalıcılığı Nedir? Yukarıda bahsedilen gelişmeler sonuçta nesne-­‐merkezli dillerle temel soyutlama seviyemizi nesne seviyesine çıkarmış oldu. Dolayısıyla herhangi bir sistemde depolanmış olan veriler, nesne-­‐merkezli (object-­‐oriented) diller ile geliştirilen uygulamalarda nesnelerle ifade edilir hale geldi. Dolayısıyla bu tür (nesne-­‐merkezli) uygulamalarda bir bilgiyi kalıcı hale getirebilmek demek o bilgiye karşılık gelen nesnenin durumunun (state) kalıcı hale getirilebilmesi demektir. Bir nesnenin durumu, o nesneye ait değişkenler (instance/object variables) ve bu değişkenlerin aldıkları değerler tarafından oluşturulur. Dolayısıyla, nesne-­‐merkezli bir dil ile geliştirilen bir uygulamanın kalıcı bilgi üzerinde işlem yapabilmesi ve yeni gelen bilgiyi kalıcı hale getirebilmesi için uygulamada oluşturulan sınıfların çalışma zamanında (run-­‐time) yaratılan nesnelerinin durumlarının, nesneler bellekten silinse bile kalıcı ve ulaşılabilir halde olması gereklidir. Nesne durumunun nesne bellekten temizlendikten sonra bile kalıcı dolayısıyla da ulaşılabilir hale getirilmesine “nesne kalıcılığı” (object persistence) denir. Nesne kalıcılığından bahsedildiğinde ilk akla gelen, nesnelerin durumlarının İlişkisel Veri Tabanı Yönetim Sistemi (İVTYS) (Relational Database Management System, RDBMS) adı verilen yapıda saklanmasıdır. Nesnelerin ilişkisel veri tabanının tablolarında kalıcı hale getirilmeleri için öncelikle, uygulamadaki sınıfların tablolara, sınıflarda tanımlanan ve kalıcı olan nesne özelliklerinin yani nesne değişkenlerinin de bu tablolardaki sütunlara (ya da kolonlara) karşılık getirilmesi ya da bir başka deyişle bu tablolardaki sütunlarla/kolonlarla eşleştirilmesi gerekmektedir. Bu şekilde, bir 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 4 sınıfın veri tabanındaki bir ya da daha fazla tabloyla, bu sınıf üzerinde tanımlı ve kalıcı olması istenen nesne özelliklerinin ya da değişkenlerinin de tabloların belirli sütunlarıyla eşleştirilmesi işlemine “nesne-­‐ilişkisel eşleştirme” (NİE) (Object-­‐Relational Mapping, ORM) denir. Böylece uygulamadaki sınıflardan çalışma zamanında oluşturulan nesneler, İVTYS (ya da bundan sonra sadece “veri tabanı”) bağlı oldukları sınıflarla eşleştirilmiş olan tabloların satırlarına yazılarak kalıcı hale getirilirler. Uygulamalar aynı zamanda veri tabanındaki tablolarda var olan satırların sorgulanması (querying), güncellenmesi ve silinmesi gibi farklı işlemeleri yapmaya da ihtiyaç duyarlar. Uygulamada üretilen nesnelerin durumlarının veri tabanına kaydedilmesi (create), nesnelerin durumlarının son hallerinin veri tabanından uygulamaya geri getirilmesi (read), nesnelerin durumlarına uygulamada yapılan değişikliklerin veri tabanına yansıtılması (update) ve nesnelerin gerekirse veri tabanından silinmesi (delete), nesnelerle ilgili 4 temel nesne-­‐veri tabanı işlemidir ve kısaca YOGS (yaratma, okuma, güncelleme ve silme (CRUD, create, read, update, delete) olarak adlandırılır. Programlama dillerinde bütün bu işlemleri yapmak için örneğin C’deki ODBC (Open Database Connectivity) ya da Java’daki JDBC gibi veri tabanı erişim bileşenleri vardır. Bu yapılar veri tabanlarına kendilerine has protokolleri kullanan sürücüler (driver) üzerinden erişerek programcılara veri tabanı üzerinde yukarıda bahsedilen türden işlemleri yapabilme imkanı verirler. SQL (Structured Query Language), veri tabanlarının ortak dilidir. Veri tabanı programcıları SQL’i, veri tabanındaki tablolar ile bu tabloların satırlarında bulunan ve nesne-­‐merkezli bir dille geliştirilen bir uygulamadaki nesnelerin değişkenlerini oluşturan verileri işlemede kullanırlar. Uygulama programcıları da programlama dillerindeki veri tabanı erişimi sağlayan yapılar sayesinde SQL sorgularını kullanarak, bu dil yardımıyla YOGS ile ifade edilen veri tabanı işlemlerini gerçekleştirirler. (Tablolar ve tablolardaki satırlar ile ilgili işlemler genel olarak SQL’in veri tanımlama (Data Definition Language, DDL) ve veri işleme (Data Manipulation Language) özellikleri kullanılarak yapılmaktadır.) Bu durum, veri tabanındaki verinin nesne olarak ifade edildiği nesne-­‐merkezli dillerle geliştirilen uygulamalarda veri tabanında kalıcı kılınacak her nesne için önce nesne-­‐ilişkisel eşleştirmenin yapılmasını ve sonra da kalıcı nesneler üzerinde yapılan işlemleri veri tabanına yansıtabilmek için YOGS sorgularının yazılmasını gerektirir. 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 1.3. 5 Nesne-­‐İlişkisel Uyumsuzluğu Nesne-­‐merkezli diller ile geliştirme yapanlar tabii olarak düşünce sistemlerini nesne kavramı etrafında örerler. Nesne kültürü bize yazılım projelerinin her safhasında, farklı soyutlama seviyelerinde ya da farklı açılardan nesneler hakkında düşünme imkanı sunar. Geliştirilecek olan yazılımın ihtiyaçlarından yola çıkarak, nesnelerin sorumlulukları ve özellikleri, nesneler arasındaki ilişkiler, çalışma zamanında nesnelerin birbirleriyle nasıl haberleşecekleri vb. düşünceler zihnimizde uçuşur dururlar. Hatta nesneler hakkında bu şekilde düşünebilmeyi ayrıcalık olarak bile görürüz. Nesneler hakkındaki düşünce faaliyetimiz tasarımda daha soyut bir seviyede iken, kodlamada hayata geçer ve bu sayede nesneleri oluşturur, birbirleriyle ilişkilendirir ve yazılım sistemimizi çalışır hale getiririz. Buraya kadar her şey güzel de “nesnelerimizi nerede saklayacağız?” sorusu gündeme gelince işin tadı biraz kaçar. Şöyle ki: Yazılımın çalışması sırasında üretilen nesnelerin taşıdıkları bilgilerin ki yukarıda tanımladığımız gibi nesnenin durumu olarak adlandırılır, daha sonra kullanılmak üzere kalıcı hale getirilmesinin en tabii yöntemi, olsa olsa bir nesne veri tabanı yönetim sistemi (NVTYS) (Object-­‐Oriented Database Management System, OODBMS) kullanmaktır. (Literatürde, “object-­‐oriented database” yerine “object database” de kullanılmaktadır. Biz ikincisini tercih ettik.) Fakat gerçekte durum biraz farklıdır. Yazılımın ilk günlerinden bu yana yazılımcılar, yapısal verilerini kalıcı kılmak için farklı çözümler geliştirmişler ama burada bahsedilmesi gerekmeyen çok değişik nedenlerden dolayı bu çözümlerden sadece İlişkisel Veri Tabanı Yönetim Sistemi (İVTYS) (Relational Database Management System, RDBMS) olarak adlandırılan ürünler günümüzde en yaygın kullanılan olmayı başarmışlardır. Kısa adıyla ilişkisel veri tabanları (ya da sadece veri tabanları), ilk ortaya çıktığı, 1970’li yıllardan bu yana kurumsal yazılım dünyasında çok iyi bir yer edinmiş ve etraflarına ördükleri İş Zekası (Business Intelligence, BI) vb. sistemlerle, Bilgi Teknolojilerinin (BT) en çekirdek uygulamalarından biri haline gelmişlerdir. Dolayısıyla, herhangi bir türden yapısal veri ya da nesne-­‐merkezli yazılım sistemlerinin nesnelerinin durumları anlamında veri için, kalıcılık yani verinin saklanması amacına yönelik olarak pratikte sahip olduğumuz tek seçenek ilişkisel veri tabanlarıdır. Fakat gerçek şudur ki, ilişkisel veri tabanları nesnelerin kalıcı olması için tasarlanmamışlardır. İlişkisel veri tabanları, nerede üretilmiş olursa olsun, veriyi, aralarındaki ilişkilerden yola çıkarak 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 6 oluşturduğu Entity-­‐Relationship ya da E-­‐R isimli bir modele göre yapılandırmakta ve veriyi bu şekilde varlıkların arasındaki ilişkileri kullanarak ve olabildiğince az tekrar yaparak saklamayı hedeflemektedir. Bu yöntemle, yüksek miktardaki verinin sağlıklı ve verimli bir şekilde saklanması ve özellikle de sorgular yoluyla tekrar sunulması kolaylaşmış olmaktadır. Yukarıda bahsedilen açılardan başarılı olan ilişkisel veri tabanlarının nesneler açısından en temel problemi, nesnelerin durumlarını saklamak için geliştirilmemiş olmalarıdır. Bu durum, tarihi olarak son derece normal olmakla birlikte, gittikçe daha nesne-­‐merkezli hale gelen yazılım dilleri ve yazılım geliştirme yöntem ve teknikleri açısından bir problemdir. Çünkü, nesne-­‐ merkezli diller ile ilişkisel veri tabanları arasında pek çok yönden uyumsuzluklara yol açan farklılıklar vardır. Nesneler ve aralarındaki ilişkilerin doğası ile veri tabanlarındaki tablolar ve aralarındaki ilişkilerin doğası tamamen farklıdır. Nesne-­‐merkezli programlama paradigması ile ilişkisel veri tabanı paradigması arasında, tip farklılıklarından güvenlik yapılarına kadar pek çok konuda ciddi farklılıklar hatta karşıtlıklar söz konusudur. (Bütün bu tartışmada her nesne-­‐ merkezli programcının yaptığı gibi biz de “nesne” ile “sınıf” kavramını birbirlerinin yerine geçer şekilde kullandığımızı belirtmeliyiz.) Örneğin, nesneler sarmalanmış (encapsulated) yapılardır, yani hem veriye hem de davranışa sahiplerdir, veri tabanlarındaki tablolar sadece veri ile ilgilenir. Ya da nesneler birbirleri hakkında bellekteki referansları yoluyla haberdar olurlarken, tabloların birbirlerini bilmeleri, anahtar sütunun (primary key), ilişkilendirilecek tablolarda tekrarlanması yoluyla olur. Nesnelerin, miras (inheritance) yoluyla birbirlerinden özellik devralabilmesi sayesinde nesne hiyerarşileri çok yaygınken, tablolar arasında hiyerarşi kurgulanması söz konusu olmayan bir durumdur. Tablolardaki sütunların tuttuğu verinin tipi veri tabanı açısından genel olarak teknik bir konu iken, nesne-­‐merkezli uygulama açısından tip kavramı, teknik bir konunun çok üzerinde bir anlama sahiptir. Miras hiyerarşisi sayesinde nesneler ile çok şekilli (polymorphic) referansları arasında, yukarı ya da aşağıya dönüştürme (up-­‐cast ya da down-­‐cast) gibi ilişkiler tanımlayabilmek, nesnelerin ve referansların tiplerin aralarında dönüşebilir olmasıyla ilgilidir. Uzun uzadıya sayılabilecek bu farklılıklar sadece teknik birer uyumsuzluk değildir; bir yaklaşım hatta paradigma farklılığıdır: veri paradigmasına karşın nesne paradigması. Dolayısıyla nesne-­‐merkezli diller nesnelerinin durumlarını ilişkisel veri tabanında saklamak istediklerinde, 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 7 iki farklı paradigma karşı karşıya gelmektedir. Bu iki paradigma ya tamamen farklı ilgi alanlarına sahiptir dolayısıyla da birinde olan bir durum diğerinde yoktur bile ya da aynı konuya farklı açılardan yaklaşıp farklı şeyleri problem olarak görmüşler, dolayısıyla da farklı çözümler üretmişlerdir. Nesneler ile ilişkisel tablolar arasında bu türden farklılıkların oluşturduğu duruma “nesne-­‐ilişkisel uyumsuzluğu” (object-­‐relational mismatch) denir. (Hatta tarihi olarak bu uyumsuzluk önceleri Elektrik mühendisliğinden alıntıyla, iki devrenin farklı empedansa sahip olmasının getirdiği bir güç aktarımı problemi olan “empedans uyumsuzluğu” (imdepance mismatch) olarak ifade edilirdi.) Nesne merkezli dillerin kullanıldığı her ortamda gündeme gelen nesne-­‐ilişkisel uyumsuzluğunu aşmanın farklı yolları tabi olarak keşfedildi. Dil içinde SQL (embedded-­‐SQL) kullanımı (örneğin SQLJ böyle bir girişimdi), Java’nın JDBC bileşeni ve farklı dil ve platformlardaki alternatifi olan ODBC ya da ADO.NET gibi yapılar, bu uyumsuzlukların bir kısmını saklayan veri tabanı iletişim ara yüzlerindendir. Aşağıda örneğini verdiğimizde daha iyi algılayacağımız gibi, bu türden bileşenler, ancak nesnelerin bütüncül yapılarını bozarak ve veri tabanları arasındaki farklılıkların getirdiği yükü geliştiricilere yükleyerek veri tabanı iletişimini kurabilmektedirler. Nesne veri tabanlarının çok da ciddi bir alternatif olamadığı ortamlarda, bu uyumsuzluğu aşmak amacıyla pek çok nesne-­‐ilişkisel eşleştirme (object-­‐relational mapping, ORM) çerçevesi (ya da çatısı (framework)) geliştirildi. Bu kitap, bu girişimin en sonuncusu olan Java’nın nesne-­‐ilişkisel eşleştirme ara yüzü Java Persistence API ya da kısaca JPA hakkındadır. Bu bölümde, JPA’nın detaylarına girmeden önce, bazı kavram ve yaklaşımları ele alacağız. 1.4. Kalıcı Nesneler ve İş Nesneleri Hangi teknoloji kullanılırsa kullanılsın ya da erişilecek veri tabanı ister ilişkisel olsun ister nesne-­‐merkezli olsun, veri tabanındaki verilerin uygulama katmanındaki nesnelerin durumlarına karşılık geldiğini yukarıda ifade ettik. Bu gerçeği bir başka deyişle “nesnelerin durumları, veri tabanında saklanmaktadır” şeklinde de ifade edebiliriz. Durumları veri tabanında saklanan nesnelere kalıcı nesneler (persistent objects) denir. İngilizce literatürde kalıcı olan nesneler için genel olarak entity terimi kullanılmaktadır. Biz bu kitapta kalıcı nesne kavramını “entity”nin 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 8 karşılığı olarak kullanacağız zira entity kelimesini dilimize “varlık” diye çevirmek konunun ruhuna pek de uyan bir durum olarak gözükmemektedir. Nesneleri kalıcı olan sınıflara da kalıcı sınıflar (persistent classes) deriz. Uygulamalardaki kalıcı olan nesnelerin, büyük oranda uygulamanın iş alanını (business domain) temsil eden kavramlar oldukları açıktır. Örneğin bir müşteri ilişkileri yönetimi (Customer Relationship Management, CRM) uygulamasından bahsediyorsak, Customer, Address, Email, Message, Sale, Opportunity, Offer vb. kavramların, bu iş alanının en temel nesnelerinden olacağını ve bu nesnelerin durumlarının veri tabanında kalıcı hale getirilmesi gerektiğini söyleyebiliriz. Bu türden olan nesnelere is nesneleri (business objects) ya da alan nesneleri (domain objects) denir. Biz de bu kitap boyunca farklı iş ya da alan nesnelerini kullanacağız. 1.5. Veri Erişimi Kalıcı nesnelerin durumlarının veri tabanındaki veriler olarak saklanması, yukarıda bahsettiğimiz YOGS işlemleri ile olmaktadır. YOGS işlemleri ise SQL ifadelerini kullanmaktadır. Örneğin veri tabanında dört sütunlu şöyle bir tablomuzun olduğunu varsayalım: PERSONS (ID, FIRSTNAME, LASTNAME, DATEOFBIRTH) Bu tablonun, uygulamada var olan kişilere karşılık geldiği ve kişilerin de kimlik numarası, isim, soy isim ve doğum tarihi bilgileri ile betimlendiği açıktır. Bu tabloyu veri tabanında oluşturmak için şöyle bir CREATE TABLE ifadesine ihtiyacımız olacaktır: CREATE TABLE PERSONS ( ID INT PRIMARY KEY, FIRSTNAME VARCHAR(30) NOT NULL, LASTNAME VARCHAR(50) NOT NULL, 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 9 DATEOFBIRTH DATE ) Kod: DB.sql PERSONS isimli tabloyu oluşturan yukarıdaki SQL ifadesinin Apache’nin Derby (http://db.apache.org/derby/) isimli açık kaynak kodlu ilişkisel veri tabanı için yazılıp çalıştırıldığını, aynı ifadenin diğer veri tabanlarında bazen hiç bir değişiklik yapmadan bazen de ufak tefek değişikliklerle çalışacağını söylemeliyiz. Apache Derby tamamen Java’da geliştirilmiş ve 2.6 MB gibi son derece az bir bellek ile çalışabilen bir ilişkisel veri tabanıdır. Dahası Apache Derby, Java DB (http://www.oracle.com/technetwork/java/javadb/overview/index.html) adıyla Oracle tarafından JDK 7’den itibaren JDK ile paketlenmiştir. Dolayısıyla JDK 7’ye sahip olan okuyucular JDK kurulumunun altındaki “db” klasöründen Java DB’ye ulaşabilirler. Bu konuda kitabın uygulamalarındaki açıklamaları dikkatle okumanız gerekmektedir. Bu tabloya veri girmek istediğimizde de şöyle bir INSERT ifadesine ihtiyacımız olacaktır: INSERT INTO PERSONS VALUES (1, 'Mihrimah', 'Kaldiroglu', {d '2004-08-24'}) Kod: DB.sql Benzer şekilde bu tablodan veri alabilmek, örneğin var olan tek satırı getirebilmek için şöyle bir SELECT ifadesine ihtiyacımız olacaktır: SELECT * FROM PERSONS Bu ifadeyi çalıştırdığımız zaman tabloda var olan tek satırı getirmiş olacağız: 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 10 Çizim 0.1 Peki bu türden SQL ifadelerini Java’da nasıl kullanabiliriz? İsterseniz önce metotlardan başlayalım. Örneğin yeni bir nesneyi veri tabanında kalıcı hale getirmek istediğimizde yani işleme veri tabanı açısından bakıp da veri tabanına yeni bir satır eklemek istediğimizde, Java’da muhtemelen aşağıdaki gibi bir ara yüzü olan metoda sahip olmamız beklenmelidir: public void savePerson(Person person) Veri tabanındaki PERSONS tablosundaki verilerden belirli bir ID bilgisine sahip olanı getirmek istediğimizde de benzer şekilde aşağıdaki gibi bir ara yüzü olan metoda sahip olmamız beklenmelidir: public Person retrievePerson(int id) Sizin de tahmin edeceğiniz gibi bu metotların derlenmesi için Person isimli bir sınıfa ihtiyacımız vardır. Dolayısıyla Person sınıfının, PERSONS tablosunun yapısına uygun olarak şöyle olmasını bekleyebiliriz: public class Person { private int id; private String firstName; private String lastName; 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 11 private Date dob; public Person(){} public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public Person(int id, String firstName, String lastName, Date dob) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.dob = dob; } . . . . . . } Kod: Person.java Tabii olarak yukarıdaki Person sınıfı uygun bir pakette oluşturulmuştur ve uygun set()/get(), equals(), hashCode() ve toString() metotlarına da sahiptir. 1.5.1. Veri Erişim Nesnesi (VEN) Tasarım Şablonu Burada, uygulamadaki Person sınıfının nesnelerinin durumlarını yönetmek amacıyla kullanacağımız örnek SQL ifadelerini ve bu ifadelerin içinde kullanılacağı örnek metot ara yüzlerini oluşturduk. Şimdi şöyle bir soru ile karşı karşıyayız: Bu metotlar nerede olmalıdır? Yani, yeni bir Person nesnesini veri tabanına kaydetmek istediğimizde çağıracağımız savePerson() metodu ya da elimizdeki bir ID değerine sahip Person nesnesinin durumunu veri tabanından uygulamaya yükleyecek olan retrievePerson() metodu hangi sınıfın parçası olmalıdır? 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 12 Yukarıdaki sorulara “Person sınıfı” diye cevap verdiğimizde, bir alan nesnesi olan Person sınıfının veri tabanı erişim teknolojisine bağımlı olması ve aynı sınıfın yukarıdaki türden pek çok SQL ifadesine sahip olması gibisinden olumsuz pek çok durumla karşı karşıya kalırız. Bir Person nesnesini uygulamadaki katmanlar arasında gezdirmek istediğimizde gereksiz pek çok SQL ve veri tabanı erişim kodunu da taşımamız gerekecektir. Bu olumsuzluklar temelde işlerin/görevlerin ayrıştırılması (separation of concerns) dediğimiz en temel tasarım prensibine aykırı bir durum takınmamızdan kaynaklanmaktadır. Dolayısıyla, Person nesnelerinin durumlarını veri tabanı üzerinde yönetecek metotların yeri Person sınıfı değildir. Bu durumda bu tipten metotların, görevi tamamen veri tabanı etkileşimi olan ayrı bir sınıfla soyutlanması en doğru çözüm olacaktır. Yani nesneleri kalıcı olan her bir kalıcı sınıf için İngilizce literatürde DAO (Data Access Object) olarak da adlandırılan Veri Erişim Nesnesi ya da kısaca VEN tasarım şablonunu uygulayabiliriz. Bu şablona göre, yukarıda verilen türden veri tabanı iletişimi yapan metotlar bir ara yüzün içinde tanımlanır ve farklı kalıcılık teknolojilerini kullanan sınıflar tarafından gerçekleştirilirler. Bu kitapta da, bir alan nesnesinin veri tabanıyla olan iletişimini yürüten sınıfları oluşturmak amacıyla VEN tasarım şablonu kullanılacaktır. (Kalıcılık mimarileri üzerine ileriki bölümlerde detaylı bir şekilde duracağız.) VEN şablonunu Person sınıfına uyguladığımızda, elde edeceğimiz PersonDAOI ismini verdiğimiz ara yüz şöyle olacaktır: public interface PersonDAOI { public void savePerson(Person person); public Person retrievePerson(int id); public List<Person> retrieveAllPersons(); public void updatePersonDOB(Person person); public void deletePerson(Person person); public void deleteAllPersons(); } Kod: PersonDAOI.java 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 13 İşte Java’da interface anahtar kelimesiyle tanımlanmış PersonDAOI arayüzü Java’daki herhangi bir kalıcılık teknolojisi tarafından gerçekleştirilecek olan bir anlaşmayı (contract) ya da el sıkışmayı (hand shake) ifade etmektedir. Bu şekilde farklı kalıcılık teknolojilerini kullanan farklı PersonDAOI gerçekleştirmeleri arasında seçim yapmak, arayüz kullanımından dolayı çok daha kolay hale gelmiş durumdadır. Bu bölümün ilerleyen kısımlarında, JDBC, nesne veri tabanı gibi teknolojileri kullanarak, PersonDAOI ara yüzünün farklı gerçekleştirmelerini yapacağız. Ayrıca verdiğimiz kod örneklerinde isimlendirme konusunda belli şablonlara uymaya özen gösterdiğimiz, buraya kadarki kodlardan da anlaşılabilir. Örneğin, ara yüz isimlerini “I” ile bitirirken VEN şablonunu uygulayan ara yüzün ismini PersonDAOI yaparak, ilgili kalıcı sınıfla da ilişkilendirdik. 1.5.2. JDBC ya da Java Database Connectivity Java Database Connectivity ya da kısa adıyla JDBC, Java Standart Sürümü’nün (Java Standard Edition, Java SE) en başından bu yana bir parçası olan ve veri tabanlarıyla iletişimi sağlayan ara yüzünün adıdır. (Bileşen (component) anlamında arayüz terimini kullandığımızda, aslında Application Programming Interface ya da kısaca API’yi kastettiğimiz açıktır diye düşünüyoruz.) Java programcıları, JDBC’yi ilgili veri tabanının sürücüsünü kullanarak, veri tabanına bağlanmak, veri tabanı üzerinde YOGS işlemlerini yapmak dolayısıyla da uygulamada oluşturulan nesnelerin durumlarını yönetmek için kullanırlar. JDBC’nin temel yapıları java.sql paketi içinde çoğunlukla arayüzler olarak bulunur. Bu arayüzlerin gerçekleştirmeleri veri tabanı sürücüleri tarafından yapılır. Örneğin Connection, bir veri tabanı bağlantısını, Statement, veri tabanına gönderilecek olan bir SQL cümlesini ifade ederler ve ikisi de arayüz olarak tanımlanmışlardır. Veri tabanından uygulamaya veri getirmek ise ResultSet isimli arayüzün görevidir. Uygulamamızda Person sınıfının VEN şablonu kullanılarak oluşturulmuş PersonDAOI ara yüzünü JDBC kullanılarak PersonJdbcDAO isimli bir sınıfla gerçekleştirelim: 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com public class PersonJdbcDAO implements PersonDAOI { private static final String SAVE_PERSON_QUERY = "INSERT INTO PERSONS VALUES(?,?,?,?)"; private static final String RETRIEVE_PERSON_QUERY = "SELECT * FROM PERSONS WHERE ID = ?"; private static final String RETRIEVE_ALL_PERSONS_QUERY = "SELECT * FROM PERSONS"; private static final String UPDATE_PERSONDOB_QUERY = "UPDATE PERSONS SET DATEOFBIRTH = ? WHERE ID = ?"; private static final String DELETE_PERSON_QUERY = "DELETE FROM PERSONS WHERE ID = ?"; private static final String DELETE_ALL_PERSONS_QUERY = "DELETE FROM PERSONS"; @Override public void savePerson(Person person){ System.out.println("Saving person:" + person); Connection conn = getConnection(); PreparedStatement stmt = null; try { stmt = conn.prepareStatement(SAVE_PERSON_QUERY); stmt.setInt(1, person.getId()); stmt.setString(2, person.getFirstName()); stmt.setString(3, person.getLastName()); stmt.setDate(4, person.getDobAsSQlDate()); int updateCount = stmt.executeUpdate(); if(updateCount != 1) System.out.println("Problem with saving the person."); else System.out.println("Person saved!"); } catch (SQLException e) { System.out.println("Problem with statement: " + e.getMessage()); e.printStackTrace(); } finally{ returnConnection(conn); 19 Ekim 2013 14 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 15 } } . . . . . . private Connection getConnection() { return JDBCUtil.getConnection(); } private void returnConnection(Connection conn){ try { conn.close(); } catch (SQLException e) { System.out.println("Problem with closing the connection: " + e.getMessage()); e.printStackTrace(); } } } Kod: PersonJdbcDAO.java Yukarıdaki kod parçasından da görüldüğü gibi, veri tabanı sorguları, sınıfın başında static ve final alanlar olarak ifade edilmişlerdir. savePerson() metodu ise ara yüzden devralınan diğer metotlar gibi PreparedStatement kullanmaktadırlar. savePerson() metodu nesneler ile ilişkisel veri tabanlarının tabloları arasındaki en temel farklılığı gözler önüne sermektedir. Person nesnesini veri tabanına kaydedebilmek için, nesnenin alanlarını tek tek alıp SQL sorgusunun içine koyduk. Bu şekilde yaparak bütüncül bir yapıda olan nesneyi, nesnenin durumunun tablonun sütunlarına karşı gelebilmesi amacıyla, parçalamış olduk. Aşağıdaki kod parçası da yine aynı sınıftan alınmış olup, veri tabanından, verilen kimlik bilgisine uyan Person nesnesini veri tabanından getirmektedir. Bu koddaki retrievePerson() metodu da savePerson() metodunun aksine, veri tabanındaki sütunlarda duran verileri önce belleğe almakta sonra da Person nesnesinin bir kurucusunu kullanarak bu verileri bir araya getirip bir Person nesnesi oluşturmaktadır. 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 16 @Override public Person retrievePerson(int id) { System.out.println("Retrieving the person with id = " + id); Person personRetrieved = null; Connection conn = getConnection(); try { PreparedStatement stmt = conn.prepareStatement(RETRIEVE_PERSON_QUERY); stmt.setInt(1, id);; ResultSet rs = stmt.executeQuery(); while(rs.next()){ String firstName = rs.getString("FIRSTNAME"); String lastName = rs.getString("LASTNAME"); Date dob = rs.getDate("DATEOFBIRTH"); personRetrieved = new Person(id, firstName, lastName, dob); } if(personRetrieved == null) System.out.println("No such person with id = " + id); } catch (SQLException e) { System.out.println("Problem with statement: " + e.getMessage()); e.printStackTrace(); } finally{ returnConnection(conn); } return personRetrieved; } Kod: PersonJdbcDAO.java Yukarıdaki kod örneğinde olduğu gibi JDBC ve veri tabanı sürücüsü, nesneler ile ilişkisel tablolar arasındaki uyumsuzlukları bir noktaya kadar saklamaktadırlar. Örneğin tip farklılıklarının getirebileceği zorluklar ve bunları aşmak için yazılması gereken dönüşüm kodları, veri tabanı 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 17 sürücüsü tarafından yapılan işlemler ve dönüşümler sayesinde, programcının sorumluluğundan çıkmış olmaktadır. JDBC ile yapılan bu örnekle ilgili özet olarak şunları söyleyebiliriz. PersonJdbcDAO sınıfı, PersonDAOI ara yüzünden devraldığı metotlar dışında savePersons(), getConnection() ve returnConnection() isimli iki metoda daha sahiptir. savePersons() metodu geçtiğiniz sayıda Person nesnesini veri tabanında oluşturmaktadır. Dolayısıyla savePersons() metodu, pek çok nesne ile çalışmanızı sağlayacak şekilde yazılmıştır ve PersonFactory sınıfını kullanmaktadır. getConnection() metodu, JDBCUtil sınıfının sağladığı static olan getConnection() metodunu kullanarak veri tabanı bağlantısı elde etmektedir. Kitabın kodlarına baktığınızda göreceğiniz gibi JDBCUtil sınıfı jdbc.properties dosyasından veri tabanı bağlantı bilgilerini okumaktadır. Dolayısıyla siz kodunuzu çalıştırırken bu dosyadaki bilgileri, kullandığınız veri tabanına uygun olarak değiştirmeniz gerekecektir. Benzer şekilde returnConnection() metodu da kullanılan veri tabanı bağlantısını kapatmaktadır. Örnek kod ile göz önüne alınması gereken bir başka nokta da kodun kullandığı Connection nesnesinin autoCommit=true ile çalıştığı gerçeğidir. JDBC kullanan bu örnekte verilmek istenen mesajın, JDBC’nin en iyi bir şekilde nasıl kullanılacağından ziyade, nesne-­‐ilişkisel uyumsuzluğunun en temel yöntemle nasıl aşılabileceği olduğu göz önüne alınmalıdır. Görüldüğü gibi, Java’nın ilk çıktığı günden bu yana bir parçası olan JDBC, Java dünyasında yoğun bir şekilde kullanılmasına rağmen uyumsuzluğun en temel parçası olan, nesnelerin tablolarla eşleştirilmesi konusunda herhangi bir katkısı yoktur. Gerek YOGS SQL cümlelerinde, gerek ise ResultSet nesnesinde, nesnelerin kalıcı olan değişkenleri ile değişkenlerin değerlerinin yazıldığı tablolar ve sütunları arasındaki eşleştirmeler, tamamen programcıya aittir. Bu eşleştirme, tüm YOGS işlemleri için kaçınılmazdır. Bu noktada JDBC ile bu uyumsuzluğa getirilen çözümün aşağıda belirtilen negatif noktalara sahip olduğu söylenebilir: • JDBC, yukarıda da bahsettiğimiz gibi, nesnenin bütüncül yapısını bozan bir teknolojidir. Uygulamanızı ne kadar doğru olarak nesne merkezli yapsanız da iş o 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 18 nesneleri veri tabanında saklamaya geldiğinde, yani YOGS işlemlerinde, nesne merkezli yapı yerini tablo merkezli yapıya terk etmektedir. Bu anlamda JDBC sizi ilişkisel veri tabanıyla yüz yüze gelmekten alıkoymamaktadır. • JDBC, SQL-­‐yoğun bir yöntemdir. Bu durum veri tabanı ile yoğun iletişimde olan uygulamalar için, geliştirme maliyetini arttırmaktadır. Dolayısıyla YOGS işlemlerini yazan geliştiricinin çok iyi SQL, veri tabanı ve buradaki veri yapısının bilgisine sahip olmaları gereklidir. Veri yapısı bilgisi her halükarda kaçınılmaz olabilir fakat bu verinin saklanması ve uygulamaya getirilmesinin bu kadar maliyetli olması, bu konuyu yönetmede insanları farklı yöntemler kullanmaya itmektedir. Birden fazla veri tabanı ile çalışabilecek şekilde geliştirilen ya da veri tabanları ile kurulacak uygulamalarda, veri tabanlarının farklı yapılara ve SQL gerçekleştirmelerine sahip olmalarından dolayı bu maliyet çok artacaktır. • Projenin geliştirme sürecinde nesne-­‐ilişkisel eşleştirmesinin, sıklıkla değişmesi muhtemeldir. Çünkü, projelerdeki değişiklikler, YOGS işlemlerine çoğu zaman doğrudan etkir. Bu eşleştirme de JDBC teknolojisinde Java kodu ile yapılmaktadır. Bu durum ise kod bakımını ve kalitesini kötü yönde etkilemekte dolayısıyla da maliyeti arttırmaktadır. • Nesne sayısı ve nesne karmaşıklığı arttıkça YOGS işlemlerinin maliyeti de artar. Dolayısıyla YOGS, projelerin en fazla iş yükü getiren taraflarındandır. • Projelerin performans, ölçeklenirlik gibi noktalarda kaygıları arttıkça, ön bellek (cache) kullanımı da gündeme gelir ve durum daha da karmaşıklaşır. Genel olarak JDBC, Java’nın sağladığı veri tabanı etkileşimi teknolojileri arasında en alt seviyeli olanıdır. Dolayısıyla, veri tabanı ve SQL ile ilgili kontrol gücü yüksek bir yapı sunmasına rağmen, nesne-­‐merkezli olmaması, eşleştirme bilgisinin Java ile veriliyor olması gibi sebeplerden dolayı maliyeti yüksek bir çözümdür. 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 19 Yukarıda saydığımız negatif noktalardan dolayı bir alternatif çözümümüz, nesne veri tabanları olabilir. 1.5.3. Nesne Veri Tabanları Bir an için yukarıdaki örnekte kullandığımız Person nesnesinin çok daha karmaşık ve farklı türde veriler içeren yapıda olduğunu düşünelim. Örneğin, Person nesnesi, Address, AddressMap, Phone, Picture, Voice vb. nesnelere sahip olsun. Hatta Person nesnesi, içinde kendi cinsinden ana ve baba değişkenlerine sahip olsun. Bu durumda Person nesnesi için yazılacak PersonJdbcDAO sınıfı son derece karmaşık hale gelecektir. Bunun temelde iki sebebi vardır: Person nesnesi, çok fazla sayıda nesne ile ilişkilidir; dolayısıyla veri tabanında Person nesnesinin durumu yönetmek amacıyla yapılacak işlemler de karmaşık olacaktır. İkinci sebep ise Person nesnesi, AddressMap, Phone, Picture, Voice gibi, bilenen iş uygulamalarındakinden farklı tipte çoklu ortam tipli (multimedia) veriler tutan nesnelere sahiptir. Modern zamanlarda çok daha sık karşılaşılan bu gibi durumlar, ilişkisel veri tabanlarının nesnelerin durumlarını tutma konusundaki yeteneklerini zorlamaktadırlar. Ayrıca giderek artan oranda kullanılan nesne-­‐merkezli programlama dilleri de nesne veri tabanlarının kullanımını daha cazip hale getirmektedir. Nesne veri tabanları, nesne-­‐merkezli programlama yapılan ortamlar için ideal bir nesne saklama yapısıdır. İlişkisel veri tabanları nesneleri, tablolarda saklamak amacıyla veri alanlarına bölüp parçalarken, nesne veri tabanları yapılacak olan YOGS işlemlerini, nesnenin bütünselliğini bozmadan, tamamen nesne üzerinde yapar. Bu amaçla nesne veri tabanları, nesne-­‐merkezli diller ile son derece yakın bir yapıda kurgulanmışlardır. Benzer şekilde nesne veri tabanlarında sorgulamalar da nesneler üzerinden gerçekleştirilir. Bu anlamda nesne veri tabanları bir nesne sorgulama diline (Object Query Language, OQL) sahiptirler. Nesne veri tabanları, detaylarına burada girmeyeceğimiz ve ilişkisel veri tabanlarının desteklemediği pek çok nesne kavram ve mekanizmasını da destekler. Nesne kimliği (object identity), tip hiyerarşileri ve kalıtım, erişim yoluyla nesne kalıcılığı (persistence by reachability) bunlar arasında sayılabilir. 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 20 Nesne veri tabanı yönetim sistemleri, ilişkisel veri tabanlarına uzunca bir süredir rakip olmak istemektedirler. 80’li yıllardan itibaren ticari olarak piyasada bulunan nesne veri tabanları, araştırma ve geliştirme kuruluşlarında tercih edilmesine rağmen Bilgi Teknolojileri (BT) sektöründe de tercih edilir hale gelmeleri daha çok zaman alacak gibi görünmektedir. NVTYS dünyasının da Versant ya da Objectivity gibi büyük şirketleri ve markaları olmakla birlikte, temelde tarihi olarak daha geç yola çıktıkları ve başka teknik sebeplerden dolayı, veri saklama sektöründe İVTYS ile yarışması mümkün olmamıştır. NVTYS nesne-­‐merkezli dillerin ve Internet’in yaygınlık kazandığı 1990’lı yıllarda bir ivme kazanmışsa da piyasa payı anlamında beklenen yere gelememiştir. Örneğin, NMVTYS kullanımı özellikle ülkemizde ancak ya bir akademik araştırmanın ya da tatminsiz programcıların geç saatlere varan meraklı çalışmalarının konusu olabilmektedir. 2.5.3.a. Db4O Bu kısımda, nesne kalıcılığının aslında nesne veri tabanı ile nasıl daha kolay ve bütüncül olarak yapılabileceğini göstermeye çalışacağız. Bu amaçla yukarıda JDBC ile yaptığımız örneği, bir nesne veri tabanı ile tekrar yapıp aradaki farklardan bahsedeceğiz. Internet’ten ücretsiz olarak ulaşabileceğiniz ve projelerinizde kullanabileceğiniz Db4O (http://www.db4o.com), Versant’ın Java ve .NET için geliştirmiş olduğu bir nesne veri tabanıdır. Db4O, Versant tarafından farklı lisanslarla kullanıma sunulmuş olup, hem istemci/sunucu (client/server) hem de gömülü (embedded) yapıda kullanılabilmektedir. Db4O’yu gömülü şekilde kullanacağımız örneğimizde öncelikle, nesne veri tabanını soyutlayan ObjectContainer nesnesine ulaşmamız gereklidir. Kalıcı nesnelerin nesne veri tabanıyla etkileşimi bu sınıfın metotları üzerinden yapılacaktır. Bu amaçla aşağıdaki kod parçasını kullanıp ObjectContainer nesnesine ulaşacağız. . . . final static String DB4OFILENAME = System.getProperty("user.home") + 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 21 "/ch01.db4o"; private static ObjectContainer db; public PersonDb4oDAO(){ db = Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(), DB4OFILENAME); } . . . Kod: PersonDb4oDAO.java Yukarıdaki kod, kullanıcının ev klasöründe nesneleri saklamak için “ch01.db4o” isimli bir dosya açmakta ve sonrasında da bu dosyayı kullanacak şekilde nesne veri tabanını soyutlayarak kullanmamızı sağlayan ObjectContainer nesnesini oluşturmaktadır. Şimdi yapılacak işlem ise, JDBC örneğinde olduğu gibi, PersonDAOI ara yüzünü, ObjectContainer nesnesini kullanarak gerçekleştirecek olan PersonD4boDAO isimli bir sınıf geliştirmektir. Bunu yaptığımızda PersonDb4oDAO sınıfı aşağıdaki gibi metot gerçekleştirmelerine sahip olacaktır. public class PersonDb4oDAO implements PersonDAOI { . . . @Override public void savePerson(Person person) { System.out.println("Saving person:" + person); db.store(person); System.out.println("Person saved!"); } @Override public Person retrievePerson(int id) { System.out.println("Retrieving the person with id = " + id); Person prototype = new Person(id); ObjectSet<Person> result = db.queryByExample(prototype); int personCount = result.size(); if(personCount > 0){ 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 22 Person personRetrieved = result.get(0); return personRetrieved; } else{ System.out.println("No such person with id = " + id); return null; } } . . . } Kod: PersonDb4oDAO.java JDBC ile geliştirilen koda göre çok daha kısa ve anlaşılır olan bu kod, nesnelerin bütünlüğünü koruyarak işlem yapmaktadır. Gerçekten de savePerson() metodu Db4O’nun ana nesnesi olan ObjectContainer nesnesi üzerinde sadece store() metodunu çağırmaktadır. Bir satırlık bu metoda karşın JDBC ile yazılan 20 civarındaki satır! İşin en ilginç yanı da, Person nesnesi karmaşıklaştıkça JDBC kodunun satır sayısının en az doğrusal olarak artacak olmasına karşın, Db4O kodunun hala bir satırdan ibaret olarak kalacağı gerçeğidir. (Bu kısımdaki kodlarda nesne veri tabanının, veri tabanını transactionlarını otomatik olarak kalıcı kılacak şekilde ayarlandığını fark etmişsinizdir.) Kitabın kodlarını incelediğiniz zaman bu cinsten, karmaşıklık ve yazılan satır sayısının miktarında çok değişik ve ilginç farklılıklar gözlemleyeceksiniz. 1.6. Çözüm: Nesne-­‐İlişkisel Eşleştirme Teorik altyapısı 70’li yıllarda atılan, geçen 40 yılda olgunlaşarak günümüze gelen ve kurumsal uygulamaların gerektirdiği özellik ve hizmetleri bünyesinde barındıran ilişkisel veri tabanı yazılımları, hem Oracle, IBM, Microsoft gibi bu dünyanın öncü şirketleri tarafından değişik ticari markalar altında lisanslı olarak hem de MySQL ya da PostgreSQL gibi açık kaynak kodlu ve/veya ticari olmayan lisanslarla piyasaya sunulmaktadır. Veriyi güvenli ve sağlıklı olarak saklama ve 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 23 sunma konusunda kurumsal uygulamalarda gerekli olan pek çok özelliği bünyesinde barındıran ilişkisel veri tabanları, kullanım yaygınlığı açısından nesne veri tabanlarına tartışılmayacak bir üstünlük sağlamıştırlar. Nesne-­‐merkezli dillerin yaygınlık kazanması ve ilişkisel veri tabanlarının pratikte alternatifsiz olmaya devam etmeleri, nesne-­‐ilişkisel uyumsuzluğunun daha uzun seneler aramızda olmaya devam edeceğinin göstergelerindendir. Nesne-­‐merkezli uygulamalar için en tabii kalıcılık yönteminin nesne-­‐merkezli veri tabanı kullanmak olduğunu daha önce belirtmiştik. Fakat böyle bir çözümün kurumsal projelerde uygulanabilirliği yukarıda çok kısaca ele aldığımız sebeplerden dolayı gerçekçi görünmemektedir. Bu durum nesne-­‐merkezli dillerin ilk çıktığı, Smalltalk ve C++ günlerinden bu yana böyledir. Bu 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, veri tabanı önüne konulacak bir katmanla veri tabanının nesne-­‐merkezli bir veri tabanına dönüştürülmesidir. Yani veri tabanı ile uygulama arasına konulacak böyle bir katmanın iki ara yüzü olacaktır: Birisi SQL’i kullanarak veri tabanı ile haberleşen ve her türlü YOGS cümleciklerini üretip, çalıştırıp, sonuçlarını uygulamaya döndüren ara yüz, diğeri ise bir API üzerinden nesnelerle alakalı YOGS işlemlerini yapmaya izin veren ara yüzdür. Dolayısıyla bu katman uygulama geliştiricileri, arka taraftaki veri tabanının ilişkisel yapısından uzak tutacak ve onlara, nesneleri sanki nesne veri tabanında kalıcı kılıyormuş gibi kod geliştirme imkanı sağlayacaktır. NIE araçlarının bir başka faydası da eşleştirme bilgisini XML gibi kod dışında bir yapıda tutmaya izin vermesidir. Bu durumda eşleştirmedeki değişikliklerin kod değişimine yol açmaması, kod kalitesi ve bakımı açısından son derece önemlidir. Nesne-­‐ilişkisel eşleştirme (NİE) (object-­‐relational Mapping, ORM) aracı denilen böyle bir çözüm, geliştirilmesi ve adaptasyonu zaman ve bilgi açısından zor olmakla birlikte, bir kurumun kurumsal BT mimarisinin bir parçası haline getirildiğinde uygulama geliştirmede çok ciddi zaman ve gayret kazanımı sağlayacağı da açıktır. Bu sebeple tarihi olarak nesne-­‐merkezli dillerle geliştirme yapan kurumların bir kısmı ya kendi NİE araçlarını yazmış ya da o diller için piyasada ticari olarak var olan NİE araçlarını kullanmayı tercih etmiştir. 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 1.7. 24 NİE Araçlarının Kısa Tarihi Nesne-­‐ilişkisel eşleştirme araçlarını genel olarak Java-­‐öncesi ve Java-­‐sonrası diye ikiye ayırdığımızda, ilk dönem ile ilgili akla gelen ürünlerin en önemlisi Rogue Wave’in C++ için geliştirdiği ve eski ismi DBTools.h++ olan SourcePro DB’si ile The Object People’ın Smalltalk için geliştirdiği TopLink’tir. Java döneminde de pek çok NİE aracı çıkmıştır. Java’ın ilk günlerinde pek çok yazılım evi JDBC’yi kullanarak kendi NİE aracını yazmayı denemişti. Java dünyasında ilk çıkan NİE araçlarından birisi, Sun’ın Java için yazıp ticari olarak piyasaya sürdüğü JavaBlend’dir (Bu satırların yazarı, bu NİE aracının fiyatını Sun ile görüşerek öğrenmişti. Nostaljik bir bilgi olarak belirtelim ki JavaBlend’in o zamanki birim lisans fiyatının son derece yüksekti. Ama günümüzde cevval bir üniversite öğrencisi eğitimi sırasında Hibernate gibi bir NİE aracını kullanabiliyor. Bu da Java kültürünün bize kazandırdıklarından olsa gerek.) Daha sonraki yıllarda Sun JavaBlend’i geliştirmeye devam etmedi ve bu ürün piyasadan çekildi. Kurumsal Java yani Java EE’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 konumlandırılmıştı. Java EE uyumlu uygulama sunucularının (application server) sağladıkları ayar (configuration) yapılarından da yararlanarak, uygulamadaki sınıflar veri tabanındaki tablolarla eşleştiriliyor ve programcının isteğine göre YODS sorguları, uygulama sunucusu tarafından çalışma zamanında oluşturuluyordu. İçinde bulunulan sunucu tarafından yönetilen bu veri katmanı yapısının (Container-­‐Managed Persistence, CMP) yetersiz kaldığı durumlarda programcı YODS sorgularını kendisi yazmak suretiyle (Bean-­‐Managed Persistence, BMP) daha etkin yapılar da oluşturabilmekteydi. Tabi kurumsal Java yani Java EE, Java’dan önce, birbirinden farklı mimari kabuller ve yaklaşımlar içeren çok farklı uygulama ve araç tarafından parçalı olarak yerine getirilen orta katman (middleware) servislerini tek bir mimari yaklaşım içerisinde halletmenin ilk girişimi olduğu için pek çok sıkıntıyı da beraberinde getirmişti. Büyük bir hüsnü kabul ile karşılanmasına rağmen Java EE’nin en temel bileşeni olan 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 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 25 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ı ve en iyi uygulamalarda (best practices) “entity beanleri kullanmayın” (don’t use entity beans) gibi tavsiyelerin sıklıkla geçmeye başlaması gerek Sun’ı gerek ise Java programcılarını veri katmanında kullanacakları NİE aracı konusunda alternatifler aramaya itti. Bu süreçte Castor (http://www.castor.org), iBatis (http://ibatis.apache.org/) ve Hibernate (http://www.hibernate.org) gibi bazıları açık kaynak kodlu bağımsız NİE araçları ortaya çıktı. (Aslında alternatif arama Java EE’nin sadece EJB’nin entity beanlerine has bir durum değildi; tüm Java EE ya da o anki genel ismi itibariyle J2EE için alternatif yaratma girişimlerinden bahsedilebilir. Spring, bunlardan en başarılı olandır.) Bu araçlar Java programcıları tarafından büyük bir kabul ile uygulamaya kondu. Bu tür araçların entity beanlerden en büyük farkı, çalışmak için bir uygulama sunucusu gerektirmiyor olup sadece NİE konusuna odaklanmış, daha hafif ve daha hızlı öğrenilip kullanılan bileşenler olmalarıydı. Entity beanlerdeki sıkıntının farkında olan Sun, 2000’li yılların JDO (Java Data Objects) isimli yeni bir NIE çözümünü dünyaya duyurdu. Bir NİE çözümü olarak farklı bir yöntem izleyen JDO, nesne kalıcılığına temel olarak daha fazla nesne veri tabanı üreticilerinin etkisindeki ODMG’nin (http://www.odbms.org/odmg/) yaklaşımını benimsemişti. Temel farklılık, JDO ile kalıcı kılınacak nesnelerin derleme zamanında (compile time) bir zenginleştirme (enhancement) sürecinden geçirilmesi gerekmesiydi. Çalışma zamanında bir uygulama sunucusuna ihtiyaç da duymayan JDO’yu, Java’ya en başından bu yana yatırım yapan Oracle ve IBM gibi büyük şirketlerden hiçbirisi desteklemedi ve bu şirketler herhangi bir JDO ürünü geliştirip Java programcılarının kullanımına sunmadı. Dolayısıyla da JDO yaygın bir kullanım alanı bulamadı. Yukarıda entity beanlerin problemlerinden dolayı veri katmanını EJB’ler üzerine bina etmekten vazgeçen Java toplumunun kendi içinden bağımsız NİE araçları çıkardığından bahsetmiştik. Bu araçlardan Hibernate, NİE problemini çözmek için basit ama etkin bir yaklaşım 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 26 oluşturmuş ve kullanıcı topluluğundan gelen geri beslemeler ile çok daha başarıyla kullanılabilir hale gelmişti. Tabi olarak bu durum Hibernate’in kullanımını gittikçe yaygınlaştırdı. Ayrıca Smalltalk dünyasında ciddi bir tecrübe kazanan TopLink’in 90’lı yıllarda zaten bir Java versiyonu çıkarılmıştı. Sonrasında TopLink, önce WebGain daha sonra da 2002’de Oracle tarafından satın alındı ve Oracle’ın Fusion Middleware’inin bir parçası olarak Java programcılarının kullanımına sunuldu. 1.8. JPA Yukarıda bahsi geçen bütün bu bağımsız NİE araçları az ya da çok kendi çevrelerinde bir kullanıcı kitlesi çekmeyi başarmışlardı. Özellikle Hibernate bu konuda lider durumdaydı. Bu araçlarla alakalı en temel problem hepsinin farklı bir yaklaşıma dolayısıyla da bir API’ye sahip olmalarıydı. Bu yüzden bu çözümlerden hiç birisi standart olmadığı gibi birbirlerinin yerine geçebilir durumda da 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 bileşeni 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 Java programcılarına sunulan JPA 1.0, JavaBlend ile başlayıp, başarısız entity bean, başarılı ama yaygınlaşamayan JDO ve çoğu başarılı bağımsız NİE araçlarının en iyi yaklaşımlarını bir araya getirdiği için çok daha büyük bir ilgiyle karşılaştı. 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.) Bu durum en başından bu yana tanım (specification) tabanlı ilerleyen Java’nın yapısına çok daha uygundu ve Java programcıları artık standart bir API üzerinden pek çok farklı NEI ürününü kullanabilir hale gelmişlerdi. JPA özellikle bir EJB türü olan entity beanlere kıyasla çok daha basit, rahat öğrenilen ve kullanılan bir tabiatta olacak şekilde geliştirildi. JPA öncelikle POJO (Plain Old Java Objects) modeline sahiptir; dolayısıyla JPA ile kalıcı kılınacak nesnelerin herhangi bir özel ara yüzü gerçekleştirmeleri ya da herhangi bir sınıftan miras devralmaları gerekmemektedir. JPA hem Java SE hem de Java EE ortamlarında kullanılabilecek şekilde geliştirilmiştir. Çok küçük ayar 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 27 değişiklikleri dışında JPA’yı Java SE ile Java EE ortamında kullanmanın hiç bir farkı yoktur. Nesneler arasındaki 1-­‐1, 1-­‐N ve M-­‐N ilişkiler yanında miras 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, Java programcılarına son derece basit ve rahat anlaşılır bir API üzerinden nesnelerini kalıcı kılma imkanı vermektedir. JPA, nesneler üzerinden sorgulama yapmaya izin veren bir nesne sorgu diline (Java Persistence Query Language, JPQL) sahiptir. JPQL ile veri tabanından bir ya da daha fazla nesne seçilerek belleğe getirilebilir. JPA ayrıca SQL’i de desteklemektedir; SQL kullanarak da veri tabanından nesne ya da nesneler getirmek mümkündür. Bütün bunların yanında JPA, transaction, eş zamanlama (concurrency) ve ön bellek (cache) yönetimi de yapmaktadır. Bir sonraki bölümde JPA’nın özelliklerine daha yakından bakmaya başlayacağız. 1.8.1. JPA 2.0 JPA’nın ilk sürümünün çıkmasından hemen sonra, 2007 yılında JSR-­‐317 ile 2.0 sürümü için çalışmalara başlandı. JPA 2.0, Java EE 6’nın bir parçası olarak 2009 Aralık ayında yayımlandı. JPA 1.0’da olmayan aşağıdaki özellikler, 2.0 sürümü ile JPA’ya eklendi: • Daha zengin eşleştirme yapısı • Ayarlara bazı ekler • Programatik olarak sorgu yazmayı sağlayan Sorgu API’si (Query API) 1.8.2. JPA 2.1 JPA’nın 2.1 sürümü 2013 yılının Mayıs ayında yayınlanan Java EE 7’nin bir parçası olarak yayınlandı. JPA’nın 2.1 sürümü ile gelen temel yenilikler ise şunlardır. • Veri tabanındaki prosedürler (stored procedure) desteği 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 28 • Dönüştürücüler (converters) • Standart otomatik şema (schema) oluşturma • JPAQ ve kriter API’sinde yenilikler Biz bu kitapta, JPA’nın en son sürümü olan 2.1’in özelliklerini de ele alacağız. Uygulamamızdaki Person sınıfının VEN şablonu kullanılarak oluşturulmuş PersonDAOI ara yüzünü, şu ana kadar JDBC kullanılarak PersonJdbcDAO isimli bir sınıfla ve DB4o’yu kullanarak PersonDb4oDAO isimli sınıfla iki defa gerçekleştirdik. Şimdi ise aynı şeyi JPA kullanarak PersonJpaDAO isimli bir sınıfla gerçekleştirelim: public class PersonJpaDAO implements PersonDAOI { public PersonJpaDAO() { JPAUtil.setPersistenceUnitName("ch01"); } @Override public void savePerson(Person person) { System.out.println("Saving person:" + person + "\n"); EntityManager em = JPAUtil.getEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); em.persist(person); tx.commit(); em.close(); System.out.println("Person saved!"); } @Override public Person retrievePerson(int id) { System.out.println("Retrieving the person with id = " + id); EntityManager em = JPAUtil.getEntityManager(); Person personRetrieved = em.find(Person.class, id); em.close(); 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 29 if (personRetrieved == null) { System.out.println("No such person with id = " + id); return null; } else { return personRetrieved; } } . . . } Kod: PersonJpaDAO.java Yukardaki kod parçasındaki savePerson() metodunda yapılan önce EntityManager nesnesini JPAUtil sınıfı yardımıyla oluşturmak, sonrasında ise bu nesne üzerindeki persist() metodunu kullanarak, geçilen Person nesnesini veri tabanında kalıcı hale getirmektir. Benzer şekilde de retrievePerson() metodunda da geçilen kimlik bilgisine karşı gelen Person nesnesi, EntityManager üzerindeki find() metoduyla veri tabanında getirilmektedir. 19 Ekim 2013 1. Bölüm Java’da Nesne Kalıcılığının Temelleri www.jpabook.com 1.9. • 30 Bölüm Özeti Bu bölümde, nesne-­‐merkezli dillerle geliştirilen uygulamalardaki nesne kalıcılığı kavramına giriş yapıldı. İlişkisel veri tabanlarının veri depolamanın standart bir yolu olduğu dünyada, nesne merkezli diller ile geliştirilen uygulamalarda üretilen nesnelerin durumlarının saklanması için en popüler yol olmasının getirdiği nesne-­‐ ilişkisel uyumsuzluğu ele alındı. • Nesne-­‐ilişkisel uyumsuzluğunun tabiatı üzerinde duruldu. Nesne-­‐merkezli dillerin nesne yapıları ile ilişkisel veri tabanlarının tabloları arasında, iki tarafın da çözmeye çalıştığı problemin farklı olmasından kaynaklanan uyumsuzluğun, uygulama geliştiricilere nasıl problemler çıkardığı ele alındı. • Veri erişimi mimarisi ile ilgili bir tasarım şablonu olarak Veri Erişim Nesnesi (VEN) ele alındı ve örnek olarak oluşturulan Person sınıfı ve PERSONS tablosu arasında yapılacak YOGS işlemlerini soyutlamak amacıyla kullanıldı. • VEN şablonu, yukarıda bahsedilen örnekteki nesne-­‐ilişkisel uyumsuzluk problemini aşmak için iki farklı teknoloji ile gerçekleştirildi. Bu teknolojiler JDBC ve nesne veri tabanlarıdır. Nesne veri tabanı olarak Db4O kullanıldı. Nesne-­‐merkezli veri tabanı kullanımının getirdiği tabilik ve kolaylık karşısında JDBC’nin zorlukları gözlendi. • Nesne-­‐ilişkisel uyumsuzluğunu aşmak için tarihi olarak yapılan girişimler anlatıldı. Bu girişimlerin Java dünyasındaki zengin geçmişi ele alındı ve JPA’ya giriş yapılarak, yukarıdaki örnek JPA ile gerçekleştirildi. 19 Ekim 2013