Transaction yapısını anlamak için öncelikle transaction’un ne demek olduğunu anlamak gerekir. Bu kavramı daha iyi anlamak için “Transaction Kavramı ve ACID” yazısına göz atabilirsiniz.

Transactional olan bir yerde, bağlı olunan transaction bitmeden veri commitlenmez. Ne zaman ki transaction biter, o zaman transaction içinde bulunan tüm işlemler commitlenir. Fakat transactional olmayan, yani non-transactional, yerlerde işlem yapılır yapılmaz commitlenir. Bu da uygulamamızın veri tutarlılığını bozabilir.

Örnek olarak bir bankacılık uygulaması düşünülebilir. Bir hesaptan başka bir hesaba para gönderme işlemini yapılırken arka planda bir çok iş yapılır. En basit hali ile bir hesaptan para çıkar, başka bir hesaba para girer. Bu hesapların bakiyeleri güncellenir. Ayrıca muhtemelen bu hareketler bir tabloya yazılıyordur. Bu tablolara da kayıt atılması gerekir.

A kullanıcısı, kendi hesabından, B hesabına 100 TL göndermeye çalıştığında öncelikle A hesabından 100 TL düşülür ve B hesabına 100 TL eklenir. Eğer B hesabına bu tutar eklenirken de bir hata alınırsa ve A hesabında yapılan güncelleme geri alınmazsa, B hesabına para gönderilemeden A hesabından para çıkışı olur. Kullanıcı bu işlemi tekrar denerse ve tekrar hata alırsa, bir 100 TL daha para göndermiş olur ama hala B kullanıcısına borcunu ödeyememiş olur. Bu gibi durumlarda, işlemin herhangi bir yerinde hata alınırsa önceki yapılan işlemlerin geri alınması gerekir. Bunun için de transaction yapısı kullanılır.
Tüm bu işlemler transactional olan bir yerde yapılırsa, ilgili transaction içinde bir hata alındığı için, commit yerine rollback yapılır ve tutarsızlık oluşmasının önüne geçilir.

Bir başka senaryo, toplu işlemler. Çok sayıda kaydın güncellenmeye çalışıldığı durumlarda, son kayıt için bir hata alınırsa bütün güncelleme işlemleri geri alınmak istenmeyebilir. Sadece başarılı olanların kaydedilmesi, başarısız olanlar için ise herhangi bir işlem yapmaması istenebilir. Bu durumda da her bir işlem için bir transaction açmalı, hata durumunda sadece ilgili transaction rollback olmalı ve diğer başarılı güncellemeler de commitlenebilmelidir.

Ya da bir metodun öncesinde muhakkak transaction olması/olmaması gerekebilir.

Tüm bu durumlar için Spring’in bize sağladığı çok kullanışlı bir anotasyon var. @Transactional anotasyonu.

Spring’te transactionların yönetimi için kullanılan @Transactional anotasyonunun en önemli propertysi “propagation“dır. Propagation ile yeni bir transaction mu başlatılacak, var olan bir transactiona mı bağlanacak, transactional mı olsun yoksa non-transactional olması garanti mi edilmeli vb. tüm bu durumları yönetebiliriz.

Propagation değerleri aşağıdaki gibidir:
– REQUIRED
– SUPPORTS
– MANDATORY
– REQUIRES_NEW
– NOT_SUPPORTED
– NEVER
– NESTED

– REQUIRED

Kısaca; aktif bir transaction varsa ona bağlanır. Yoksa yeni açar.

@Transactional anotasyonunun default değeri “required” dır. Yani hiç propagation belirtilmezse “required” olarak davranır.
Bu propagation ile ilgili yerin transactional olması garanti edilmiş olur. Eğer daha öncesinde bir transaction var ise, commit işlemi için önceki transactionın bitmesi beklenir. Ama daha önce bir transaction yoksa ve transaction burada açılmışsa, işlem bittiğinde transaction da sonlanır ve commit işlemi yapılır.

– SUPPORTS

Kısaca; aktif bir transaction varsa ondan devam eder, yoksa non-transaction olarak devam eder.
Bu propagation ile önceki durum desteklenir. Yani önceden ne ise, o şekilde devam edilir.

– MANDATORY

Kısaca; aktif bir transaction varsa ondan devam eder, yoksa hata verir.
Bu propagation kendinden öncesinde muhakkak bir transaction açılmış olmasını bekler. Eğer açılmamışsa hata verir ve işleme devam etmez.

– REQUIRES_NEW

Kısaca; aktif bir transaction varsa onu askıya alır ve yeni bir transaction oluşturur.
Bu propagation ise öncesinde bir transaction olsun ya da olmasın, yeni bir transaction başlatır. Eğer öncesinde varsa onu askıya alır. Başlatılan transaction bittiğinde kendi içinde yapılan işlemleri commitler. Öncesinde bir transaction varsa, onun verileri de o transaction bittiğinde commitlenir.

Bununla alakalı sizleri uyarmak istediğim bir nokta var. Bir metot için propagationı requires_new seçtiyseniz ve bu metodu yine aynı classtan çağırıyorsanız, Spring yeni bir transaction açamıyor. Bunun nedeni transactionların proxy-based olması. Bu şekilde çalışmak istiyorsanız, muhakkak başka bir service in içinden bu metodu çağırmalısınız.
Daha fazla detay için tıklayınız.

– NOT_SUPPORTED

Kısaca; aktif bir transaction varsa onu askıya alır ve non-transactional olarak yürütülür.
Bu propagation ile işlemin non-transactional yürütüleceği garanti edilmiş olur. Özellikle transactional olması istenmeyen yerlerde bu propagation ı kullanılabilir.

– NEVER

Kısaca; aktif bir transaction varsa hata fırlatır.
Bu propagation kendinden önce bir transaction olmaması gerektiğini belirtir. Daha önceden bir transaction varsa, işleme devam etmez ve hata verir.

– NESTED

Kısaca; Halihazırdaki transactiona ait olan iç içe bir transaction başlatır. Bu iç içe çağrılar arasına savepointler koyar. Yani içteki transactionlar dıştakinden bağımsız olarak geri alınabilirler.

JPA dialect, Hibernate vs bunu desteklemez. (NestedTransactionNotSupportedException: JpaDialect does not support savepoints – check your JPA provider’s capabilities)

Tüm bu örnekleri teker teker denediğim bir controller yazdım. Toplamda 21 tane senaryo var. Bu örnekleri incelemek isterseniz: TransactionalController.java