11. [Cours]: Spring Data ile ilişkisel veritabanı yönetimi
Anahtar kelimeler: çok katmanlı mimari, Spring, bağımlılık enjeksiyonu, API JPA (Java Persistence API), Spring Data.
[DAO] katmanını, Spring ekosisteminin bir dalı olan TD ile [Spring Data] kullanarak uygulayacağız. [Spring Data], JPA (Java Persistence API) katmanına dayanır; bu katman, [DAO] katmanının SQL komutları yerine nesneleri işleyebilmesini sağlar. Sonuç olarak, [DAO] katmanı bir veritabanıyla iletişim kurduğunun farkında değildir. Yalnızca [Spring Data] katmanının arayüzünü tanır.
![]() |
Öncelikle iki örnek üzerinden [Spring Data]'i inceleyeceğiz.
11.1. Support
![]() |
- [1]'te, [support / chap-11] klasörü üç Eclipse projesi içerir;
- [2] dosyasında ise bu bölümdeki örnek veritabanını oluşturmaya yarayan SQL komut dosyası;
11.2. Örnek 1
Spring web sitesinde, Spring'e başlamak için çok sayıda öğretici bulunmaktadır ([http://spring.io/guides]). Spring Data'yı tanıtmak için bunlardan birini kullanacağız. Bunun için Spring Tool Suite'i (STS) kullanacağız.
![]() |
- [1]'te, [spring.io/guides]'teki öğreticilerden birini içe aktarıyoruz;
![]() |
- [2]'te, Spring Data ile bir veritabanına nasıl erişileceğini gösteren [Accessing Data Jpa] öğreticisini seçiyoruz;
- [3]'te, Maven tarafından yapılandırılmış bir proje seçilir;
- [4]'te, öğretici iki şekilde sunulabilir: öğreticiyi takip ederek doldurulacak boş bir sürüm olan [initial] veya öğreticinin son hali olan [complete]. Biz ikincisini seçiyoruz;
- [5]'te, öğreticiyi bir tarayıcıda görüntülemeyi seçebiliriz;
- [6]'te ise nihai projeyi görebiliriz.
11.2.1. Projenin Maven yapılandırması
Projenin Maven bağımlılıkları [pom.xml] dosyasında yapılandırılmıştır:
<groupId>org.springframework</groupId>
<artifactId>gs-accessing-data-jpa</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.10.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
<properties>
<!-- her şey için UTF-8 kullan -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<start-class>hello.Application</start-class>
</properties>
- 5-9. satırlar: bir üst Maven projesini tanımlar. Projenin bağımlılıklarının büyük kısmını bu proje tanımlar. Bu bağımlılıklar yeterli olabilir; bu durumda başka bağımlılık eklenmez, ya da yeterli olmayabilir; bu durumda eksik bağımlılıklar eklenir;
- 12-15. satırlar: [spring-boot-starter-data-jpa]'e bir bağımlılık tanımlar. Bu artefakt, Spring Data sınıflarını içerir;
- satır 16-19: bellek içi veritabanları oluşturmaya ve yönetmeye olanak tanıyan SGBD ve H2'e bir bağımlılık tanımlar.
Bu bağımlılıkların getirdiği sınıflara bir göz atalım:
![]() | ![]() | ![]() |
Bunların sayısı oldukça fazla:
- bazıları Spring ekosistemine aittir (spring ile başlayanlar);
- diğerleri ise Hibernate ekosistemine aittir (hibernate, jboss); burada JPA uygulamasını kullanıyoruz;
- bazıları test kütüphaneleridir (junit, hamcrest);
- bazıları ise günlük kütüphaneleridir (log4j, logback, slf4j);
Hepsini saklayacağız. Üretim ortamındaki bir uygulama için ise sadece gerekli olanları saklamak gerekir.
[pom.xml] dosyasının 26. satırında şu satır yer almaktadır:
<start-class>hello.Application</start-class>
Bu satır, aşağıdaki satırlarla bağlantılıdır:
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
6-9. satırlarda, [spring-boot-maven-plugin] eklentisi uygulamanın çalıştırılabilir jar dosyasını oluşturmaya olanak tanır. [pom.xml] dosyasının 26. satırı ise bu jar dosyasının çalıştırılabilir sınıfını belirtir.
11.2.2. [JPA] katmanı
Veritabanına erişim, [JPA] katmanı ve Java Persistence API aracılığıyla gerçekleştirilir:
![]() |
![]() |
Uygulama basittir ve [Customer] istemcilerini yönetir. [Customer] sınıfı, [JPA] katmanının bir parçasıdır ve şu şekildedir:
package hello;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
protected Customer() {
}
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
}
}
Bir müşterinin kimliği [id], adı [firstName] ve soyadı [lastName]'tir. Her [Customer] örneği, veritabanındaki bir tablonun bir satırını temsil eder.
- 8. satır: JPA anotasyonu, [Customer] örneklerinin kalıcılığının (Oluşturma, Okuma, Güncelleme, Silme) JPA uygulaması tarafından yönetileceğini belirtir. Maven bağımlılıklarına göre, JPA / Hibernate uygulamasının kullanıldığı görülmektedir;
- 11-12. satırlar: JPA anotasyonları, [id] alanını [Customer] tablosunun birincil anahtarıyla ilişkilendirir. 12. satır, JPA uygulamasının, kullanılan SGBD'e özgü birincil anahtar oluşturma yöntemini (burada H2) kullanacağını belirtir;
JPA ile ilgili başka bir açıklama yoktur. Bu durumda varsayılan değerler kullanılacaktır:
- [Customer] tablosu, sınıfın adını taşıyacaktır, yani [Customer];
- bu tablonun sütunları, sınıfın alanlarının adlarını taşıyacaktır: [id, firstName, lastName]; tablonun sütun adlarında büyük/küçük harf ayrımı yapılmadığını unutmayın;
Kullanılan JPA uygulamasının hiçbir aşamada adının belirtilmediğine dikkat edilmelidir.
11.2.3. [Spring Data] katmanı
[CustomerRepository] sınıfı, [Customer] tablosuna erişim katmanını uygular. Kod şöyledir:
![]() |
![]() |
package hello;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
Dolayısıyla bu bir sınıf değil, bir arayüzdür (7. satır). Spring Data'ya ait [CrudRepository] arayüzünü genişletir (5. satır). Bu arayüz iki türle tanımlanmıştır: ilki, yönetilen öğelerin türü; burada [Customer] türüdür; ikincisi ise yönetilen öğelerin birincil anahtarının türü; burada [Long] türüdür. [CrudRepository] arayüzü şöyledir:
package org.springframework.data.repository;
import java.io.Serializable;
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> save(Iterable<S> entities);
T findOne(ID id);
boolean exists(ID id);
Iterable<T> findAll();
Iterable<T> findAll(Iterable<ID> ids);
long count();
void delete(ID id);
void delete(T entity);
void delete(Iterable<? extends T> entities);
void deleteAll();
}
Bu arayüz, JPA T türü üzerinde gerçekleştirilebilecek CRUD işlemlerini (Oluştur – Oku – Güncelle – Sil) tanımlar:
- 8. satır: save yöntemi, bir T varlığının veritabanında kalıcı hale getirilmesini sağlar. Bu yöntem, varlığı SGBD tarafından verilen birincil anahtarla kalıcı hale getirir. Ayrıca, id birincil anahtarıyla tanımlanan bir T varlığının güncellenmesini de sağlar. Hangi işlemin gerçekleştirileceği, id birincil anahtarının değerine göre belirlenir: bu değer null ise kalıcılık işlemi gerçekleştirilir, aksi takdirde güncelleme işlemi gerçekleştirilir;
- 10. satır: Aynı işlem, ancak bu kez bir varlık listesi için;
- 12. satır: findOne yöntemi, birincil anahtarı id ile tanımlanan bir T varlığını bulmaya olanak tanır;
- 22. satır: delete yöntemi, birincil anahtarı id ile tanımlanan bir T varlığını silmeye yarar;
- satır 24-28: [delete] yönteminin varyantları;
- 16. satır: [findAll] yöntemi, kalıcı T varlıklarının tümünü bulmaya olanak tanır;
- 18. satır: Aynı işlem, ancak kimlik listesi aktarılan varlıklarla sınırlıdır;
[CustomerRepository] arayüzüne geri dönelim:
package hello;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
- 9. satır, [Customer]'i [lastName] adıyla bulmaya olanak tanır;
[DAO] katmanı ile ilgili bilgiler bu kadar. Önceki arayüzün bir uygulama sınıfı yoktur. Bu sınıf, yürütme sırasında [Spring Data] tarafından oluşturulur. [CrudRepository] arayüzünün yöntemleri otomatik olarak uygulanır. [CustomerRepository] arayüzüne eklenen yöntemler için ise durum değişebilir. [Customer] tanımına geri dönelim:
private long id;
private String firstName;
private String lastName;
- satırdaki yöntem, [Customer]'in [lastName] (3. satır) alanına referans verdiği için [Spring Data] tarafından otomatik olarak uygulanır. Spring Data, uygulanacak arayüzde bir [findBySomething] yöntemi ile karşılaştığında, bunu aşağıdaki JPQL (Java Persistence Query Language) sorgusu ile uygular:
Bu nedenle, T türünün [something] adlı bir alanı olması gerekir. Böylece
yöntemi, aşağıdakine benzer bir kodla uygulanacaktır:
return [em].createQuery("select c from Customer c where c.lastName=:value").setParameter("value",lastName).getResultList()
burada [em], JPA kalıcılık bağlamını ifade eder. Bu, ancak [Customer] sınıfında [lastName] adlı bir alan varsa mümkündür; ki bu durum söz konusudur.
Sonuç olarak, basit durumlarda Spring Data, [DAO] katmanını basit bir arayüzle uygulamamıza olanak tanır.
11.2.4. [console] katmanı
![]() |
![]() |
[Application] sınıfı şu şekildedir:
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application implements CommandLineRunner {
@Autowired
CustomerRepository repository;
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@Override
public void run(String... strings) throws Exception {
// birkaç müşteriyi kaydet
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));
// tüm müşterileri getir
System.out.println("Customers found with findAll():");
System.out.println("-------------------------------");
for (Customer customer : repository.findAll()) {
System.out.println(customer);
}
System.out.println();
// ID ile tek bir müşteriyi getir
Customer customer = repository.findOne(1L);
System.out.println("Customer found with findOne(1L):");
System.out.println("--------------------------------");
System.out.println(customer);
System.out.println();
// müşterileri soyadına göre getir
System.out.println("Customer found with findByLastName('Bauer'):");
System.out.println("--------------------------------------------");
for (Customer bauer : repository.findByLastName("Bauer")) {
System.out.println(bauer);
}
}
}
- 9. satır: Sınıf, [CommandLineRunner] arayüzünü uygular; bu arayüz, [Spring Boot] (4. satır) arayüzüdür. Bu arayüzün tek bir yöntemi vardır, o da 19. satırdaki yöntemdir;
- 8. satır: @SpringBootApplication, birkaç [Spring Boot] anotasyonunu bir araya getiren bir anotasyondur:
- @Configuration: sınıfın bir yapılandırma sınıfı olduğunu belirtir;
- @EnableAutoConfiguration: [Spring Boot]'ten, çeşitli özelliklere, özellikle de projenin Classpath içeriğine bağlı olarak belirli sayıda bean'i kendisi oluşturmasını ister. Hibernate kütüphaneleri Classpath'te bulunduğu için, [entityManagerFactory] bean'i Hibernate ile uygulanacaktır. SGBD kütüphanesi Classpath'te bulunduğu için, H2 bean'i H2 ile uygulanacaktır. [dataSource] bean'inde kullanıcı adı ve şifre de tanımlanmalıdır. Burada Spring Boot, H2'in varsayılan yöneticisini kullanacaktır; bu, şifresizdir. [spring-tx] kütüphanesi Classpath'te bulunduğu için Spring'in işlem yöneticisi kullanılacaktır;
- @EnableWebMvc: Classpath'te [spring-mvc] kütüphanesi varsa. Bu durumda, web uygulaması için otomatik bir yapılandırma yapılır;
- @ComponentScan: Spring'e diğer bean'leri, yapılandırmaları ve hizmetleri nerede arayacağını bildirir. Burada bunlar varsayılan olarak etiketli sınıfın bulunduğu pakette, yani [hello] paketinde aranır. Böylece [Customer] ve [CustomerRepository] sınıfları bulunacaktır. İlki [@Entity] anotasyonuna sahip olduğu için Hibernate tarafından yönetilecek bir varlık olarak kataloglanacaktır. İkincisi ise [CrudRepository] arayüzünü genişlettiği için bir Spring bean'i olarak kaydedilecektir;
- 11-12. satırlar: [CustomerRepository] bean'i ana sınıfın koduna enjekte edilir;
- 15. satır: Spring Boot projesindeki [SpringApplication] sınıfının [run] statik yöntemi yürütülür. Bu yöntemin parametresi, [Configuration] veya [EnableAutoConfiguration] anotasyonuna sahip sınıftır. Daha önce açıklanan tüm süreç bu aşamada gerçekleşir. Sonuç, bir Spring uygulama bağlamıdır, yani Spring tarafından yönetilen bir dizi bean;
Aşağıdaki işlemler, yalnızca [CustomerRepository] arayüzünü uygulayan bean'in yöntemlerini kullanır. Konsol çıktıları şöyledir:
- 1-8. satırlar: Spring Boot projesinin logosu;
- 9. satır: [hello.Application] sınıfı çalıştırılır;
- 10. satır: [AnnotationConfigApplicationContext], Spring'in [ApplicationContext] arayüzünü uygulayan bir sınıftır. Bu, bir bean konteyneridir;
- satır 11: [entityManagerFactory] bean'i, bir Spring sınıfı olan [LocalContainerEntityManagerFactory] sınıfı ile uygulanır;
- 12. satır: [Hibernate] ortaya çıkıyor. Seçilen, JPA adlı bu uygulamadır;
- 19. satır: SGBD ile birlikte kullanılacak Hibernate lehçesi, SQL varyantıdır. Burada [H2Dialect] lehçesi, Hibernate'in SGBD ve H2 ile çalışacağını göstermektedir;
- 21-22. satırlar: veritabanı oluşturulur. [CUSTOMER] tablosu oluşturulur. Bu, Hibernate'in JPA tanımlarından tabloları oluşturacak şekilde yapılandırıldığı anlamına gelir; burada [Customer] sınıfının JPA tanımı kullanılır;
- 26-30. satırlar: arayüzün [findAll] yönteminin sonucu;
- 34. satır: arayüzün [findOne] yönteminin sonucu;
- 38-39. satırlar: [findByLastName] yönteminin sonuçları;
- 41. satır ve sonrası: Spring bağlamının kapatılmasına ait günlükler.
11.2.5. Spring Data projesinin manuel yapılandırması
Önceki projeyi [gs-accessing-data-jpa-02] projesinde kopyalıyoruz:
![]() |
Bu yeni projede, Spring Boot tarafından yapılan otomatik yapılandırmaya güvenmeyeceğiz. Yapılandırmayı manuel olarak gerçekleştireceğiz. Varsayılan yapılandırmalar bize uymuyorsa bu yararlı olabilir.
Öncelikle, [pom.xml] dosyasında gerekli bağımlılıkları açıkça belirteceğiz:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-accessing-data-jpa-02</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Data -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<!-- H2 Veritabanı -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
</dependencies>
<properties>
<!-- her şey için UTF-8 kullanın -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release</url>
</repository>
<repository>
<id>org.jboss.repository.releases</id>
<name>JBoss Maven Release Repository</name>
<url>https://repository.jboss.org/nexus/content/repositories/releases</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>
- 10-14. satırlar: Tanımladığı kütüphaneleri kullanacağımız üst Maven projesi;
- 18-21. satırlar: Veritabanına erişmek için kullanılan Spring Data;
- 23-26. satırlar: JPA spesifikasyonunun Hibernate uygulaması;
- 28-31. satırlar: SGBD ve H2;
- 33-36. satırlar: Veritabanları genellikle, bağlantıların tekrar tekrar açılıp kapatılmasını önleyen açık bağlantı havuzlarıyla kullanılır. Burada kullanılan uygulama [tomcat-jdbc]’tir;
Yeni projede, [Customer] varlığı ve [CustomerRepository] arayüzü değişmez. [Application] sınıfını değiştireceğiz; bu sınıf iki sınıfa bölünecektir:
- [Config], yapılandırma sınıfı olacak;
- [Main], bu da çalıştırılabilir sınıf olacaktır;
![]() |
Çalıştırılabilir sınıf [Application] artık şu şekildedir:
package console;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import repositories.CustomerRepository;
import config.AppConfig;
import entities.Customer;
public class Application {
public static void main(String[] args) {
// Spring bağlamı oluşturma
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
CustomerRepository repository = context.getBean(CustomerRepository.class);
// birkaç müşteriyi kaydetme
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));
...
// bağlamın kapatılması
context.close();
}
}
- 9. satır: [Application] sınıfında artık yapılandırma açıklamaları bulunmamaktadır;
- 3-7. satırlar: [Spring Boot] paketlerinin artık içe aktarılmadığına dikkat edin;
- 12. satır: Spring bean'leri oluşturulur. Bu şekilde oluşturulan bean'lerin referansını içeren Spring bağlamı elde edilir;
- 13. satır: [CustomerRepository] türündeki bean'e bir referans talep edilir;
Projeyi yapılandıran [Config] sınıfı aşağıdaki gibidir:
package config;
import javax.persistence.EntityManagerFactory;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
//@EnableTransactionManagement
@EnableJpaRepositories(basePackages = { "repositories" })
@Configuration
// @ComponentScan(basePackages={"package1","package2"})
public class AppConfig {
// veritabanı H2
@Bean
public DataSource dataSource() {
// veri kaynağı TomcatJdbc
DataSource dataSource = new DataSource();
// erişim yapılandırması JDBC
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:./demo");
dataSource.setUsername("sa");
dataSource.setPassword("");
// başlangıçta açık bir bağlantı
dataSource.setInitialSize(1);
// sonuç
return dataSource;
}
// sağlayıcı JPA
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(false);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.H2);
return hibernateJpaVendorAdapter;
}
// EntityManagerFactory
@Bean
public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(jpaVendorAdapter);
factory.setPackagesToScan("entities");
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
return factory.getObject();
}
// İşlem yöneticisi
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
- 17. satır: [@EnableTransactionManagement] anotasyonu, [CrudRepository] arayüzlerinin yöntemlerinin bir işlem içinde gerçekleştirilmesi gerektiğini belirtir. Bu, varsayılan olarak geçerli olduğu için yorum satırına alınmıştır;
- 18. satır: [@EnableJpaRepositories] anotasyonu, Spring Data [CrudRepository] arayüzlerinin bulunduğu klasörleri belirtmeye yarar. Bu arayüzler Spring bileşenlerine dönüşecek ve Spring bağlamında kullanılabilir hale gelecektir;
- 19. satır: [@Configuration] anotasyonu, [Config] sınıfını bir Spring yapılandırma sınıfı haline getirir;
- 20. satır: [@ComponentScan] anotasyonu, Spring bileşenlerinin aranacağı klasörlerin listelenmesini sağlar. Spring bileşenleri, @Service, @Component, @Controller gibi Spring anotasyonlarıyla etiketlenmiş sınıflardır. Burada, [AppConfig] sınıfı içinde tanımlananlar dışında başka bileşen bulunmadığından, anotasyon yorum satırına alınmıştır;
- 24-37. satırlar: veri kaynağını, yani H2 veritabanını tanımlar. 25. satırdaki @Bean anotasyonu, bu yöntemle oluşturulan nesneyi Spring tarafından yönetilen bir bileşen haline getirir. Yöntemin adı burada herhangi bir şey olabilir. Ancak, 51. satırdaki EntityManagerFactory yoksa ve otomatik yapılandırma ile tanımlanmışsa, bu yöntemin adı [dataSource] olmalıdır;
- 30. satır: Veritabanının adı [demo] olacak ve proje klasöründe oluşturulacaktır;
- satır 40-47: Kullanılan JPA uygulamasını tanımlar; burada bir Hibernate uygulamasıdır. Yöntemin adı burada herhangi bir şey olabilir;
- satır 43: SQL için günlük kaydı yok;
- satır 44: veritabanı mevcut değilse oluşturulacaktır;
- satır 50-58: JPA'in kalıcılığını yönetecek olan EntityManagerFactory'i tanımlar. Yöntemin adı mutlaka [entityManagerFactory] olmalıdır;
- satır 51: Yöntem, daha önce tanımlanan iki bean'in türünde iki parametre alır. Bu bean'ler daha sonra oluşturulur ve Spring tarafından yöntemin parametreleri olarak enjekte edilir;
- satır 53: Kullanılacak JPA uygulaması belirlenir;
- satır 54: JPA varlıklarının bulunacağı klasörleri belirler;
- satır 55: yönetilecek veri kaynağını belirler;
- satır 61-66: işlem yöneticisi. Yöntemin adı mutlaka [transactionManager] olmalıdır. Bu yöntem, satır 51-58'deki bean'i parametre olarak alır;
- 64. satır: işlem yöneticisi, EntityManagerFactory ile ilişkilendirilir;
Yukarıdaki yöntemler herhangi bir sırayla tanımlanabilir.
Projenin çalıştırılması aynı sonuçları verir. Proje klasöründe, H2 adlı yeni bir veritabanı dosyası belirir:
![]() |
11.2.6. Çalıştırılabilir arşiv oluşturma
Projenin çalıştırılabilir arşivini oluşturmak için şu adımları uygulayabilirsiniz:
![]() |
- [1]'te: bir yürütme yapılandırması oluşturulur;
- [2]'te: [Java Application] türünde
- [3]: çalıştırılacak projeyi belirtir (Browse düğmesini kullanın);
- [4]'te: yürütülecek sınıfı belirtir;
- [5]: çalıştırma yapılandırmasının adı – herhangi bir isim olabilir;
![]() |
- [6]'te: proje dışa aktarılır;
- [7]'te: çalıştırılabilir bir arşiv JAR biçiminde;
- [8]: Oluşturulacak yürütülebilir dosyanın yolunu ve adını belirtir;
- [9]'te: [5]'te oluşturulan çalıştırma yapılandırmasının adı;
![]() |
- [10]'te, oluşturulan arşiv;
Bu işlem tamamlandıktan sonra, yürütülebilir arşivin bulunduğu klasörde bir konsol açılır:
Arşiv şu şekilde çalıştırılır:
.....\dist>java -jar gs-accessing-data-jpa-02.jar
Konsolda elde edilen sonuçlar şunlardır:
11.3. Örnek 2
11.3.1. Giriş
API JDBC'i tanıtmak için kullandığımız ürün tablosu örneğini tekrar ele alacağız ve aşağıdaki mimariyi oluşturacağız:
![]() |
[dbintrospringjpa] veritabanında [PRODUITS] ve [CATEGORIES] adlı iki tablo bulunmaktadır. [CATEGORIES] tablosu şu şekildedir:
![]() |
- [ID]: AUTO_INCREMENT modunda birincil anahtar;
- [VERSION]: kaydın sürüm numarası;
- [NOM]: kategori adı - benzersiz;
[PRODUITS] tablosu aşağıdaki gibidir:
![]() |
- [ID]: AUTO_INCREMENT modunda birincil anahtar;
- [VERSION]: kaydın sürüm numarası;
- [NOM]: ürün adı - benzersiz;
- [ID_CATEGORIE]: kategorisi numarası - [CATEGORIES.ID] alanındaki yabancı anahtar;
- [PRIX]: fiyatı;
- [DESCRIPTION]: ürün açıklaması;
Yapılması gereken iş: Destekteki SQL ve [dbintrospringdata.sql] komut dosyalarını kullanarak [dbintrospringdata] veritabanını oluşturun:
11.3.2. Maven projesinin oluşturulması
Bir Spring Data proje iskeleti oluşturmak için aşağıdaki adımları uygulayabilirsiniz:
![]() |
- [1]'te yeni bir proje oluşturulur;
- [2]'te: [Spring Starter Project] türünde;
- oluşturulan proje bir Maven projesi olacaktır. [3]'te, proje grubunun adını belirtin;
- [4]'te: projenin derlenmesi sırasında oluşturulacak artefaktın (burada bir jar dosyası) adını belirtin;
- [5]'te: projenin Eclipse adı – herhangi bir ad olabilir ([4] ile aynı olması gerekmez);
- [7]'te: [JPA] katmanına sahip ve SGBD ile MySQL'i içeren bir proje oluşturulacağı belirtilir. Böyle bir proje için gerekli bağımlılıklar daha sonra [pom.xml] dosyasına eklenecektir;
![]() |
- [8] dosyasına, proje klasörünün adını girin;
- [9] dosyasına, sihirbazı tamamlayın;
![]() |
- [10]: oluşturulan proje;
[pom.xml] dosyası, bir proje için gerekli bağımlılıkları içerir JPA:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.springdata</groupId>
<artifactId>intro-spring-data-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>intro-spring-data-01</name>
<description>démo spring data avec table de produits</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.2.RELEASE</version>
<relativePath/> <!-- depo'dan üst öğeyi ara -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.IntroSpringData01Application</start-class>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 14-19. satırlar: üst Maven projesi – çok sayıda kütüphaneyi sürümleriyle birlikte tanımlar – bu kütüphaneler, sürümleri belirtilmeden Maven bağımlılıkları olarak kullanılır;
- 28-31. satırlar: JPA için gerekli bağımlılık – [Spring Data]'i içerecektir;
- satır 32-36: MySQL'in JDBC sürücüsüne olan bağımlılığı;
- satır 37-41: Spring ile entegre JUnit testleri için gerekli bağımlılıklar;
Çalıştırılabilir sınıf [Application] hiçbir işlem yapmaz ancak önceden yapılandırılmıştır:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class IntroSpringData01Application {
public static void main(String[] args) {
SpringApplication.run(IntroSpringData01Application.class, args);
}
}
- [@SpringBootApplication] anotasyonu, sınıfı projenin otomatik yapılandırma sınıfı haline getirir;
[ApplicationTests] test sınıfı hiçbir işlem yapmaz, ancak önceden yapılandırılmıştır:
package demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = IntroSpringData01Application.class)
public class IntroSpringData01ApplicationTests {
@Test
public void contextLoads() {
}
}
- 9. satır: [@SpringApplicationConfiguration] anotasyonu, [Application] yapılandırma dosyasının kullanılmasını sağlar. Böylece test sınıfı, bu dosyada tanımlanacak tüm bean'lerden yararlanacaktır;
- 8. satır: [@RunWith] anotasyonu, Spring'in JUnit ile entegrasyonunu sağlar: sınıf, bir JUnit testi olarak çalıştırılabilecektir. [@RunWith], JUnit (4. satır) anotasyonudur; [SpringJUnit4ClassRunner] sınıfı ise bir Spring sınıfıdır (6. satır);
Artık JPA adlı bir uygulama iskeletine sahip olduğumuza göre, bunu tamamlayarak ürün veritabanıyla ilişkili kalıcılık katmanı projesini yazabiliriz.
11.3.3. Eclipse projesi
Önceki projeyi şu şekilde geliştiriyoruz:
![]() |
- [AppConfig.java]: Spring projesinin yapılandırma sınıfı;
- [Main.java]: projenin çalıştırılabilir sınıfı;
- [IDao.java]: [DAO] katmanının arayüzü;
- [Dao.java]: [DAO] katmanının uygulama sınıfı;
- [AbstractEntity.java]: [Produit] ve [Categorie] sınıflarının üst sınıfı;
- [Produit.java]: veritabanındaki [PRODUITS] tablosunun bir satırıyla ilişkili sınıf;
- [Categorie.java]: veritabanındaki [CATEGORIES] tablosunun bir satırıyla ilişkili sınıf;
- [ProduitsRepository]: [PRODUITS] tablosuna erişim sağlayan Spring Data arayüzü;
- [CategoriesRepository]: [CATEGORIES] tablosuna erişim sağlayan Spring Data arayüzü;
- [pom.xml]: Maven projesinin yapılandırma dosyası;
Bu proje aşağıdaki mimariyi uygulamaktadır:
![]() |
[DAO] katmanı, yalnızca [Spring Data] tarafından uygulanan katmanı görür.
11.3.4. Maven Yapılandırması
Maven projesinin [pom.xml] dosyası şöyledir:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.springdata</groupId>
<artifactId>intro-spring-data-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>intro-spring-data-01</name>
<description>démo spring data avec table de produits</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Data -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<!-- MySQL Veritabanı -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<!-- kütüphane jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Google Guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>16.0.1</version>
</dependency>
<!-- Spring Boot Testi -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- günlük kütüphanesi -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
Bu yapılandırma, 11.2.5. paragrafında kullanılan ve açıklanan yapılandırmadır. Buna aşağıdaki kütüphaneleri ekliyoruz:
- 42-49. satırlar: [Produit] sınıfının [toString] yöntemi tarafından kullanılan jSON kütüphanesi;
- 51-55. satırlar: öğe koleksiyonlarını yönetmek için yardımcı yöntemler sağlayan [Google Guava] kütüphanesi. Bu kütüphane, [DAO] katmanını uygulayan [Dao] sınıfı tarafından kullanılacaktır;
- satır 56-67: testler için gerekli kütüphaneler JUnit;
- 69-72. satırlar: bir log kütüphanesi;
- 81-86. satırlar: proje için gerekli Maven eklentileri;
11.3.5. [JPA] katmanındaki varlıklar
Katman
[DAO]
Katman
[console]
Katman
[JPA]
Sürücü
[JDBC]
Katman
[Spring Data]
Spring 4
SGBD
![]() |
11.3.5.1. [AbstractEntity] sınıfı
[AbstractEntity] sınıfı şu şekildedir:
package spring.data.entities;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@MappedSuperclass
public abstract class AbstractEntity {
// özellikler
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
protected Long id;
@Version
@Column(name = "VERSION")
protected Long version;
// yapıcılar
public AbstractEntity() {
}
public AbstractEntity(Long id, Long version) {
this.id = id;
this.version = version;
}
// [equals] ve [hashcode]'in yeniden tanımlanması
@Override
public int hashCode() {
return (id != null ? id.hashCode() : 0);
}
@Override
public boolean equals(Object entity) {
if (!(entity instanceof AbstractEntity)) {
return false;
}
String class1 = this.getClass().getName();
String class2 = entity.getClass().getName();
if (!class2.equals(class1)) {
return false;
}
AbstractEntity other = (AbstractEntity) entity;
return id != null && this.id.longValue() == other.id.longValue();
}
// imza jSON
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
// alıcı ve ayarlayıcılar
....
}
Bu sınıfın amacı, JPA varlıklarına bir üst sınıf sağlamak ve [id, version] (satır 19, 22) özelliklerini tek bir yerde kapsülleyerek [Produit] ve [Categorie] varlıklarına bir ana sınıf sağlamaktır. Bu özellikler, tabloların [ID, VERSION] sütunlarıyla ilişkilidir (satır 18, 21).
- 13. satır: [@MappedSuperclass] açıklaması, sınıfın JPA varlıklarının üst sınıfı olduğunu belirtir;
- 16. satır: [@Id] açıklaması, [id] alanının (başka bir adı da olabilir) bir tablonun birincil anahtarıyla ilişkili olduğunu belirtir;
- 17. satır: [@GeneratedValue(strategy=GenerationType.IDENTITY)] açıklaması, birincil anahtarların oluşturulma şeklini belirler. [GenerationType.IDENTITY] modu, MySQL ile birlikte [AUTO_INCREMENT] modunu kullanacaktır. Başka bir SGBD ile bu mod, farklı bir yöntem kullanır. Bunun avantajı, geliştiricinin bu konuyla ilgilenmesine gerek kalmaması ve kullanılan SGBD ne olursa olsun kodunun geçerliliğini korumasıdır;
- 18. satır: [@Column] açıklaması, alanla ilişkili sütunu belirtir. Bu açıklama mevcut olmadığında, JPA, sütunun alanla aynı ada sahip olduğunu varsayar. Burada durum böyledir. Dolayısıyla bu açıklamayı eklememek de mümkündü;
- satır 20: [@Version] açıklaması, [version] alanının bir sürümleme sütunuyla ilişkili olduğunu belirtir. JPA uygulaması, varlık her değiştirildiğinde bu sürüm numarasını artıracaktır. Bu numara, varlığın iki farklı kullanıcı tarafından aynı anda güncellenmesini önlemek için kullanılır: U1 ve U2 adlı iki kullanıcı, sürüm numarası V1 olan E varlığını okur. U1, E'yi değiştirir ve bu değişikliği veritabanına kaydeder: sürüm numarası bu durumda V1+1'e geçer. U2 da E'yi değiştirir ve bu değişikliği veritabanına kaydeder: veritabanındaki sürümden (V1+1) farklı bir sürüme (V1) sahip olduğu için bir istisna alır;
- 35-52. satırlar: [hashCode] ve [equals] yöntemlerinin yeniden tanımlanması. Varsayılan olarak, [obj1.equals(obj2)], [obj1==obj2] doğruysa, yani ob1 ve obj2 iki eşit işaretçi ise true değerini alır. İşaretçilerin kendileri yerine işaret ettikleri nesneleri karşılaştırmak istenirse, [equals] ve [hashCode] yöntemlerini yeniden tanımlamak gerekir. Bu yöntem, [equals] yönteminin eşit olduğunu belirten iki nesne için aynı değeri döndürmelidir;
- 42-51. satırlar: [AbstractEntity] türünde veya türevleri olan iki nesne, [id] birincil anahtarları eşitse eşit kabul edilir;
- satır 35-38: [hashCode] yöntemi, aynı olan ve dolayısıyla aynı [id] birincil anahtarına sahip iki [AbstractEntity] nesnesi için gerçekten de aynı değeri döndürür;
- satır 55-63: [toString] yöntemi, [this] nesnesinin jSon dizesini döndürür. Bu nesne bir alt sınıfı işaret ediyorsa, bu yöntem alt sınıfın jSON dizesini döndürür. Bu sayede alt sınıflarda [toString] yöntemini oluşturmamıza gerek kalmaz;
11.3.5.2. JPA [Produit] varlığı
[Produit] sınıfı, [PRODUITS] tablosundaki bir satıra bağlı bir JPA varlığıdır:
![]() |
package spring.data.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonFilter;
@Entity
@Table(name = "PRODUITS")
@JsonFilter("jsonFilterProduit")
public class Produit extends AbstractEntity {
// özellikler
@Column(name = "NOM")
private String nom;
@Column(name = "CATEGORIE_ID", insertable = false, updatable = false)
private Long idCategorie;
@Column(name = "PRIX")
private double prix;
@Column(name = "DESCRIPTION")
private String description;
// kategori
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CATEGORIE_ID")
private Categorie categorie;
// yapıcılar
public Produit() {
}
public Produit(String nom, double prix, String description) {
this.nom = nom;
this.prix = prix;
this.description = description;
}
// getter ve setter'lar
...
}
- 12. satır: [@Entity] açıklaması, [Produit] sınıfını [JPA] katmanı tarafından yönetilen bir varlık haline getirir;
- 13. satır: [@Table(name = "PRODUITS")] açıklaması, [Produit] sınıfının, veritabanındaki [PRODUITS] tablosundaki bir satırın nesne görüntüsü olduğunu belirtir;
- satır 14: varlık üzerine uygulanacak jSON adlı filtre. 13. satırdaki [categorie] özelliğinin her zaman mevcut olmadığını göreceğiz. Bu durumda, nesnenin jSON temsilinden bu özelliği hariç tutmamız gerekir. Bunun için bir filtreye ihtiyacımız var. Dolayısıyla, [jsonFilterCategorie] adlı filtrede, [categorie] özelliğini dahil etmek isteyip istemediğimizi belirleyeceğiz;
- 18. satır: [@Column] açıklaması, [nom] alanını [PRODUITS] tablosunun [NOM] sütunuyla ilişkilendirir. Alan, ilişkili sütunla aynı adı taşıyorsa, [@Column] açıklaması atlanabilir. Bu durumda da öyle olur;
- 31-33. satırlar: ürün kategorisi;
- 31. satır: [@ManyToOne] açıklaması, 32. satırdaki [@JoinColumn(name = "CATEGORIE_ID")] açıklamasının sütununun, 33. satırdaki varlığa ait [CATEGORIES] tablosundaki[Produit] varlığının, 33. satırdaki varlıkla ilişkili [CATEGORIES] tablosundaki yabancı anahtarıdır. Bu açıklama, bir JPA varlığını açıklamalıdır. Dolayısıyla 33. satırdaki sınıf, bir JPA varlığı olmalıdır;
- 31. satır: [fetch = FetchType.LAZY] açıklaması, [PRODUITS] tablosundan bir ürün getirildiğinde, ürünün kategorisinin (33. satır) hemen getirilmemesini (gecikmeli yükleme) talep eder. Kategori, [getCategorie] yöntemine ilk çağrı yapıldığında alınır. Bu öznitelik zorunlu değildir. Kullanılan JPA uygulaması bu özniteliği göz ardı etme hakkına sahiptir. [categorie] özelliğinin mevcut olabileceği veya olmayabileceği için 14. satırdaki jSON filtresini ekledik. Mevcut JPA uygulamaları (Hibernate, Eclipselink, OpenJPA) bu anotasyonu aynı şekilde yönetmez. Hibernate, kategoriyi almak için SGBD'e bir çağrı yaparak, başlangıçtaki [getCategorie] yöntemini (sadece categorie alanını döndüren) zenginleştirir. Bunun mümkün olabilmesi için, ürünü almak üzere başlangıçta kullanılan SGBD bağlantısının hâlâ açık olması gerekir; aksi takdirde bir istisna oluşur.
11.3.5.3. JPA ve [Categorie] varlıkları
[Categorie] sınıfı, [CATEGORIES] tablosundaki bir satıra bağlı bir JPA varlığıdır:
![]() |
Kodu şöyledir:
package spring.data.entities;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonFilter;
@Entity
@Table(name = "CATEGORIES")
@JsonFilter("jsonFilterCategorie")
public class Categorie extends AbstractEntity {
// özellikler
@Column(name = "NOM")
private String nom;
// ilgili ürünler
@OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL })
public Set<Produit> produits = new HashSet<Produit>();
// yapıcılar
public Categorie() {
}
public Categorie(String nom) {
this.nom = nom;
}
// yöntemler
public void addProduit(Produit produit) {
// ürünü ekliyoruz
produits.add(produit);
// kategorisi belirlenir
produit.setCategorie(this);
}
// getter ve setter'lar
...
}
- 21-22. satırlar: kategorinin adı;
- 25-26. satırlar: bu kategorideki ürünler;
- 25. satır: [@OneToMany] notu, [Produit] varlığında karşılaştığımız [@ManyToOne] ilişkisinin ters ilişkisidir. [mappedBy = "categorie"] özniteliği, [@ManyToOne] ters ilişkisiyle etiketlenen [Produit] varlığının alanını belirtir. [cascade = { CascadeType.ALL }] özniteliği, bir @Entity [Categorie] üzerinde yapılan işlemlerin (persist, merge, remove) 26. satırdaki [produits]'lere kaskad olarak uygulanmasını gerektirir. [CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE] sabitleri kullanılarak kısmi zincirleme işlemler belirtilebilir;
- 25. satır: [fetch = FetchType.LAZY] özniteliği, [CATEGORIES] tablosundan bir kategori getirildiğinde, bu kategorinin ürünlerinin hemen getirilmemesini talep eder. Ürünler, [getProduits] yöntemine ilk çağrıldığında getirilecektir. Mevcut JPA uygulamaları (Hibernate, Eclipselink, OpenJPA) bu anotasyonu aynı şekilde yönetmemektedir. Hibernate, başlangıçtaki [getProduits] yöntemini (sadece produits alanını döndüren) zenginleştirerek, kategorideki ürünleri almak için SGBD yöntemini çağırır. Bunun mümkün olabilmesi için, kategoriyi almak üzere başlangıçta kullanılan SGBD bağlantısının hâlâ açık olması gerekir. Bu özellik zorunludur. JPA uygulaması bunu göz ardı edemez. [produits] özelliği başlatılmış olabilir veya olmayabilir; bu nedenle, 17. satırdaki jSON filtresini ekledik; bu filtre, bu özelliği isteyip istemediğimizi belirtmemizi sağlayacaktır;
- 26. satır: [Set] türü bir arayüzdür. [HashSet] türü ise bu arayüzü uygulayan bir sınıftır. Bu sınıf, ensemble adlı bir eleman koleksiyonunu uygular. Bir küme, iki özdeş nesne içerebilir. Burada nesneler [Produit] türündedir. Dolayısıyla, küme içinde iki özdeş nesne bulunamaz. Üst sınıf olan [AbstractEntity]'in [equals] yöntemi, aynı birincil anahtara sahip iki ürünün aynı olduğu şeklinde yeniden tanımlandığından, [produits] alanı aynı birincil anahtara sahip iki ürünü içerebilir;
- satır 38-43: [addProduit] yöntemi, bir ürünü kategoriye eklemeye olanak tanır;
11.3.6. [Spring Data] katmanı
Katman
[DAO]
Katman
[console]
Katman
[JPA]
Sürücü
[JDBC]
Katman
[Spring Data]
Yay 4
SGBD
![]() |
[CategoriesRepository] arayüzü, [CATEGORIES] tablosuna erişimi yönetir:
package spring.data.repositories;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import spring.data.entities.Categorie;
public interface CategoriesRepository extends CrudRepository<Categorie, Long> {
// ürünleriyle birlikte kategori
@Query("select c from Categorie c left join fetch c.produits p where c.id=?1")
public Categorie getCategorieByIdWithProduits(Long id);
@Query("select c from Categorie c left join fetch c.produits p where c.nom=?1")
public Categorie getCategorieByNameWithProduits(String nom);
// adıyla belirtilen, ürünleri olmayan bir kategori
public Categorie findByNom(String nom);
}
- 8. satır: [CrudRepository] arayüzü, 11.2.3. paragrafında kullanılmış ve açıklanmıştır. Şunu hatırlatmak isteriz ki:
- arayüzün ilk türü, CRUD (findOne, findAll, kaydet, sil, deleteAll) erişimleri için yönetilen JPA varlığıdır;
- ikinci tür ise JPA varlığının birincil anahtarıdır; burada bir tamsayı olan [Long];
- 12. satır: 12. satırdaki yöntem, 11. satırdaki JPQL (Java Persistence Query Language) sorgusu ile uygulanır. Bu sorgu, JPA varlıklarını sorgular. Böyle bir sorguda:
- tablolar, bunlara karşılık gelen JPA varlıklarıyla değiştirilir;
- sütunlar, sorguda kullanılan JPA varlıklarının alanlarıyla değiştirilir;
- 11. satır: JPQL sorgusu, bir kategoriyi ürünleriyle birlikte döndürür. [Categorie] varlığında, [produits] alanının [fetch = FetchType.LAZY] (gecikmeli yükleme) özelliğine sahip olduğunu hatırlayalım. JPQL sorgusunda, [fetch] anahtar sözcüğüyle ürünlerin yüklenmesi zorlanmaktadır. Sorgudaki ?1 parametresi, yürütme sırasında 12. satırdaki yöntemin ilk parametresinin değeriyle, yani [Long id] parametresiyle değiştirilecektir;
- 14-15. satırlar: adıyla tanımlanan bir kategori için benzer bir yöntem;
- 18. satır: [findByNom] yöntemi, [Category] türünün bir [nom] alanı olduğu için [Spring Data] tarafından otomatik olarak uygulanacaktır;
[ProduitsRepository] arayüzü, [PRODUITS] tablosuna erişimi yönetir:
package spring.data.repositories;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import spring.data.entities.Produit;
public interface ProduitsRepository extends CrudRepository<Produit, Long> {
// kategorisi olan bir ürün
@Query("select p from Produit p left join fetch p.categorie c where p.id=?1")
public Produit getProduitByIdWithCategorie(Long id);
@Query("select p from Produit p left join fetch p.categorie c where p.nom=?1")
public Produit getProduitByNameWithCategorie(String nom);
// adıyla belirtilen, kategorisi olmayan bir ürün
public Produit findByNom(String nom);
}
Açıklamalar, [CategoriesRepository] arayüzü için verilenlerle aynıdır.
Bu arayüzler, projenin yürütülmesi sırasında [Spring Data] tarafından oluşturulan sınıflar tarafından uygulanacaktır. Bu tür sınıflara [proxy] denir. Varsayılan olarak, uygulama sınıfının yöntemleri bir işlem içinde yürütülür. Bu arayüzlerin [CrudRepository] sınıfını genişletmesi, onları Spring bileşenleri haline getirir.
11.3.7. [DAO] katmanı
Katman
[DAO]
Katman
[console]
Katman
[JPA]
Sürücü
[JDBC]
Yatak
[Spring Data]
Yay 4
SGBD
![]() |
[DAO] katmanının [IDao] arayüzü şöyledir:
package spring.data.dao;
import java.util.List;
import spring.data.entities.Categorie;
import spring.data.entities.Produit;
public interface IDao {
// ürün listesinin eklenmesi
public List<Produit> addProduits(List<Produit> produits);
// tüm ürünlerin silinmesi
public void deleteAllProduits();
// bir ürün listesinin güncellenmesi
public List<Produit> updateProduits(List<Produit> produits);
// tüm ürünlerin alınması
public List<Produit> getAllProduits();
// kategori listesi ekleme
public List<Categorie> addCategories(List<Categorie> categories);
// tüm kategorilerin silinmesi
public void deleteAllCategories();
// kategori listesinin güncellenmesi
public List<Categorie> updateCategories(List<Categorie> categories);
// tüm kategorileri alma
public List<Categorie> getAllCategories();
// belirli bir ürünü, kategorisi olsun ya da olmasın
public Produit getProduitByIdWithoutCategorie(Long idProduit);
public Produit getProduitByIdWithCategorie(Long idProduit);
public Produit getProduitByNameWithCategorie(String nom);
public Produit getProduitByNameWithoutCategorie(String nom);
// belirli bir kategoriyi, ürünleriyle birlikte veya ürünsüz olarak alma
public Categorie getCategorieByIdWithoutProduits(Long idCategorie);
public Categorie getCategorieByIdWithProduits(Long idCategorie);
public Categorie getCategorieByNameWithProduits(String nom);
public Categorie getCategorieByNameWithoutProduits(String nom);
}
Burada, giriş parametreleri olarak aldığı nesneleri değiştiren her yöntemin, bunları sonuç olarak geri vermesi gerektiği kuralı benimsenmiştir. Bu kuralın nedeni 4.2. paragrafta açıklanmıştır: bu kural, bir katmanın ve müşterisinin iki ayrı JVM içinde bulunmasına ve dolayısıyla istemci/sunucu olarak çalışmasına olanak tanır.
Bu arayüzün [Dao] uygulaması şu şekildedir:
package spring.data.dao;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.common.collect.Lists;
import spring.data.entities.Categorie;
import spring.data.entities.Produit;
import spring.data.repositories.CategoriesRepository;
import spring.data.repositories.ProduitsRepository;
@Component
public class Dao implements IDao {
@Autowired
private ProduitsRepository produitsRepository;
@Autowired
private CategoriesRepository categoriesRepository;
@Override
public List<Produit> addProduits(List<Produit> produits) {
try {
return Lists.newArrayList(produitsRepository.save(produits));
} catch (Exception e) {
throw new DaoException(101, getMessagesForException(e));
}
}
@Override
public void deleteAllProduits() {
try {
produitsRepository.deleteAll();
} catch (Exception e) {
throw new DaoException(102, getMessagesForException(e));
}
}
@Override
public List<Produit> updateProduits(List<Produit> produits) {
try {
return Lists.newArrayList(produitsRepository.save(produits));
} catch (Exception e) {
throw new DaoException(103, getMessagesForException(e));
}
}
@Override
public List<Categorie> addCategories(List<Categorie> categories) {
try {
return Lists.newArrayList(categoriesRepository.save(categories));
} catch (Exception e) {
throw new DaoException(104, getMessagesForException(e));
}
}
@Override
public void deleteAllCategories() {
try {
categoriesRepository.deleteAll();
} catch (Exception e) {
throw new DaoException(105, getMessagesForException(e));
}
}
@Override
public List<Categorie> updateCategories(List<Categorie> categories) {
try {
return Lists.newArrayList(categoriesRepository.save(categories));
} catch (Exception e) {
throw new DaoException(106, getMessagesForException(e));
}
}
@Override
public List<Categorie> getAllCategories() {
try {
return Lists.newArrayList(categoriesRepository.findAll());
} catch (Exception e) {
throw new DaoException(107, getMessagesForException(e));
}
}
@Override
public List<Produit> getAllProduits() {
try {
return Lists.newArrayList(produitsRepository.findAll());
} catch (Exception e) {
throw new DaoException(108, getMessagesForException(e));
}
}
@Override
public Produit getProduitByIdWithCategorie(Long idProduit) {
try {
return produitsRepository.getProduitByIdWithCategorie(idProduit);
} catch (Exception e) {
throw new DaoException(109, getMessagesForException(e));
}
}
@Override
public Categorie getCategorieByIdWithProduits(Long idCategorie) {
try {
return categoriesRepository.getCategorieByIdWithProduits(idCategorie);
} catch (Exception e) {
throw new DaoException(110, getMessagesForException(e));
}
}
@Override
public Categorie getCategorieByNameWithProduits(String nom) {
try {
return categoriesRepository.getCategorieByNameWithProduits(nom);
} catch (Exception e) {
throw new DaoException(111, getMessagesForException(e));
}
}
@Override
public Produit getProduitByNameWithCategorie(String nom) {
try {
return produitsRepository.getProduitByNameWithCategorie(nom);
} catch (Exception e) {
throw new DaoException(112, getMessagesForException(e));
}
}
@Override
public Produit getProduitByIdWithoutCategorie(Long idProduit) {
try {
return produitsRepository.findOne(idProduit);
} catch (Exception e) {
throw new DaoException(113, getMessagesForException(e));
}
}
@Override
public Categorie getCategorieByIdWithoutProduits(Long idCategorie) {
try {
return categoriesRepository.findOne(idCategorie);
} catch (Exception e) {
throw new DaoException(114, getMessagesForException(e));
}
}
@Override
public Produit getProduitByNameWithoutCategorie(String nom) {
try {
return produitsRepository.findByNom(nom);
} catch (Exception e) {
throw new DaoException(115, getMessagesForException(e));
}
}
@Override
public Categorie getCategorieByNameWithoutProduits(String nom) {
try {
return categoriesRepository.findByNom(nom);
} catch (Exception e) {
throw new DaoException(116, getMessagesForException(e));
}
}
}
- 16. satır: [@Component] anotasyonu, [Dao] sınıfını bir Spring bileşeni haline getirir;
- 19-23. satırlar: [Spring Data] arayüzünün iki [CrudRepository] arayüzüne referansların enjeksiyonu. Bu enjeksiyon, Spring nesnelerinin oluşturulması sırasında, genellikle Spring projesinin yürütülmesinin başlangıcında gerçekleşecektir;
- 28. ve 46. satırlarda, [produitsRepository] arayüzünün [save] yönteminin hem ürün ekleme hem de güncelleme işlemleri için kullanıldığı görülmektedir. [Spring Data], ürünün birilik anahtarını kullanarak ekleme mi yoksa güncelleme mi yapması gerektiğini belirler. Birilik anahtarı [null] ise ekleme yapılır, aksi takdirde güncelleme yapılır;
- 82. satır: Ürün listesini almak için Guava kütüphanesindeki [Lists.newArrayList] yöntemi kullanılır. [produitsRepository.findAll()] yöntemi, [Iterable<Produit>] türünde bir değer döndürür;
- 28. satır: [produitsRepository.save(produits)] yöntemi, [Iterable<Produit>] türünde bir değer döndürür. Sınıfın diğer [save] işlemleri için de durum aynıdır;
Yukarıdaki [Dao] sınıfında, meydana gelebilecek istisnalar aşağıdaki [DaoException] türünde kapsüllenmiştir:
package spring.data.dao;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// "Seçimler" uygulaması için istisna sınıfı
// istisna denetlenmez
public class DaoException extends RuntimeException implements Serializable {
// seri numarası ID
private static final long serialVersionUID = 1L;
// yerel alanlar
private int code;
private List<String> erreurs;
// oluşturucular
public DaoException() {
super();
}
public DaoException(int code, Throwable e) {
// üst
super(e);
// yerel
this.code = code;
this.erreurs = getErreursForException(e);
}
public DaoException(int code, String message, Throwable e) {
// üst öğe
super(message, e);
// yerel
this.code = code;
this.erreurs = getErreursForException(e);
}
public DaoException(int code, String message) {
// üst
super(message);
// yerel
this.code = code;
List<String> erreurs = new ArrayList<>();
erreurs.add(message);
this.erreurs = erreurs;
}
public DaoException(int code, List<String> erreurs) {
// üst
super();
// yerel
this.code = code;
this.erreurs = erreurs;
}
// bir istisnaya ait hata mesajları listesi
private List<String> getErreursForException(Throwable th) {
// istisnanın hata mesajları listesi alınır
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// mesaj, yalnızca !=null ve boş değilse alınır
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// sonraki neden
cause = cause.getCause();
}
return erreurs;
}
// getter ve setter'lar
...
}
- 10. satır: Sınıf, [RuntimeException] sınıfından türetilmiştir ve bu nedenle kontrol edilemeyen bir istisnadır;
- 16. satır: bir hata kodu;
- satır 17: [DaoException]'i tetikleyen istisna yığınıyla ilişkili hata mesajlarının listesi;
- satır 59-76: özel [getMessagesForException] yöntemi, istisna yığınına ait istisnalarla ilişkili hata mesajlarının listesini almayı sağlar. Aslında, Exception sınıfının aşağıdaki oluşturucuları kullanılarak istisnalar yığınlanabilir:
- Exception(String message, Throwable cause): bir mesaj ve kapsanmak istenen istisna ile bir istisna oluşturur;
- Exception(Throwable cause): kapsanmasını istediğimiz istisna ile bir istisna oluşturur;
[Throwable] türü, [Exception] sınıfının üst sınıfıdır. Yukarıdaki yapıcılar tekrar tekrar çalıştırılırsa, son istisna birden fazla istisna içerir. Buna istisna yığını denir.
- e1 istisnasının son nedeni, [e1.getCause()] ifadesiyle elde edilir;
- e1 istisnasının sondan bir önceki nedeni, [e1.getCause().getCause()] ifadesiyle elde edilir;
- bu şekilde [getCause()==null] elde edilene kadar devam edilir;
11.3.8. Spring projesinin yapılandırılması
![]() |
[DaoConfig] sınıfı, [DAO] katmanını yapılandırır:
package spring.data.config;
import javax.persistence.EntityManagerFactory;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
@EnableJpaRepositories(basePackages = { "spring.data.repositories" })
@Configuration
@ComponentScan(basePackages = { "spring.data.dao" })
public class DaoConfig {
// sabitler
final static String URL = "jdbc:mysql://localhost:3306/dbIntroSpringData";
final static String USER = "root";
final static String PASSWD = "";
final static String DRIVER_CLASSNAME = "com.mysql.jdbc.Driver";
final static String[] ENTITIES_PACKAGES = { "spring.data.entities" };
// veri kaynağı [tomcat-jdbc]
@Bean
public DataSource dataSource() {
// veri kaynağı TomcatJdbc
DataSource dataSource = new DataSource();
// erişim yapılandırması JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// başlangıçta açık bir bağlantı
dataSource.setInitialSize(1);
// sonuç
return dataSource;
}
// sağlayıcı JPA
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(false);
hibernateJpaVendorAdapter.setDatabase(Database.MYSQL);
return hibernateJpaVendorAdapter;
}
// EntityManagerFactory
@Bean
public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(jpaVendorAdapter);
factory.setPackagesToScan(packagesToScan());
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
return factory.getObject();
}
// İşlem yöneticisi
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
@Bean
public String[] packagesToScan() {
return ENTITIES_PACKAGES;
}
}
Benzer bir yapılandırma 11.2.5. paragrafında ele alınmış ve açıklanmıştır. Buraya aşağıdaki Spring anotasyonlarını ekledik:
- 17. satır: [@EnableJpaRepositories] anotasyonu, [CrudRepository] ve [Spring Data] arayüzlerinin bulunduğu paketleri belirtmek için kullanılır;
- 18. satır: Sınıf, bir Spring yapılandırma sınıfıdır. Bu bilgi önemlidir. Bu not kaldırılsa bile proje çalışır. Ancak belgenin ilerleyen kısımlarında, bu projeyi temel alan projeler oluşturulduğunda, 18. satırdaki not kaldırılırsa bunlardan bazıları çalışmaz hale gelir;
- 19. satır: [@ComponentScan] anotasyonu, Spring nesnelerinin bulunduğu paketleri belirtir. Bunlar, [@Component, @Service, @Controller, ...] ile anotlanmış sınıflardır. Burada [Dao] adlı Spring bileşeni bulunacak ve örneklenecektir;
- 73-76. satırlar: JPA varlıklarını bulmak için taranacak paketler dizisini temsil eden bir bean tanımladık. Bu, [DaoConfig] sınıfını içe aktaran bir projenin bu bean'i yeniden tanımlamasına ve böylece 59. satırda taranacak paketleri değiştirmesine olanak sağlayacaktır. Belgenin ilerleyen kısımlarında bu sorunla karşılaşacağız;
[AppConfig] sınıfı, projenin tamamını yapılandırır:
package spring.data.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
@Configuration
@Import({DaoConfig.class})
public class AppConfig {
// filtreler jSON
@Bean(name = "jsonMapper")
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
@Bean(name = "jsonMapperCategorieWithProduits")
public ObjectMapper jsonMapperCategorieWithProduits() {
// eşleştirici jSON
ObjectMapper mapper = new ObjectMapper();
// filtreler
mapper.setFilters(
new SimpleFilterProvider().addFilter("jsonFilterCategorie", SimpleBeanPropertyFilter.serializeAllExcept())
.addFilter("jsonFilterProduit", SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
// sonuç
return mapper;
}
@Bean(name = "jsonMapperProduitWithCategorie")
public ObjectMapper jsonMapperProduitWithCategorie() {
// eşleştirici jSON
ObjectMapper mapper = new ObjectMapper();
// filtreler
mapper.setFilters(
new SimpleFilterProvider().addFilter("jsonFilterProduit", SimpleBeanPropertyFilter.serializeAllExcept())
.addFilter("jsonFilterCategorie", SimpleBeanPropertyFilter.serializeAllExcept("produits")));
// sonuç
return mapper;
}
@Bean(name = "jsonMapperCategorieWithoutProduits")
public ObjectMapper jsonMapperCategorieWithoutProduits() {
// eşleştirici jSON
ObjectMapper mapper = new ObjectMapper();
// filtreler
mapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept("produits")));
// sonuç
return mapper;
}
@Bean(name = "jsonMapperProduitWithoutCategorie")
public ObjectMapper jsonMapperProduitWithoutCategorie() {
// eşleştirici jSON
ObjectMapper mapper = new ObjectMapper();
// filtreler
mapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
// sonuç
return mapper;
}
}
- 11. satır: Bu sınıf, bir Spring yapılandırma sınıfıdır;
- 12. satır: az önce incelediğimiz [DaoConfig] sınıfı tarafından tanımlanan bean'leri içe aktarır;
- [console] katmanı, burada tanımlanan jSON eşleştiricilerini kullanır;
- satır 14-64: beş adet jSON eşleştiricisini tanımlar;
- satır 15-18: jSON [jsonMapper] filtresinde filtre bulunmamaktadır;
- satır 20-30: jSON ve [jsonMapperCategorieWithProduits] filtreleri, bir [Categorie] nesnesini ve ürünlerini serileştirmeye/serileştirmeyi kaldırmaya olanak tanır;
- satır 32-42: jSON [jsonMapperProduitWithCategorie] filtresi, bir [Produit] nesnesini kategorisi ile birlikte serileştirmeye/deserileştirmeye olanak tanır;
- satır 43-53: jSON ve [jsonMapperCategorieWithoutProduits] filtreleri, [Categorie] nesnesini ürünleri olmadan serileştirmeye/deserileştirmeye olanak tanır;
- satır 55-64: jSON [jsonMapperProduitWithoutCategorie] filtresi, bir [Produit] nesnesini kategorisi olmadan serileştirmeye/deserileştirmeye olanak tanır;
Bir T varlığı için jSON filtresi oluşturulurken, sadece T varlığının filtresini değil, aynı zamanda T varlığının içerebileceği Ti varlıklarının filtrelerini de yapılandırmak gerektiği unutulmamalıdır.
11.3.9. [console] katmanı
Katman
[DAO]
Katman
[console]
Katman
[JPA]
Sürücü
[JDBC]
Katman
[Spring Data]
Spring 4
SGBD
![]() |
[Main] sınıfı şöyledir:
package spring.data.console;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import spring.data.config.AppConfig;
import spring.data.dao.DaoException;
import spring.data.dao.IDao;
import spring.data.entities.Categorie;
import spring.data.entities.Produit;
public class Main {
public static void main(String[] args) throws JsonProcessingException {
AnnotationConfigApplicationContext context = null;
try {
// Spring bağlamı örneklendirme
context = new AnnotationConfigApplicationContext(AppConfig.class);
ObjectMapper jsonMapperCategorieWithProduits = context.getBean("jsonMapperCategorieWithProduits",
ObjectMapper.class);
ObjectMapper jsonMapperProduitWithCategorie = context.getBean("jsonMapperProduitWithCategorie",
ObjectMapper.class);
ObjectMapper jsonMapperCategorieWithoutProduits = context.getBean("jsonMapperCategorieWithoutProduits",
ObjectMapper.class);
ObjectMapper jsonMapperProduitWithoutCategorie = context.getBean("jsonMapperProduitWithoutCategorie",
ObjectMapper.class);
IDao dao = context.getBean(IDao.class);
// --------------------------------------------------------------------------------------
// veritabanı boşaltılıyor
log("Vidage de la base de données", 1);
// [CATEGORIES] tablosu boşaltılıyor - zincirleme olarak [PRODUITS] tablosu da boşaltılacak
dao.deleteAllCategories();
// --------------------------------------------------------------------------------------
log("Remplissage de la base", 1);
// tablolar dolduruluyor
List<Categorie> categories = new ArrayList<Categorie>();
for (int i = 0; i < 2; i++) {
Categorie categorie = new Categorie(String.format("categorie%d", i));
for (int j = 0; j < 5; j++) {
categorie.addProduit(new Produit(String.format("produit%d%d", i, j), 100 * (1 + (double) (i * 10 + j) / 100),
String.format("desc%d%d", i, j)));
}
categories.add(categorie);
}
// kategori ekleniyor - zincirleme olarak ürünler de eklenecek
dao.addCategories(categories);
// --------------------------------------------------------------------------------------
log("Affichage de la base", 1);
// kategori listesi
log("Liste des catégories", 2);
affiche(dao.getAllCategories(), jsonMapperCategorieWithoutProduits);
// ürün listesi
log("Liste des produits", 2);
affiche(dao.getAllProduits(), jsonMapperProduitWithoutCategorie);
// 1. kategori ve ürünleri
Categorie categorie = dao.getCategorieByNameWithProduits("categorie1");
log("Catégorie 1 avec ses produits", 2);
affiche(categorie, jsonMapperCategorieWithProduits);
// [produit14] ürünü ve kategorisi
Produit p = dao.getProduitByNameWithCategorie("produit14");
log("Produit [produit14] avec sa catégorie", 2);
affiche(p, jsonMapperProduitWithCategorie);
// --------------------------------------------------------------------------------------
log("Mise à jour du prix des produits de [categorie1]", 1);
log("Produits de la catégorie [categorie1] avant la mise à jour", 2);
Categorie categorie1 = dao.getCategorieByNameWithProduits("categorie1");
Set<Produit> produits = categorie1.getProduits();
affiche(categorie1, jsonMapperCategorieWithProduits);
for (Produit produit : produits) {
produit.setPrix(1.1 * produit.getPrix());
}
dao.updateProduits(Lists.newArrayList(produits));
log("Produits de la catégorie [categorie1] après la mise à jour", 2);
affiche(dao.getCategorieByNameWithProduits("categorie1"), jsonMapperCategorieWithProduits);
// --------------------------------------------------------------------------------------
log("Vidage de la base de données", 1);
// [CATEGORIES] tablosu boşaltılıyor - zincirleme olarak [PRODUITS] tablosu da boşaltılacak
dao.deleteAllCategories();
// veritabanının görüntülenmesi
log("Liste des categories avant l'ajout", 2);
affiche(dao.getAllCategories(), jsonMapperCategorieWithoutProduits);
log("Liste des produits avant l'ajout", 2);
affiche(dao.getAllProduits(), jsonMapperProduitWithoutCategorie);
log("Ajout d'une catégorie [cat1] avec deux produits de même nom", 1);
// ekleme işlemi gerçekleştiriliyor
categorie = new Categorie("cat1");
categorie.addProduit(new Produit("x", 1.0, ""));
categorie.addProduit(new Produit("x", 1.0, ""));
// kategori ekleniyor - zincirleme olarak ürünler de eklenecek
try {
dao.addCategories(Lists.newArrayList(categorie));
} catch (DaoException e) {
System.out.println(e);
}
// doğrulama
log("Liste des categories après l'ajout", 2);
affiche(dao.getAllCategories(), jsonMapperCategorieWithoutProduits);
log("Liste des produits après l'ajout", 2);
affiche(dao.getAllProduits(), jsonMapperProduitWithoutCategorie);
} catch (DaoException e) {
System.out.println(e);
} finally {
if (context != null) {
// tamam
context.close();
}
}
System.out.println("Travail terminé");
}
// T türünde bir öğenin görüntülenmesi
static private <T> void affiche(T element, ObjectMapper jsonMapper) throws JsonProcessingException {
System.out.println(jsonMapper.writeValueAsString(element));
}
// T türündeki öğelerin listesinin görüntülenmesi
static private <T> void affiche(List<T> elements, ObjectMapper jsonMapper) throws JsonProcessingException {
for (T element : elements) {
affiche(element, jsonMapper);
}
}
private static void log(String message, int mode) {
// mesaj görüntüleme
String toPrint = null;
switch (mode) {
case 1:
toPrint = String.format("%s --------------------------------", message);
break;
case 2:
toPrint = String.format("-- %s", message);
break;
}
System.out.println(toPrint);
}
}
- 25. satır: [AppConfig] yapılandırma sınıfından Spring bean'lerinin oluşturulması;
- 26-33. satırlar: jSON eşleştiricilerinden referansların alınması. [ApplicationContext].getBean yönteminin aşağıdaki imzası kullanılır:
- [ApplicationContext].getBean(String id, Class sınıf) : [classe] türünde birden fazla bean olduğunda kullanılır. Bu durumda, istenen bean'in tanımlayıcısı belirtilir. Eğer bu bean, [@Bean] anotasyonu ile tanımlanmışsa, kimliği anotasyon uygulanan yöntemin adıdır. [@Bean(« identifiant »] anotasyonu ile tanımlanmışsa, kimliği anotasyonda belirtilen değerdir;
- 34. satır: [DAO] katmanından bir referans alınması;
- satır 37-39: veritabanının boşaltılması. Kategori tablosu boşaltılır (satır 39). Çünkü şunu yazdık:
@OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL })
public Set<Produit> produits = new HashSet<Produit>();
bir kategori silindiğinde, o kategoriye bağlı tüm ürünler de silinir;
- satır 43-53: tabloya her biri 5 ürün içeren 2 kategori ekleniyor. Satır 50'de, iki kategorinin eklenmesi aynı anda ürünlerinin de eklenmesine neden olacak, çünkü yine [cascade = { CascadeType.ALL }] yazdık;
- 58. satır: Kategoriler görüntülenir. Kategorileri ürünleri olmadan görüntülemek için jSON [jsonMapperCategorieWithoutProduits] eşleştiricisi kullanılır. Nitekim [dao.getAllCategories()] yöntemi, kategorileri ürünleri olmadan görüntüler (gecikmeli yükleme);
- 61. satır: Ürünler, kategorileri olmadan görüntülenir. [dao.getAllProduits()] yöntemi, ürünleri kategorileri olmadan görüntüler (gecikmeli yükleme);
- 63-65. satırlar: [categorie1] adlı kategoriyi ürünleriyle birlikte görüntüler (eager loading);
- 67-69. satırlar: Bir ürünü, kategorisi ile birlikte görüntüler;
- 71-81. satırlar: [categorie1] kategorisindeki tüm ürünlerin fiyatlarını %10 artırır;
- 91-101. satırlar: aynı ada sahip iki ürün içeren bir kategori eklenir. Ancak [PRODUITS] tablosunda [NOM] sütununda tekillik kısıtlaması vardır. Dolayısıyla ikinci ürünün eklenmesi reddedilecek ve bir istisna atılacaktır. Ancak [dao.addProduits] yöntemi bir işlem içinde yürütülür. İkinci eklemenin başarısız olması, dolayısıyla ilk ürünün ve bunların kategorisi olan [cat1]'in eklenmesini de iptal etmelidir. Doğrulamak istediğimiz şey budur;
- satır 119-121: T türündeki herhangi bir öğenin jSON dizesini görüntüleyebilen genel bir yöntem. jSON serileştirmesi, parametre olarak geçirilen eşleştirici tarafından kontrol edilir;
- satır 124-128: benzer bir yöntem, bu sefer T türündeki öğelerden oluşan bir liste için;
[Main] sınıfının çalıştırılması şu sonuçları verir (Spring günlükleri hariç):
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
Affichage de la base --------------------------------
-- Liste des catégories
{"id":4,"version":0,"nom":"categorie0"}
{"id":5,"version":0,"nom":"categorie1"}
-- Liste des produits
{"id":13,"version":0,"nom":"produit00","idCategorie":4,"prix":100.0,"description":"desc00"}
{"id":14,"version":0,"nom":"produit01","idCategorie":4,"prix":101.0,"description":"desc01"}
{"id":15,"version":0,"nom":"produit02","idCategorie":4,"prix":102.0,"description":"desc02"}
{"id":16,"version":0,"nom":"produit03","idCategorie":4,"prix":103.0,"description":"desc03"}
{"id":17,"version":0,"nom":"produit04","idCategorie":4,"prix":104.0,"description":"desc04"}
{"id":18,"version":0,"nom":"produit10","idCategorie":5,"prix":110.0,"description":"desc10"}
{"id":19,"version":0,"nom":"produit11","idCategorie":5,"prix":111.0,"description":"desc11"}
{"id":20,"version":0,"nom":"produit12","idCategorie":5,"prix":112.0,"description":"desc12"}
{"id":21,"version":0,"nom":"produit13","idCategorie":5,"prix":113.0,"description":"desc13"}
{"id":22,"version":0,"nom":"produit14","idCategorie":5,"prix":114.0,"description":"desc14"}
-- Catégorie 1 avec ses produits
{"id":5,"version":0,"nom":"categorie1","produits":[{"id":18,"version":0,"nom":"produit10","idCategorie":5,"prix":110.0,"description":"desc10"},{"id":19,"version":0,"nom":"produit11","idCategorie":5,"prix":111.0,"description":"desc11"},{"id":20,"version":0,"nom":"produit12","idCategorie":5,"prix":112.0,"description":"desc12"},{"id":21,"version":0,"nom":"produit13","idCategorie":5,"prix":113.0,"description":"desc13"},{"id":22,"version":0,"nom":"produit14","idCategorie":5,"prix":114.0,"description":"desc14"}]}
-- Produit [produit14] avec sa catégorie
{"id":22,"version":0,"nom":"produit14","idCategorie":5,"prix":114.0,"description":"desc14","categorie":{"id":5,"version":0,"nom":"categorie1"}}
Mise à jour du prix des produits de [categorie1] --------------------------------
-- Produits de la catégorie [categorie1] avant la mise à jour
{"id":5,"version":0,"nom":"categorie1","produits":[{"id":18,"version":0,"nom":"produit10","idCategorie":5,"prix":110.0,"description":"desc10"},{"id":19,"version":0,"nom":"produit11","idCategorie":5,"prix":111.0,"description":"desc11"},{"id":20,"version":0,"nom":"produit12","idCategorie":5,"prix":112.0,"description":"desc12"},{"id":21,"version":0,"nom":"produit13","idCategorie":5,"prix":113.0,"description":"desc13"},{"id":22,"version":0,"nom":"produit14","idCategorie":5,"prix":114.0,"description":"desc14"}]}
-- Produits de la catégorie [categorie1] après la mise à jour
{"id":5,"version":0,"nom":"categorie1","produits":[{"id":18,"version":1,"nom":"produit10","idCategorie":5,"prix":121.0,"description":"desc10"},{"id":19,"version":1,"nom":"produit11","idCategorie":5,"prix":122.1,"description":"desc11"},{"id":20,"version":1,"nom":"produit12","idCategorie":5,"prix":123.2,"description":"desc12"},{"id":21,"version":1,"nom":"produit13","idCategorie":5,"prix":124.3,"description":"desc13"},{"id":22,"version":1,"nom":"produit14","idCategorie":5,"prix":125.4,"description":"desc14"}]}
Vidage de la base de données --------------------------------
-- Liste des categories avant l'ajout
-- Liste des produits avant l'ajout
Ajout d'une catégorie [cat1] avec deux produits de même nom --------------------------------
Les erreurs suivantes se sont produites :
- org.hibernate.exception.ConstraintViolationException: could not execute statement
- could not execute statement
- Duplicate entry 'x' for key 'NOM'
-- Liste des categories après l'ajout
-- Liste des produits après l'ajout
Travail terminé
- satır 4-17: tabloya eklenen kategoriler ve ürünler;
- 18-19. satırlar: ürünleri ile birlikte bir kategori;
- 20-21. satırlar: bir ürün ve kategorisi;
- 22-26. satırlar: Bazı ürünlerin fiyatlarının güncellenmesi. 24. satırda, fiyatların gerçekten %10 arttığı görülüyor;
- satır 27-36: aynı isimde iki ürün içeren [cat1] kategorisinin eklenmesi. Tablonun ekleme öncesi (satır 28-29) ve ekleme sonrası (satır 35-36) aynı olduğu görülüyor; bu da işlemin tüm eklemelerinin gerçekten iptal edildiğini gösteriyor;
- satır 31-34: İkinci ürünün eklenmesi sırasında meydana gelen ve tüm işlemin başarısız olmasına neden olan istisna;
11.3.10. JUnit birim testi
![]() |
![]() |
[Test01] sınıfı şu şekildedir:
package spring.data.tests;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import spring.data.config.AppConfig;
import spring.data.dao.DaoException;
import spring.data.dao.IDao;
import spring.data.entities.Categorie;
import spring.data.entities.Produit;
@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {
// katman [DAO]
@Autowired
private IDao dao;
// jSON filtreleri
@Autowired
@Qualifier("jsonMapper")
private ObjectMapper jsonMapper;
@Autowired
@Qualifier("jsonMapperCategorieWithProduits")
private ObjectMapper jsonMapperCategorieWithProduits;
@Autowired
@Qualifier("jsonMapperProduitWithCategorie")
private ObjectMapper jsonMapperProduitWithCategorie;
@Autowired
@Qualifier("jsonMapperCategorieWithoutProduits")
private ObjectMapper jsonMapperCategorieWithoutProduits;
@Autowired
@Qualifier("jsonMapperProduitWithoutCategorie")
private ObjectMapper jsonMapperProduitWithoutCategorie;
@Before
public void cleanAndFill() {
// her testten önce veritabanı temizlenir
log("Vidage de la base de données", 1);
// [CATEGORIES] tablosu boşaltılır - buna bağlı olarak [PRODUITS] tablosu da boşaltılacaktır
dao.deleteAllCategories();
// --------------------------------------------------------------------------------------
log("Remplissage de la base", 1);
// tablolar doldurulur
List<Categorie> categories = new ArrayList<Categorie>();
for (int i = 0; i < 2; i++) {
Categorie categorie = new Categorie(String.format("categorie%d", i));
for (int j = 0; j < 5; j++) {
categorie.addProduit(new Produit(String.format("produit%d%d", i, j), 100 * (1 + (double) (i * 10 + j) / 100),
String.format("desc%d%d", i, j)));
}
categories.add(categorie);
}
// kategori ekleniyor - zincirleme olarak ürünler de eklenecek
categories = dao.addCategories(categories);
}
@Test
public void showDataBase() throws BeansException, JsonProcessingException {
// kategori listesi
log("Liste des catégories", 2);
List<Categorie> categories = dao.getAllCategories();
affiche(categories, jsonMapperCategorieWithoutProduits);
// ürün listesi
log("Liste des produits", 2);
List<Produit> produits = dao.getAllProduits();
affiche(produits, jsonMapperProduitWithoutCategorie);
// bazı kontroller
Assert.assertEquals(2, categories.size());
Assert.assertEquals(10, produits.size());
Categorie categorie = findCategorieByName("categorie0", categories);
Assert.assertNotNull(categorie);
Produit produit = findProduitByName("produit03", produits);
Assert.assertNotNull(produit);
Long idCategorie = produit.getIdCategorie();
Assert.assertEquals(categorie.getId(), idCategorie);
}
@Test
public void getCategorieByNameWithProduits() {
log("getCategorieByNameWithProduits", 1);
Categorie categorie1 = dao.getCategorieByNameWithProduits("categorie1");
Assert.assertNotNull(categorie1);
Assert.assertEquals(5, categorie1.getProduits().size());
}
@Test
public void getCategorieByNameWithoutProduits() {
log("getCategorieByNameWithoutProduits", 1);
Categorie categorie1 = dao.getCategorieByNameWithoutProduits("categorie1");
Assert.assertNotNull(categorie1);
Assert.assertEquals("categorie1", categorie1.getNom());
}
@Test
public void getProduitByIdWithCategorie() {
log("getProduitByNameWithCategorie", 1);
Produit produit = dao.getProduitByNameWithCategorie("produit03");
Produit produit2 = dao.getProduitByIdWithCategorie(produit.getId());
Assert.assertNotNull(produit2);
Assert.assertEquals(produit2.getNom(), produit.getNom());
Assert.assertEquals(produit2.getId(), produit.getId());
Assert.assertEquals(produit.getCategorie().getId(), produit2.getCategorie().getId());
}
@Test
public void getProduitByIdWithoutCategorie() {
log("getProduitByIdWithoutCategorie", 1);
Produit produit = dao.getProduitByNameWithCategorie("produit03");
Produit produit2 = dao.getProduitByIdWithoutCategorie(produit.getId());
Assert.assertNotNull(produit2);
Assert.assertEquals(produit2.getNom(), produit.getNom());
Assert.assertEquals(produit2.getId(), produit.getId());
}
...
// -------------- özel yöntemler
private Produit findProduitByName(String nom, List<Produit> produits) {
for (Produit produit : produits) {
if (produit.getNom().equals(nom)) {
return produit;
}
}
return null;
}
private Categorie findCategorieByName(String nom, List<Categorie> categories) {
for (Categorie categorie : categories) {
if (categorie.getNom().equals(nom)) {
return categorie;
}
}
return null;
}
// T türünde bir öğenin görüntülenmesi
static private <T> void affiche(T element, ObjectMapper jsonMapper) throws JsonProcessingException {
System.out.println(jsonMapper.writeValueAsString(element));
}
// T türündeki öğelerin listesinin görüntülenmesi
static private <T> void affiche(List<T> elements, ObjectMapper jsonMapper) throws JsonProcessingException {
for (T element : elements) {
affiche(element, jsonMapper);
}
}
private static void log(String message, int mode) {
// mesaj görüntüleme
String toPrint = null;
switch (mode) {
case 1:
toPrint = String.format("%s --------------------------------", message);
break;
case 2:
toPrint = String.format("-- %s", message);
break;
}
System.out.println(toPrint);
}
private static void show(String title, List<String> messages) {
// başlık
System.out.println(String.format("%s : ", title));
// mesajlar
for (String message : messages) {
System.out.println(String.format("- %s", message));
}
}
}
- 27. satır: birim testi, 11.3.8. paragrafında daha önce tanıtılan [AppConfig] sınıfı tarafından yapılandırılmıştır;
- 32-33. satırlar: [DAO] katmanına bir referans eklenmesi;
- satır 36-50: beş jSON eşleştiricisinin eklenmesi;
- satır 60-71: veritabanını boşalttıktan sonra (satır 57), veritabanı her biri 5 ürün içeren 2 kategori ile doldurulur. Bu yöntem, satır 52'deki [@Before] açıklaması nedeniyle her testten önce yürütülür;
- satır 75-93: veritabanının içeriğini görüntüler;
- 95-101. satırlar: adıyla tanımlanan bir kategoriyi, içindeki ürünlerle birlikte ister;
- satır 103-109: ürünleri olmadan bir kategori sorgulanır; kategori, adıyla tanımlanır;
- satır 111-120: kategorisi ile birlikte bir ürün talep eder; ürün, numarasıyla tanımlanır;
- satır 122-130: kategorisi olmadan bir ürün ister; ürün numarasıyla tanımlanır;
- satır 133-184: farklı testler tarafından paylaşılan özel yöntemler;
Yapılması gereken: Testi çalıştırın. Testin başarılı olması gerekir.
11.3.11. Günlük yönetimi
Konsol uygulamasının veya JUnit testinin günlükleri, aşağıdaki [logback.xml] dosyası ile yapılandırılır:
![]() |
Dosyanın adı [logback.xml] olmalı ve projenin Classpath'inde bulunmalıdır. Bunun için, Classpath'in bir parçası olan [src/main/resources] klasörüne yerleştirilmiştir. İçeriği şöyledir:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- kodlayıcılara varsayılan olarak tür atanır
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- günlük düzeyini kontrol eder -->
<root level="info"> <!-- bilgi, hata ayıklama, uyarı -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
- 12. satır: [<root level="info">] etiketi, [info] seviyesindeki günlükleri görüntüler. [info] yerine şunlar yazılabilir:
- [debug]: Bu, en ayrıntılı günlük seviyesidir. Projenin hata ayıklama aşamasında kullanılması tavsiye edilir, çünkü istemci/sunucu arasındaki iletişimle ilgili çok ilginç günlükler içerir. Bu, "perde arkasında" neler olup bittiğini anlamanın bir yoludur;
- [off]: Hiçbir günlük kaydı yoktur;
- [warn]: Spring'in hata sayılmayan ancak olağandışı durumları görüntülediği orta düzey bir günlük seviyesi. Beklenen sonucu alamazsanız bu günlükleri incelemeniz gerekir;
Yapılması gereken: 12. satırdaki seviyeyi [debug] olarak değiştirin ve ardından birim testini çalıştırın. Günlüklerdeki farkı inceleyin.
11.3.12. Projenin Maven arşivinin oluşturulması
Proje arşivini yerel Maven deposuna yüklemek için aşağıdaki adımları izleyin [1-3]:
![]() |
Arşiv, [pom.xml] dosyasında bulunan kimlik bilgileriyle oluşturulacaktır:
<groupId>istia.st.springdata</groupId>
<artifactId>intro-spring-data-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
Yerel Maven deposunun konumu Eclipse yapılandırmasında bulunabilir:
![]() |
Böylece, Maven artefaktının doğru şekilde kurulup kurulmadığını kontrol etmek mümkündür:
![]() |
Artık başka bir yerel Maven projesi bu arşivi kullanabilir.





































