@Transaction Explanation

박진석·2025년 2월 8일
0
post-thumbnail

Understanding @Transactional in Spring Boot

Ever wondered how banks like Toss Bank or Kakao Bank ensure your money doesn't vanish into thin air during a transfer? Or how an e-commerce site like Coupang makes sure your order and payment details stay in sync? The secret behind these reliable operations is something called a "transaction," and in Spring Boot, we handle it with a simple annotation: @Transactional.

What is @Transactional?

Think of @Transactional as a promise: either all the operations within a method succeed, or none of them do. It's like sending a package - either it arrives complete at its destination, or it doesn't get delivered at all. There's no middle ground!

Here's a real-world example. Imagine you're transferring money between bank accounts:

@Transactional
public void transferMoney(Account from, Account to, BigDecimal amount) {
    from.withdraw(amount);     // Step 1
    to.deposit(amount);        // Step 2
}

Without @Transactional, you might withdraw money from one account but fail to deposit it in another! With @Transactional, Spring ensures either both operations succeed or neither does, keeping your money safe.

When Should You Use @Transactional?

1. Multiple Database Operations

Consider creating a blog post with tags. You want to save both the post and its tags, or save neither:

@Transactional
public BlogPost createPostWithTags(String content, List<String> tags) {
    BlogPost post = postRepository.save(new BlogPost(content));
    tags.forEach(tag -> tagRepository.save(new Tag(tag, post)));
    return post;
}

2. Update Operations

When updating related data, @Transactional ensures consistency:

@Transactional
public Article update(long id, UpdateArticleRequest request) {
    Article article = repository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException("not found: " + id));
    article.update(request.getTitle(), request.getContent());
    return article;
}

3. Delete Operations with Dependencies

When removing data that has relationships with other data:

@Transactional
public void deleteUserAndPosts(Long userId) {
    postRepository.deleteAllByUserId(userId);  // Delete all user's posts
    userRepository.deleteById(userId);         // Delete the user
}

When Not to Use @Transactional

Not every database operation needs a transaction. Here are some cases where you can skip it:

  1. Simple read operations:
public Article findById(long id) {
    return repository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException("not found"));
}
  1. Single, atomic operations (save):
public void saveSingleArticle(Article article) {
    repository.save(article);
}

Tips for Using @Transactional

  1. Public Methods Only: The annotation only works on public methods. Private methods with @Transactional won't have any transaction management.

  2. Read-Only Operations: If you're only reading data, you can optimize performance:

@Transactional(readOnly = true)
public List<Article> getAllArticles() {
    return repository.findAll();
}
  1. Exception Handling: You can specify which exceptions should trigger a rollback:
@Transactional(rollbackFor = CustomException.class)
public void riskyOperation() {
    // Your code here
}

Common Pitfalls

  1. Self-Invocation: Calling a @Transactional method from within the same class bypasses the transaction management.

  2. Too Many Operations: Keep your transactional methods focused. If you're doing too many things in one transaction, consider breaking it up.

  3. Unnecessary Use: Don't add @Transactional to every method just because you can. Each transaction has overhead.

Takeaway

@Transactional is like a safety net for your DB operations. It ensures data consistency and reliability, especially when multiple operations need to succeed or fail together. Use it wisely, and your application will handle complex operations with grace and reliability. 🚀

0개의 댓글