Spring Transaction Yönetimi Nasıl Uygulanır?
Spring framework geliştiricilere, transaction yönetimlerini daha kolay ve rahat yapabilmeleri için @Transactional anotasyonu ile çeşitli implementasyonlar sağlamaktadır. İçeriğimizin devamında @Transactional, propagation, rollbackFor ve noRollbackFor kavramlarından detaylıca bahsedilmektedir.
Spring transaction yönetiminin nasıl olduğundan bahsetmeden önce Transaction nedir ve ACID prensipleri nelerdir bunlardan bahsetmek daha faydalı olacaktır.
Transaction Nedir?
Veritabanı üzerinden birden çok yapılan işlemlerin(create, update, insert, delete olabilir), bir bütün olarak veritabanına işlenmesi hata halinde ise yapılan tüm işlemlerin geri alınmasını garanti eden operasyonlar dizisidir. Transaction sayesinde veritabanındaki kayıtlarda tutarlılık ve bütünlük sağlanmış olur.
ACID Prensipleri Nelerdir?
ACID tanımı için transaction’lar/veritabanı işlemleri ile ilgili tanımlanmış standart kurallar ve prensiplerdir diyebiliriz.
Atomicity
İşlemlerin tümünün başarılı olmasını, hata durumunda ise tüm işlemlerin geri alınmasını sağlar. Tüm işlemler başarılı olduğunda commit işlemi ile birlikte “başarılı” şeklinde işaretlerken, işlemlerden herhangi birinde(update, delete, insert vs. olabilir) rollback ile tüm başarılı işlemlerin de geri alınması prensibine dayanır.
Consistency:
Veritabanındaki verilerin tutarlı olmasını açıklayan prensiptir diyebiliriz. Bir transaction içerisinde birden fazla operasyon olduğunu varsayalım. Şöyle bir senaryo düşünelim; createPost methodumuz olsun. Bu metod içerisinde operasyon1 başarılı, operasyon2 başarısız olduğunda verilerde tutarsızlık oluşacağından, operasyon1'in de rollback edilmesi amaçlanır.
Isolation:
Her transaction birbirinden bağımsız çalışır. Transaction sonlanıncaya kadar farklı bir transaction bu durumdan etkilenmez.
Durability:
Yapılan işlemlerde hata alınması senaryosunda işlemlerin tümünün geri alınmasını garanti eder.
Spring Transaction Yönetimi Nedir?
Spring framework hayatımızda yokken, transaction yönetimini yapmak çok daha zorluydu. Bunu aşağıdaki örnek üzerinden inceleyelim.
“Begin Transaction -> Operation 1 -> Operation 2-> Rollback/commit”
Spring framework’unden önce yeni bir transaction başlattıktan sonra yukarıdaki örnekteki gibi operation1 ve operation2 işlemleri başarılı ise commit ile işaretle, eğer herhangi birinde hata alındıysa rollback ile işaretleyerek başarılı işlem dahi olsa bunların tümünü geri al şeklinde o esnadaki işin durumuna yönelik logic’ler yazmamız gerekiyordu.
Fakat spring framework ile beraber transaction yönetimleri çok daha kolaylaştı. @Transactional anotasyonu ile tüm transactionların scope’larını yönetebiliyor ve manuel şekilde commit/rollback işlemlerini yapmaya ihtiyaç duymuyoruz. Spring bu anotasyonun arkasında Aspect programlamaya dayalı olarak tüm transaction işlemlerini bizim yerimize handle ediyor. Böylelikle begin transaction yazma işleminden bizi kurtarıp, kendisi bir transaction başlatıyor ve tüm işlemler sonuçlandığında ise commit ya da rollback işlemini gerçekleştiriyor.
@Transactional anotasyonu hem class seviyesinde hem de method seviyesinde kullanılabilir. Eğer class seviyesinde kullanılıyorsa, ilgili sınıftaki tüm methodlar için aynı transaction kullanımı kabul edilmiş olur. Ayrıca spring framework için önemli olan bir diğer konu ise; @Transactional anotasyonunun private methodlar üzerinde kullanılmamasıdır. Private method’larda kullanıldığı takdirde spring framework bu durumu ignore’layacağından transaction devre dışı kalacaktır.
@Transactional anotasyonunu genellikle propagation ve rollbackFor attribute’larıyla kullanırız. Aşağıda bu kavramlardan detaylıca bahsedilmektedir.
Transactional Propagation Özelliği:
Propagation özelliği sayesinde mevcut transaction’ın kullanılıp kullanılmayacağını, yeni bir transaction başlatılıp başlatılmayacağı gibi pek çok özellik kullanılabilir.
Propagation.REQUIRED:
@Transactional(propagation = Propagation.REQUIRED): @Transactional anotasyonunun varsayılan propagation değeridir. Eğer mevcutta bir transaction varsa, yeni bir transaction başlatmaz. Fakat bir transaction yoksa yeni bir transaction başlatır.
Propagation.REQUIRES_NEW:
@Transactional(propagation = Propagation.REQUIRES_NEW): Hali hazırda bir transaction varsa, o transaction’ı suspend eder ve yeni bir transaction açar.
Propagation.NEVER:
@Transactional(propagation = Propagation.NEVER): Mevcutta bir transaction varsa, exception fırlatır. Bu özelliğe sahip method, transactional bir method tarafından çağrılmak istenmez diyebiliriz.
Propagation.MANDATORY:
@Transactional(propagation = Propagation.MANDATORY): Eğer mevcutta bir transaction yoksa, exception fırlatır.
Propagation.SUPPORTED:
@Transactional(propagation = Propagation.SUPPORTS): Eğer mevcutta bir transaction varsa, bu transaction’ı kullanır. Eğer bir transaction yok ise yeni bir transaction başlatmadan çalışır.
Propagation.NOT_SUPPORTED:
@Transactional(propagation = Propagation.NOT_SUPPORTED): Eğer bir transaction varsa, bu transaction’ı suspend eder ve transaction’sız bir şekilde çalışır.
Transactional RollbackFor Özelliği:
Transactional rollback özelliği ile hangi exception fırlatıldığında tüm işlemlerin rollback edileceğini belirtebiliriz. Bu özelliği checked exception fırlatıldığı durumlarda kullanırız.
Şöyle bir senaryomuz olduğunu farzedelim: bir blog sistemimiz olsun. Post eklemek için createPost methodumuzu çağıralım, bu metod içerisinde ise Tag servisimizdeki createTag metodu çağırılsın. İlgili senaryo için aşağıdaki service class’ı incelenebilir.
@Service
public class PostServiceImpl implements PostService {
private static final Logger logger = LoggerFactory.getLogger(PostServiceImpl.class);
private final PostRepository postRepository;
private final TagService tagService;
private final PostDtoMapper postDtoMapper;
public PostServiceImpl(PostRepository postRepository, TagService tagService, PostDtoMapper postDtoMapper) {
this.postRepository = postRepository;
this.tagService = tagService;
this.postDtoMapper = postDtoMapper;
}
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = FileNotFoundException.class)
public PostResponse createPost(PostCreateRequest postCreateRequest) throws Exception {
logger.info("req: {}", postCreateRequest);
Post post = postDtoMapper.convertReqToEntity(postCreateRequest);
Post savedPost = postRepository.save(post);
logger.info("===> Post Id: {}", savedPost.getId());
TagResponse tagResponse = postCreateRequest.getTagResponse();
TagCreateRequest tagCreateRequest = TagCreateRequest.builder()
.name(tagResponse.getName())
.build();
tagService.createTag(tagCreateRequest);
PostResponse postResponse = postDtoMapper.convertEntityToResp(savedPost);
return postResponse;
}
@Override
public List<PostResponse> findAll() {
return postRepository.findAll().stream()
.map((post) -> postDtoMapper.convertEntityToResp(post))
.collect(Collectors.toList());
}
}
createTag metodunda bir checked exception(compile time exception) fırlatıldığında, post tablosuna create gerçekleşmiş fakat tag tablomuza create işlemi gerçekleşmemiş olur. Bu durum ise ACID’ın consistency prensibine yani veri tutarlılığına aykırı olacaktır. Bunu önlemek için ise rollbackFor özelliğinden yararlanıp, ilgili Exception fırlatıldığında tüm işlemlerin rollback yapılmasını yani post tablosuna da kaydın eklenmemesini sağlayabiliriz.
RollbackFor özelliği ile ilgili bir diğer önemli husus ise, unchecked exception’lar yani runtime exception’lardır. Eğer createTag metodu içerisinde unchecked/runtime exception fırlatırsak, rollback işlemi otomatik olarak devreye girecektir. RollbackFor özelliği kullanmadan dahi tüm rollback işlemleri gerçekleşmiş olacaktır.
Transactional noRollbackFor Özelliği:
noRollbackFor özelliği ise rollbackFor özelliğinin tam tersidir diyebiliriz. Belirli checked exception’lar için rollback işleminin devreye girmesini istemiyorsak, @Transactional(noRollbackFor=PostNotFoundException.class)
şeklinde belirtebiliriz.
Transactional anotasyonu, propagation ve rollbackFor özelliklerini direkt olarak barındıran Post ve tag servislerini içeren sample bir proje oluşturdum. Github’daki projeyi clone’layıp, Readme’de belirtilen case’lerin her birisini canlı olarak proje üzerinde de deneyebilirsiniz.
Spring Transaction Usage Proje Github Kaynak Kod: https://github.com/kayhanoztrk/spring-transaction-usage