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ı
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.bahadirakin</groupId> <artifactId>spring-jpa</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>spring-jpa</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.java.version>1.6</project.java.version> <!-- Versions --> <slf4j.version>1.7.5</slf4j.version> <spring.version>4.0.5.RELEASE</spring.version> <hibernate.version>4.2.13.Final</hibernate.version> <hibernate-jpa.version>1.0.1.Final</hibernate-jpa.version> <mysql.version>5.1.30</mysql.version> <typetools.version>0.3.0</typetools.version> <postgresql.version>9.3-1101-jdbc4</postgresql.version> <h2.version>1.3.176</h2.version> </properties> <build> <finalName>spring-jpa</finalName> <plugins> <!-- Compiler --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${project.java.version}</source> <target>${project.java.version}</target> </configuration> </plugin> <!-- Eclipse Plugin --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-eclipse-plugin</artifactId> <version>2.9</version> <configuration> <downloadJavadocs>true</downloadJavadocs> <downloadSources>true</downloadSources> <additionalConfig> <file> <name>.settings/org.eclipse.core.resources.prefs</name> <content><![CDATA[eclipse.preferences.version=1${line.separator}encoding/<project>=${project.build.sourceEncoding}${line.separator}]]></content> </file> </additionalConfig> </configuration> </plugin> </plugins> </build> <dependencies> <!-- LOGGING DEPENDENCIES --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-ext</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.0.13</version> </dependency> <!-- Spring Dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <!-- JPA Dependencies --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.0-api</artifactId> <version>${hibernate-jpa.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-c3p0</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-ehcache</artifactId> <version>${hibernate.version}</version> </dependency> <!-- Database Dependencies --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>{mysql.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>${postgresql.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>${h2.version}</version> <scope>runtime</scope> </dependency> <!-- Test Engine Dependencies --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> </project> |
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;
- 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.
- Model, DAO ve Servislerin Tanımlanması: Burada JPA modelimizi, basitçe tanımlanmış DAO ve Servis yapılarından bahsedeceğiz.
- 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.
- 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.
- 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.
- 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # JDBC Bilgileri jdbc.driverClassName=org.h2.Driver jdbc.url=jdbc\:h2\:~/h2SpringJpaTutorialDb;AUTO_SERVER\=TRUE jdbc.username=bhdrkn jdbc.password=1q2w3e hibernate.default_schema=H2SPRINGJPATUTORIALDB.PUBLIC # https://docs.jboss.org/hibernate/core/4.2/manual/en-US/html/ch03.html#configuration-optional-properties hibernate.ejb.naming_strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy hibernate.hbm2ddl.auto=create hibernate.showSql=true hibernate.formatSql=true hibernate.generate_statistics=true hibernate.max_fetch_depth=3 hibernate.default_batch_fetch_size=16 hibernate.jdbc.batch_size=20 hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory hibernate.cache.use_query_cache=true hibernate.cache.use_second_level_cache=false |
persistance-uat.properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # JDBC Bilgileri jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc\:mysql\://localhost\:3306/SpringJpaTutorialDb?characterEncoding\=UTF-8 jdbc.username=bhdrkn jdbc.password=1q2w3e hibernate.default_schema=SpringJpaTutorialDb # https://docs.jboss.org/hibernate/core/4.2/manual/en-US/html/ch03.html#configuration-optional-properties hibernate.ejb.naming_strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy hibernate.hbm2ddl.auto=update hibernate.showSql=true hibernate.formatSql=true hibernate.generate_statistics=false hibernate.max_fetch_depth=3 hibernate.default_batch_fetch_size=16 hibernate.jdbc.batch_size=20 hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory hibernate.cache.use_query_cache=true hibernate.cache.use_second_level_cache=false |
persistance-prod.properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # JDBC Bilgileri jdbc.driverClassName=org.postgresql.Driver jdbc.url=jdbc\:postgresql\://localhost\:5432/SpringJpaTutorialDb?characterEncoding\=UTF-8 jdbc.username=bhdrkn jdbc.password=1q2w3e hibernate.default_schema=SpringJpaTutorialDb # https://docs.jboss.org/hibernate/core/4.2/manual/en-US/html/ch03.html#configuration-optional-properties hibernate.ejb.naming_strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy hibernate.hbm2ddl.auto=update hibernate.showSql=false hibernate.formatSql=false hibernate.generate_statistics=false hibernate.max_fetch_depth=3 hibernate.default_batch_fetch_size=16 hibernate.jdbc.batch_size=20 hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory hibernate.cache.use_query_cache=true hibernate.cache.use_second_level_cache=true |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | INSERT INTO car (id, year, brand, color, price) VALUES (1, 1969,'Audi', 'Yellow', 49233); INSERT INTO car (id, year, brand, color, price) VALUES (2, 1992,'Volvo', 'Brown', 36248); INSERT INTO car (id, year, brand, color, price) VALUES (3, 1979,'Renault', 'Black', 77932); INSERT INTO car (id, year, brand, color, price) VALUES (4, 1982,'Honda', 'Yellow', 87409); INSERT INTO car (id, year, brand, color, price) VALUES (5, 1989,'Jaguar', 'Green', 74833); INSERT INTO car (id, year, brand, color, price) VALUES (6, 1977,'Volvo', 'Blue', 16821); INSERT INTO car (id, year, brand, color, price) VALUES (7, 1973,'Honda', 'White', 16255); INSERT INTO car (id, year, brand, color, price) VALUES (8, 1983,'Volvo', 'Blue', 4478); INSERT INTO car (id, year, brand, color, price) VALUES (9, 1993,'Ford', 'Blue', 11019); INSERT INTO car (id, year, brand, color, price) VALUES (10, 1991,'Honda', 'White', 14820); INSERT INTO car (id, year, brand, color, price) VALUES (11, 1965,'BMW', 'Green', 95244); INSERT INTO car (id, year, brand, color, price) VALUES (12, 1995,'Renault', 'Yellow', 21252); INSERT INTO car (id, year, brand, color, price) VALUES (13, 1965,'Jaguar', 'Yellow', 52705); INSERT INTO car (id, year, brand, color, price) VALUES (14, 1984,'Audi', 'Red', 79079); INSERT INTO car (id, year, brand, color, price) VALUES (15, 1977,'Ford', 'Blue', 87718); INSERT INTO car (id, year, brand, color, price) VALUES (16, 1987,'BMW', 'Orange', 36324); INSERT INTO car (id, year, brand, color, price) VALUES (17, 1986,'Ford', 'Maroon', 2489); INSERT INTO car (id, year, brand, color, price) VALUES (18, 1995,'Mercedes', 'Silver', 96858); INSERT INTO car (id, year, brand, color, price) VALUES (19, 1994,'Audi', 'White', 55902); INSERT INTO car (id, year, brand, color, price) VALUES (20, 1996,'Jaguar', 'Brown', 75318); INSERT INTO car (id, year, brand, color, price) VALUES (21, 2000,'Renault', 'Red', 5677); INSERT INTO car (id, year, brand, color, price) VALUES (22, 2003,'Volkswagen', 'Silver', 8257); INSERT INTO car (id, year, brand, color, price) VALUES (23, 1974,'BMW', 'Green', 60866); INSERT INTO car (id, year, brand, color, price) VALUES (24, 1985,'Mercedes', 'Brown', 10972); INSERT INTO car (id, year, brand, color, price) VALUES (25, 2009,'Volkswagen', 'White', 96635); |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd"> <bean id="databaseProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="ignoreResourceNotFound" value="false" /> <property name="locations"> <list> <value>classpath*:environment/persistance-${environment:dev}.properties </value> </list> </property> </bean> <context:property-placeholder ignore-resource-not-found="true" ignore-unresolvable="true" system-properties-mode="ENVIRONMENT" properties-ref="databaseProperties" /> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driverClassName}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxPoolSize" value="50" /> <property name="acquireIncrement" value="10" /> <property name="maxStatements" value="0" /> <property name="maxStatementsPerConnection" value="0" /> <property name="initialPoolSize" value="2" /> <property name="idleConnectionTestPeriod" value="10800" /> <property name="maxIdleTime" value="21600" /> <property name="unreturnedConnectionTimeout" value="600000" /> <property name="debugUnreturnedConnectionStackTraces" value="false" /> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> </property> <property name="jpaProperties" ref="databaseProperties" /> <property name="packagesToScan"> <list> <value>com.bahadirakin.model</value> </list> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> </beans> |
Ş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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | package com.bahadirakin.model; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "car") public class Car implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; @Column(name = "brand") private String brand; @Column(name = "year") private int year; @Column(name = "color") private String color; @Column(name = "price") private Double price; public Car() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } @Override public String toString() { return "Car [id=" + id + ", brand=" + brand + ", year=" + year + ", color=" + color + ", price=" + price + "]"; } } |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package com.bahadirakin.dao; import java.io.Serializable; import java.util.List; import com.bahadirakin.model.Car; public interface ICarDao extends Serializable{ void detach(Object arg0); List<Car> findAll(); Car merge(Car arg0); void persist(Car arg0); } |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | package com.bahadirakin.dao; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.bahadirakin.model.Car; @Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class, value = "transactionManager") public class CarDao implements ICarDao { private static final long serialVersionUID = 1L; @PersistenceContext(unitName = "entityManagerFactory") private EntityManager entityManager; public EntityManager getEntityManager() { return entityManager; } public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } /* (non-Javadoc) * @see com.bahadirakin.dao.ICarDao#detach(java.lang.Object) */ @Override public void detach(Object arg0) { entityManager.detach(arg0); } /* (non-Javadoc) * @see com.bahadirakin.dao.ICarDao#findAll() */ @Override public List<Car> findAll() { System.out.println(entityManager); return entityManager.createQuery("SELECT car FROM Car car").getResultList(); } /* (non-Javadoc) * @see com.bahadirakin.dao.ICarDao#merge(com.bahadirakin.model.Car) */ @Override public Car merge(Car arg0) { return entityManager.merge(arg0); } /* (non-Javadoc) * @see com.bahadirakin.dao.ICarDao#persist(com.bahadirakin.model.Car) */ @Override public void persist(Car arg0) { entityManager.persist(arg0); } } |
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ğer | Anlamı |
REQUIRED | Eğer açık bir transaction varsa onu kullanır. Eğer yoksa yeni bir transaction yaratır |
SUPPORTS | Eğ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. |
MANDATORY | Eğer açık bir trasnaction varsa onu kullanır. Yoksa exception fırlatır. |
REQUIRES_NEW | EÄŸ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_SUPPORTED | EÄŸ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. |
NEVER | Metodu transactionsız olarak çalıştırır. Eğer aktif bir transaction varsa exception fırlatır. |
NESTED | Nested 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package com.bahadirakin.service; import java.io.Serializable; import java.util.List; import org.springframework.stereotype.Service; import com.bahadirakin.model.Car; public interface ICarService extends Serializable{ void delete(Object arg0); List<Car> getAllCars(); Car updateCar(Car arg0); void createNewCar(Car arg0); } |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | package com.bahadirakin.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.bahadirakin.dao.ICarDao; import com.bahadirakin.model.Car; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, value = "transactionManager") public class CarService implements ICarService { private static final long serialVersionUID = 1L; private ICarDao carDao; /* * (non-Javadoc) * * @see com.bahadirakin.service.ICarService#delete(java.lang.Object) */ @Override public void delete(Object arg0) { carDao.detach(arg0); } /* * (non-Javadoc) * * @see com.bahadirakin.service.ICarService#getAllCars() */ @Override public List<Car> getAllCars() { return carDao.findAll(); } /* * (non-Javadoc) * * @see * com.bahadirakin.service.ICarService#updateCar(com.bahadirakin.model.Car) */ @Override public Car updateCar(Car arg0) { return carDao.merge(arg0); } /* * (non-Javadoc) * * @see * com.bahadirakin.service.ICarService#createNewCar(com.bahadirakin.model * .Car) */ @Override public void createNewCar(Car arg0) { carDao.persist(arg0); } public ICarDao getCarDao() { return carDao; } public void setCarDao(ICarDao carDao) { this.carDao = carDao; } } |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd"> <import resource="classpath:persistenceContext.xml" /> <context:annotation-config /> <bean id="carDao" class="com.bahadirakin.dao.CarDao" /> <bean id="carService" class="com.bahadirakin.service.CarService"> <property name="carDao" ref="carDao" /> </bean> </beans> |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package com.bahadirakin; import java.util.List; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.bahadirakin.model.Car; import com.bahadirakin.service.CarService; import com.bahadirakin.service.ICarService; public class App { public static void main(String[] args) throws InterruptedException { System.out.println("Application is going to be started"); final AbstractApplicationContext applContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); applContext.registerShutdownHook(); System.out.println("Application is started"); final ICarService carService = (ICarService) applContext.getBean("carService"); final List<Car> cars = carService.getAllCars(); if(cars == null || cars.isEmpty()){ System.out.println("Cars is empty."); }else{ System.out.println("Cars: " + cars); } Thread.sleep(10000); } } |
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.
Son
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