İçeriğe geç

Spring Framework Transaction ve Propagation yapısı

Merhaba,

Spring Framework Transaction yapısına ait olan Propagation elementinden bahsedeceğim. 

Öncesinde transaction işlemi nedir? Spring bunu nasıl yapıyor biraz değinelim. 

Transaction; veritabanı bağlantı yapılarımızda sorguları, insertleri vs. bir grup olarak değerlendirip ya hep ya hiç mantığına göre çalıştıran yapıdır. Veri bütünlüğünü korumak açısından çok önemli bir kavramdır. 

En basit olarak Spring Framework bu işi yapmak için çok güzel bir anotasyon kullanmaktadır.

@Transaction anotasyonu ile bu veri tutarlılığı korunuyor. 

Gelelim bizim asıl konumuza. Spring bu anotasyonun Propagation elementini kullanarak veriyi farklı şekillerde commitleme ve rollback yapma imkanları sunuyor. Bu Propagation çeşitlerini örnekler üzerinden inceleyelim:

Propagation.REQUIRED :

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)

Required  propagation’ı en sık kullanılan propagation’dır.  Eğer transaction içerisinde bir propagation tipi belirlemezseniz default olarak bu tipi kullanacaktır. Required çalışan bir transaction’ın altındaki tüm class ve metotlar aynı session üzerinde çalışırlar.

Amacı: Kullanıldığı metot veya class altındaki tüm verileri işleme sırasında hata olması durumunda bütün işlemleri geri alır veya commit yapar.Aynı session üzerinde çalıştıkları için blok işlemlerinde bir hata olması durumunda tüm bloğun işlemini geri alır veya başarılı olan bloğun tüm işlemini commitler. Yani 100adet veriyi insert etmek istediğinizde 999’uncu veriye kadar herşey yolunda bile son veriyi kaydetmek istediğinde yaşayacağı herhangi bir sıkıntı(Bağlantı kopması, Verinin hatalı olması) durumunda 1000 adet veriyi de commit etmeyecektir. 

ÖRNEK:

@Service
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public class UserManagerEntityService implements UserManager {

@Autowired
private UserDAOImpl userDAO;

public void insert() {
insertUser_REQUIRED();
insertUser_REQUIRED_EXP ();
}
public void insertUser_REQUIRED() {

User user = new User ();
user.setName ("Doruk");
userDAO.insertUser_REQUIRED (user);
}
private void insertUser_REQUIRED_EXP() {
User user = null;
user.setName ("burçak");

}}

@Repository
public class UserDAOImpl {
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void insertUser_REQUIRED(User user) {
sessionFactory.getCurrentSession ().save (user);
}
}

public class Main
{
public static void main( String[] args ) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext(“spring.xml”);
UserManager userManager = (UserManager) ctx.getBean(“userManagerEntityService”);

User user = new User();
userManager.insert ();
}}

Yukarıdaki örnekte Main class içerisinde insert() metodunu çalıştırmak istediğimizde ilk olarak insertUser_REQUIRED(); metoduna gidecektir. Bu metot sağlıklı bir şekilde çalışacaktır. Bu metodunun hemen alt satırına bir breakpoint koyarak breakpointe geldiğimiz esna da D.B’de kontrol ettiğimizde “Doruk” verisinin User tablosuna henüz yazılmadığını görürüz. Çünkü Transaction’ı classın başında tanımladık ve blok işini bitirip son süslü parantezi görmeyene kadar commit işlemi gerçekleşmeyecektir. 

Alt satırdaki insertUser_REQUIRED_EXP (); satırı çalıştığı sırada nullpointerexception hatası alacaktır. Ve bu metot insert(){} bloğunun içerisinde olduğu için bu blokta ki tüm işlemler rollback olacağı için DB’de hiçbir kayıt göremeyeceğiz. 

UserManagerEntityService class’ı üzerindeki Transaction tanımlamasını @Transactional bu şekilde de yapabiliriz. Yine Propagation.REQUIRED olarak çalışacaktır.  Veya class üzerinde çalışmasını istemeyip sadece metot üzerinde de çalıştırabiliriz. Aşağıdaki gibi bir tanımlama da aynı şekilde çalışacaktır. 

@Service
public class UserManagerEntityService implements UserManager {

@Autowired
private UserDAOImpl userDAO;

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void insert() {
insertUser_REQUIRED();
insertUser_REQUIRED_EXP ();
}

Propagation.REQUIRES_NEW : En kolay anlayabileceğimiz şekli ile bu propagation askıya alma işlemi yapar.

Amacı: Yani Bu propagation eğer başlatılmış bir session içerisinde ise o session’ı askıya alır. Yeni bir session oluşturur. Oluşturduğu session içerisinde görevini başarı ile tamamladıktan sonra session  kapanır ve kaldığı sessiondan veri işlenmesi devam eder. Bunu ne gibi durumlarda kullanabiliriz dersek elimizde 1000 adet veri olsun. Bu veriler üzerinde güncelleme yapılıyor olsun tek tek. Ancak ben bir hata ile karşılaşma durumunda sağlıklı verilerimin güncellenmesinin kesilmesini istemiyorum, aksine sorunlu olan verilerimi bir tabloda görüp ayrıca müdehale etmek istiyorum. Bu gibi bir durumda hem hatamı kendim sarmallayıp hemde hata durumlarında işlemimi kesmemiş olacağım. 

ÖRNEK:

@Service
@Transactional
public class UserManagerEntityService implements UserManager {

@Autowired
private UserDAOImpl userDAO;

public void insert() {

insertUser_REQUIRES_NEW();
insertUser_REQUIRES_NEW_EXP();
}}
private void insertUser_REQUIRES_NEW() {
User user = new User ();
user.setName ("Nesrin");
userDAO.insertUser_REQUIRES_NEW (user);

}
private void insertUser_REQUIRES_NEW_EXP() {
User user = null;
user.setName ("Burak");
}

@Repository
public class UserDAOImpl {

@Autowired
SessionFactory sessionFactory;

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertUser_REQUIRES_NEW(User user) {
sessionFactory.getCurrentSession ().save (user);
}
}

Yukarıda ki örnekte dikkat etmemiz gereken nokta UserManagerEntityService  class’ının propagation elementinin REQUIRED olması(default). insertUser_REQUIRES_NEW() metodunda herşey yolunda görünüyor. Yani User tablosuna “Nesrin” verisi normal bir şekilde kaydediliyor. Ancak bu metodun hemen altındaki insertUser_REQUIRES_NEW_EXP() metodunda ters giden birşeyler var. Burada nullpointerexception hatası alınacaktır. Ancak veritabanımıza gelip kontrol ettiğimizde User tablosuna “Nesrin” verisinin kaydedildiğini göreceğiz. Yukarıda dikkat etmeniz gereken noktayı belirtmiştim. UserManagerEntityService  REQUIRED çalıştığı için beklentimiz bu class içerisinde çağrılan metotta hata olması durumunda tüm veriyi rollback etmesi. Ama öyle olmadı. Çünkü biz içerideki insertUser_REQUIRES_NEW() metodunun propagation değerini REQUIES_NEW verdik .Yani bana ayrı bir session aç. Eğer bu sessionda hata olmadan kapatabilirsen commit işlemini yap, üst classtaki  transaction’ın propagation tipine göre çalışma dedik.

Propagation.MANDATORY: Mandatory zorunlu demektir. Yani kendinden önce  bir transaction başlamasını istemektedir. Eğer kendinden önce bir transaction başlamışssa ona dahil olur. Önceki transaction propagation’ı ne olursa olsun MANDATORY propagation kendinden önceki transaction’a bağlanır ve REQUIRED gibi davranır. Class propagation’ı REQUIES_NEW olsa bile MANDATORY propagation’ı REQUIRED olarak çalışacaktır.

Amacı: Büyük yapılarda, iç içe geçmiş ilişkili class yapılarında kontrol amaçlı kullanılır. Yani bir ödeme metodu yazacağım ve bu metot birkaç bağlantılı class üzerinden gelebilir. En üstteki servis zaman içerisinde değişebilir. Transaction yapısını artık başka bir servis üzerinden sağlanacağı bu servisin normal bir yapıda olması istenebilir. Ve bu yapılırken içerisindeki transaction çalışması gereken metotların hepsinin taşınması unutulmuş olabilir. Böyle bir durumda ödeme ile ilgili bir hata anında transaction yapıda olmadığı için üst class veya metodu çok büyük sorunlar olacaktır. Metot üzerinde Propagation.MANDATORY yapısını kullanmamız halinde eğer bize bu metodun kullanıldığı üst metot veya classlar transaction yapısına uygun değilse hata verecektir. “Bu metodu kullanmak istiyorsan herhangi bir transaction kullan” diyecektir. Hata mesajı aşağıdaki gibi olacaktır : No existing transaction found for transaction marked with propagation ‘mandatory’

ÖRNEK:

@Service
public class UserManagerEntityService implements UserManager {

@Autowired
private UserDAOImpl userDAO;

public void insert() {
insertUser_MANDATORY();
}

private void insertUser_MANDATORY() {
User user = new User ();
user.setName ("nesrin");
userDAO.insertUser_MANDATORY (user);

}

@Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
public void insertUser_MANDATORY(User user) {
sessionFactory.getCurrentSession ().save (user);
}

Yukarıda ki örnekte UserManagerEntityService class’ı herhangi bir transaction yapıda olmadığı için hata fırlatacaktır.

Propagation.NEVER: Kendinden önce bir transaction başlatılmış ise hata fırlatır. 

AMACI: Bu propagation kullanmaktaki amaç metot ve classın özel olarak transactional olmasını engellemektir. Bu tarz yapılar küçük yapılarda gözardı edilebilir. Zaten hangisinin transactional olmasını istediğimi hangisinin olmasını istemediğimi biliyorum diyebilirsiniz ama proje kapsamı genişledikçe, ekip büyüdükçe, çalışanların bu kontrollere göre hareket etmesi daha doğru ve düzgün bir yapı olmasını sağlayacaktır.

Propagation.SUPPORT: Eğer kendisinden önce bir transaction başlamış ise ona dahil olur ve onun gibi davranır. Yani adından da belli olacağı gibi var olan transaction’ı destekler.Ama kendinden önce bir transaction yok ise non-transactiol bir davranış sergiler ve kendinden sonra gelen transactionların da etkisi kalkmış olur.

ÖRNEK:

@Service
@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
public class UserManagerEntityService implements UserManager {

@Autowired
private UserDAOImpl userDAO;

public void insert() {
insert_SUPPORTS ();
insertUser_REQUIRED();
insertUser_REQUIRES_NEW_EXP();
}
private void insert_SUPPORTS() {
User user = null;
for (int i = 0; i < 5; i++) {
if (i != 3) {
user = new User ();
}
user.setName ("nesrin" + i);
userDAO.insert_SUPPORT (user);

}}

Yukarıda ki örnekte UserManagerEntityService class’ının propagation’ı SUPPORT olarak tanımlanmış.  içerideki insert_SUPPORTS (); metodu sağlıklı çalışıyor. Hemen altındaki insertUser_REQUIRED(); metodu da doğru çalışıyor. Ancak en alttaki insertUser_REQUIRES_NEW_EXP() metodu hata vermektedir. Ama veritabanımızdan kotnrol ettiğimizde ilk iki metodun verilerini kaydetmiş olduğunu görüyoruz.  Çünkü class herhangi bir transaction başlatmamış daha önceden bu nedenle non-transactional davranmaktadır.

Eğer aşağıdaki gibi  UserManagerEntityService class’ının propagation’ı Propagation.REQIRED olsaydı; class içerisinde ki metot propagation’ı SUPPORT olsaydı metot REQUIRED çalışacaktı.  Ve commit işlemi olmayacaktı.

@Service
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public class UserManagerEntityService implements UserManager {

@Autowired
private UserDAOImpl userDAO;

public void insert() {
insert_SUPPORTS();
}
private void insert_SUPPORTS() {
User user = null;
for (int i = 0; i < 5; i++) {
if (i != 3) {
user = new User ();
}else{
user = null;
}
user.setName ("nesrin" + i);
userDAO.insert_SUPPORT (user);
}
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORT)
public void insert_SUPPORT(User user) {
sessionFactory.getCurrentSession ().save (user);
}

Propagation.NOT_SUPPORTED: Öncesinde bir transaction başlatmaya gerek yoktur. Eğer bir transaction başlamışşsa onu askıya alır, kendi işlemlerini tamamlar ve kaldığı yerden devam eder. REQUIES_NEW propagation’ına benzetebiliriz bunu. Ancak çok temel bir fark var arada: REQUIRES_NEW propagation’ı kullandığınız zaman sadece kullanılan o blok için ayrı bir sassion açılır, blok işlemi bittikten sonra sıradaki diğer metotlar için kaldığı transactio’dan devam eder. Ama NOT_SUPPORTED öyle değil. Kendi bulunduğu blok tamamlandıktan sonra kaldığı yerden devam etmez, sonraki transactionları da yok sayar. Yani görüldüğü yerden sonraki tüm metotlar non-transaction çalışır.

ÖRNEK:

Service
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
public class UserManagerEntityService implements UserManager {

@Autowired
private UserDAOImpl userDAO;

public void insert() {
    insert_NOT_SUPPORTED ();
insertUser_REQUIRED_EXP();
}

private void insert_NOT_SUPPORTED() {
User user = null;
for (int i = 0; i < 5; i++) {
if (i != 3) {
user = new User ();
}
user.setName ("Dorukcum" + i);
userDAO.insert_NOT_SUPPORTED (user);
}
}

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
private void insertUser_REQUIRED_EXP() {
User user = null;
for (int i = 0; i < 5; i++) {
if (i != 3) {
user = new User ();
}else{
user = null;
}
user.setName (“nesrin” + i);
userDAO.insertUser_REQUIRED (user);
}
}
Yukarıdaki kodda insert_NOT_SUPPORTED(); metodu çalışacaktır sağlıklı bir şekilde.Sonra ki metot olan insertUser_REQUIRED_EXP(); metodunun içeriği hatalıdır. Ve bu metodun üzerinde Propagation’ı REQUIRED olan bir transaction vardır. Yani aslında bu metot içerisindeki bir hata durumunda komple rollback olacaktır metodun içi. Ancak class’ın üzerinde NOT_SUPPORTED tanımlanan propagation sayesinde bu insertUser_REQUIRED_EXP() metodu non-transactional özellik gösterecek ve sadece hata alınacaktır. Rollback olmayacaktır veriler. Eğer class üzerinde NOT_SUPPORTED yerine REQUIRES_NEW kullanmış olsaydık insertUser_REQUIRED_EXP() içerisindeki veriler rollback olacaktı.

Genel olarak anlatacaklarım bu kadar. Bir sonraki yazıda görüşmek dileği ile.

Örneklerin çalışır haldeki kodlarına aşağıdaki github hesabımdan erişebilirsiniz.

https://github.com/Nesrinasan/Spring-Transaction-Propagation

Bug’sız günler 🙂

Tarih:Spring

İlk Yorumu Siz Yapın

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir