Spring Data JPA ve Transaction Yönetimi

Umut Akbulut
BilgeAdam Teknoloji
5 min readJul 8, 2024

Modern yazılım geliştirme dünyasında, veri yönetimi ve veritabanı işlemleri, uygulamaların bel kemiğini oluşturur. Veritabanı ile etkileşim, veri erişim katmanı (Data Access Layer) aracılığıyla sağlanır ve bu katmanın etkin bir şekilde yönetilmesi, uygulamaların performansı ve güvenilirliği açısından kritik öneme sahiptir. İşte burada, Java dünyasında Hibernate gibi ORM (Object Relational Mapping) araçları ve Spring Data JPA devreye girer.

Java Persistence API (JPA), Java uygulamalarında veritabanı işlemlerini basitleştiren bir Java EE spesifikasyonudur. Hibernate, JPA’nın en yaygın kullanılan uygulamalarından biridir ve Spring Data JPA, Hibernate’in sunduğu özellikleri daha kolay ve hızlı bir şekilde kullanmayı sağlar. Transaction yönetimi, veri bütünlüğünü korumak ve tutarsızlıkları önlemek için hayati öneme sahiptir. Bu yazıda, Spring Data JPA ve transaction yönetimi konularını derinlemesine inceleyeceğiz.

1. Veri Erişim Stratejileri ve Fetch Tipleri

Veri erişim stratejileri, veritabanı performansını doğrudan etkiler. Fetch tipleri, ilişkili verilerin ne zaman ve nasıl yükleneceğini belirler.

Eager Fetch

Eager fetch, ilişkili verilerin ana varlık ile birlikte hemen yüklenmesini sağlar. Bu, ManyToOne ve OneToOne ilişkilerde varsayılan olarak kullanılır. Eager fetch, verilerin hemen yüklenmesi gerektiğinde ve veritabanı turlarının en aza indirilmesi gerektiğinde kullanışlıdır.

@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.EAGER)
private Customer customer;
}

Lazy Fetch

Lazy fetch, ilişkili verilerin sadece gerektiğinde yüklenmesini sağlar. Bu, OneToMany ve ManyToMany ilişkilerde varsayılan olarak kullanılır. Lazy fetch, bellekte gereksiz veri yükünü azaltarak performansı artırır.

@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToMany(fetch = FetchType.LAZY, mappedBy = "customer")
private List<Order> orders;
}

2. İleri Düzey Fetch Modları

Fetch modları, ilişkili verilerin nasıl yükleneceğini belirler. Select, Batch, Join ve Sub Select gibi farklı fetch modları, çeşitli senaryolarda performansı optimize etmek için kullanılır.

FetchMode.Select

Varsayılan fetch modudur ve her bir ilişki için ayrı SELECT ifadesi kullanır. Küçük veri setleri için uygundur.

@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToMany(fetch = FetchType.LAZY)
@Fetch(FetchMode.SELECT)
private List<OrderItem> orderItems;
}

FetchMode.Batch

İlişkili verileri gruplar halinde alır, bu da SELECT ifadelerinin sayısını azaltır. Büyük veri setleri için performansı artırır.

@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToMany(fetch = FetchType.LAZY)
@Fetch(FetchMode.BATCH)
@BatchSize(size = 10)
private List<OrderItem> orderItems;
}

FetchMode.Join

İlişkili verileri tek bir SELECT ifadesi ile alır. N+1 sorgu problemini önler.

@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToMany(fetch = FetchType.LAZY)
@Fetch(FetchMode.JOIN)
private List<OrderItem> orderItems;
}

FetchMode.SubSelect

Ana varlığı yükledikten sonra tüm ilişkili verileri tek bir SELECT ifadesi ile alır. Orta büyüklükteki veri setleri için uygundur.

@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToMany(fetch = FetchType.LAZY)
@Fetch(FetchMode.SUBSELECT)
private List<OrderItem> orderItems;
}

3. Projeksiyonlar ve Veri Seçimi

Projeksiyonlar, sadece gerekli veri alanlarını seçmek için kullanılan tekniklerdir. Interface-based ve Class-based projeksiyonlar, veri seçiminde esneklik ve performans sağlar.

Interface-Based Projections

Arayüz tabanlı projeksiyonlar daha az kod gerektirir, tip güvenliği sağlar ve performansı artırır.

public interface CustomerProjection {
String getFirstName();
String getLastName();
}

List<CustomerProjection> findAllProjectedBy();

Class-Based Projections

DTO sınıfları kullanılarak özelleştirilmiş projeksiyonlar oluşturulur, esneklik ve yeniden kullanılabilirlik sağlar.

public class CustomerDTO {
private String firstName;
private String lastName;

// Constructor, getters, and setters
}

List<CustomerDTO> findAllProjectedBy();

4. Sayfalama ve Büyük Veri Setleri ile Çalışma

Sayfalama, büyük veri setlerini verimli bir şekilde işlemek için kullanılan bir tekniktir. Spring Data JPA, sayfalama için esnek ve güçlü araçlar sunar.

Pageable Query

Sayfalama, büyük veri setlerini küçük parçalara bölerek işlemeyi sağlar. Bu, bellek kullanımını azaltır ve kullanıcı deneyimini iyileştirir.

public interface CustomerRepository extends JpaRepository<Customer, Long> {
Page<Customer> findAll(Pageable pageable);
}

Pageable pageable = PageRequest.of(0, 10);
Page<Customer> customers = customerRepository.findAll(pageable);

5. Dinamik Sorgular ve Spesifikasyonlar

Spesifikasyonlar, dinamik ve esnek sorgular oluşturmak için kullanılır. JPA Criteria API ile birlikte, kompleks sorguların okunabilirliğini ve bakımını artırır.

Specifications

Spesifikasyonlar, yeniden kullanılabilir ve kombinlenebilir sorgu kriterleri sağlar.

public class CustomerSpecification {
public static Specification<Customer> hasFirstName(String firstName) {
return (root, query, cb) -> cb.equal(root.get("firstName"), firstName);
}

public static Specification<Customer> hasLastName(String lastName) {
return (root, query, cb) -> cb.equal(root.get("lastName"), lastName);
}
}

Specification<Customer> spec = Specification.where(CustomerSpecification.hasFirstName("John"))
.and(CustomerSpecification.hasLastName("Doe"));

List<Customer> customers = customerRepository.findAll(spec);

6. İlişki Yönetimi ve Cascade Tipleri

İlişkili varlıkların yönetimi, uygulama mimarisinin kritik bir parçasıdır. Cascade tipleri, ebeveyn varlık üzerinde yapılan işlemlerin çocuk varlıklara da uygulanmasını sağlar.

Cascade Types

Farklı cascade tipleri, ilişkili varlıkların otomatik olarak yönetilmesini sağlar.

@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "order")
private List<OrderItem> orderItems;
}

7. Hibernate Yaşam Döngüsü

Hibernate yaşam döngüsü, varlıkların veritabanı ile olan ilişkisini yönetir. Transient, Persistent, Detached ve Removed gibi durumlar, varlıkların yaşam döngüsündeki farklı aşamalardır.

Transient State

Nesne oluşturulmuş ancak Hibernate oturumu ile ilişkilendirilmemiştir. Sadece bir Java nesnesidir.

Persistent State

Nesne Hibernate oturumu ile ilişkilidir ve veritabanında bir satırı temsil eder. Değişiklikler otomatik olarak senkronize edilir.

Customer customer = new Customer();
customer.setFirstName("Umut");
customer.setLastName("Akbulut");

entityManager.persist(customer);

Detached State

Nesne bir zamanlar Hibernate oturumu ile ilişkilendirilmiş ancak şu anda ilişkilendirilmemiştir. Değişiklikler otomatik olarak senkronize edilmez.

Removed State

Nesne silinmek üzere işaretlenmiştir ve oturum temizlendiğinde veritabanından silinir.

entityManager.remove(customer);

8. Performans Optimizasyonu: HikariCP ve Batch Insert

Veritabanı performansını optimize etmek, uygulamaların verimli çalışması için gereklidir. HikariCP, yüksek performanslı bağlantı havuzu sağlarken, batch insert işlemleri veritabanı turlarını azaltır.

HikariCP

HikariCP, yüksek performanslı ve verimli bir bağlantı havuzudur.

spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
hikari:
minimum-idle: 5
maximum-pool-size: 20
idle-timeout: 30000
pool-name: HikariCP
max-lifetime: 2000000
connection-timeout: 30000

Batch Insert

Batch insert işlemleri, veritabanı turlarını azaltarak performansı artırır.

List<Order> orders = ...; // Assume this is a list of orders to be inserted

entityManager.unwrap(Session.class).setJdbcBatchSize(30);
for (Order order : orders) {
entityManager.persist(order);
if (i % 30 == 0) {
entityManager.flush();
entityManager.clear();
}
}

9. Önbellekleme Stratejileri

Hibernate, ilk ve ikinci seviye önbellekler sunarak veritabanı yükünü azaltır. İlk seviye önbellek, aynı oturum içinde varlıkları önbelleğe alırken, ikinci seviye önbellek oturumlar arası önbellekleme sağlar.

First Level Cache

Varsayılan ve etkinleştirilmiş, aynı oturum içinde varlıkları önbelleğe alır.

Second Level Cache

İsteğe bağlı ve yapılandırılabilir, oturumlar arası varlıkları önbelleğe alır. Popüler sağlayıcılar: Ehcache, Hazelcast, Infinispan, Redis.

@Cacheable("customers")
@Entity
public class Customer {
// Entity fields and methods
}

10. Transaction Yönetimi ve İzolasyon Seviyeleri

Transaction yönetimi, veri bütünlüğünü korumak ve tutarsızlıkları önlemek için kritik öneme sahiptir. Spring, çeşitli propagation türleri, izolasyon seviyeleri ve rollback stratejileri sunar.

Propagation Types

Transaction yönetiminde çeşitli propagation türleri kullanılır: REQUIRED, REQUIRES_NEW, SUPPORTS, NOT_SUPPORTED, MANDATORY, NEVER, NESTED.

@Transactional(propagation = Propagation.REQUIRED)
public void placeOrder(Order order) {
orderRepository.save(order);
}

Isolation Levels

Transaction izolasyon seviyeleri, veri bütünlüğünü korumak için kullanılır: READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE.

@Transactional(isolation = Isolation.SERIALIZABLE)
public void processOrder(Long orderId) {
// Order processing logic
}

Bir sonraki yazıda görüşmek üzere.

--

--

Umut Akbulut
BilgeAdam Teknoloji

"Tech leader & Software Architect. Passionate about digital transformation, microservices, and innovation in the finance and tech sectors."