Spring Data JPA ve Transaction Yönetimi
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.