HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🤩
개발
/
Spring Data
Spring Data
/
🚉
Spring Transaction 관리
🚉

Spring Transaction 관리

Spring의 트랜잭션 관리TransactionManager(프로그래밍적 트랜잭션 관리)TransactionTemplate(프로그래밍적 트랜잭션 관리)@Transactional (선언적 트랜잭션 관리)Transaction PropagationREQUIREDREQUIRED_NEW가 CustomerService에 붙어있을 때SUPPORTSNOT_SUPPORTEDTransaction Isolation Level

Spring의 트랜잭션 관리

  • 스프링 트랜잭션 기능은 전역 트랜잭션으로 동작하는 JTA(Java Transaction API)뿐만 아니라 JDBC API, 하이버네이트 트랜잭션 API, JPA 트랜잭션 API를 포함한 다양한 트랜잭션 API에 대한 추상화를 제공함
    • 후자의 세 개 API는 로컬 트랜잭션과 함께 동작
  • 전역 트랜잭션은 일반적으로 관계형 데이터베이스와 메시지 큐(JMS)와 같은 다양한 트랜잭션 자원과 함께 동작할 수 있음
  • JTA로 전역 트랜잭션을 관리하는 것은 애플리케이션 서버
  • 반면에 예를 들어 JDBC 커넥션과 관련된 트랜잭션인 로컬 트랜잭션은 자원에 한정적임. 로컬 트랜잭션은 다양한 자원들과 함께 동작할 수 없다

TransactionManager(프로그래밍적 트랜잭션 관리)

notion image
  • 위와 같은 구조를 가지고 애플리케이션 계층에서는 PlatformTransactionManager를 보고 활용함. 그 안에 구현체가 무엇이든지 상관없이(JDBC를 쓰던, Hibernate를 쓰던..)
private final PlatformTransactionManager transactionManager; public void testTransaction(Customer customer){ var transaction = transactionManager.getTransaction( new DefaultTransactionDefinition()); try{ jdbcTemplate.update("UPDATE customers SET name = :name WHERE customer_id = UUID_TO_BIN(:customerId);", Map.of("name", customer.getName(), "customerId", customer.getCustomerId().toString())); jdbcTemplate.update("UPDATE customers SET email = :email WHERE customer_id = UUID_TO_BIN(:customerId);", Map.of("email", customer.getEmail(), "customerId", customer.getCustomerId().toString())); transactionManager.commit(transaction); }catch (DataAccessException e){ logger.error(e.getMessage()); transactionManager.rollback(transaction); } }
  • 이와 같이 transaction을 받아와서 commit, rollback을 일일히 진행해주어야 함 → Template화 진행

TransactionTemplate(프로그래밍적 트랜잭션 관리)

transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { jdbcTemplate.update("UPDATE customers SET email = :email WHERE customer_id = UUID_TO_BIN(:customerId);", Map.of("email", customer.getEmail(), "customerId", customer.getCustomerId().toString())); jdbcTemplate.update("UPDATE customers SET name = :name WHERE customer_id = UUID_TO_BIN(:customerId);", Map.of("name", customer.getName(), "customerId", customer.getCustomerId().toString())); } });

@Transactional (선언적 트랜잭션 관리)

[ 우아한형제들 기술블로그 ] CheckedException과 UncheckedException 에 대한 default rollback 전략
  • 만약에 Repository의 메서드에 @Transactional 을 붙이면 이 Repository가 bean으로 만들어 질 때 spring에서 proxy객체로 만들어줌(Spring AOP 프레임워크 기반). 그리고 이것을 쓰는 곳에서 Repository의 proxy객체를 이용하게 됨
  • 주로 service단에 @Transactional 을 많이 붙임. 왜냐하면 repository의 insert, update, delete등이 한 군데 모이는 곳이고 걔네들을 한번에 처리하려고 하기 때문
  • 해당 어노테이션을 사용하려면 @EnableTransactionManagement 을 달아주어야 함(스프링 부트에서는 필요 없다). @Configuration같은 곳에
  • [ default 설정 ] checkedException에 대해서는 rollback 안하고, uncheckedException은 rollback 한다.
    • checked는 알고 있는 에러가 터지는 거라 rollback 안하고, unchecked는 예상 못한 에러라 rollback 하는 것.. (default 전략은 그렇다)

Transaction Propagation

  • @Transactional 붙은 메서드 안에 또 다른 @Transactional이 붙은 메서드가 있을 때 적용됨
notion image
notion image
notion image

REQUIRED

notion image

REQUIRED_NEW가 CustomerService에 붙어있을 때

notion image

SUPPORTS

  • 트랜잭션이 있는 곳에서 트랜잭션(support)를 부르면 해당 트랜잭션 사용하고, 바깥에 부른 곳에서 트랜잭션이 없으면 트랜잭션 사용 안하고.

NOT_SUPPORTED

notion image

Transaction Isolation Level

https://techannotation.wordpress.com/2014/12/04/5-minutes-with-spring-transaction-isolation-level/
  • READ_UNCOMMITED : 트랜잭션에서 처리 중인 커밋되지 않은 데이터를 다른 트랜젝션에서 읽는 것을 허용하는 것
  • READ_COMMITED : 트랜잭션이 커밋이 되어서 확정된 데이터만 읽도록
  • REPEATABLE_READ : 먼저 발생한 트랜잭션이 읽은 데이터는 다른 트랜잭션에 업데이트하거나 삭제하는 것을 방지 해줌. 같은 데이터를 한 트랜잭션 내에서 여러번 쿼리 했을 때 일관성이 있는 것
  • SERIALIZABLE : 추가되는 것도 막아줌. 이전 것은 update, delete를 방지하고, 이 단계에서는 추가되는 것 까지 막아주는 것
  • @Transactional의 Isolation.DEFAULT 는 사용하는 DB의 설정을 따르겠다는 의미임
  • dirty read : 커밋되지 않은 수정 중인 데이터를 다른 트랜잭션에서 읽을 수 있을 때 발생함
  • non-repeatable reads : 한 트랜 잭션 내에서 같은 쿼리를 실행할 때, 일관되지 않은 결과가 발생하는 것
  • phantom reads : 한 트랜잭션 안에서 일정범위의 쿼리를 읽어올 때, 첫 번째 쿼리에는 없던 데이터가 두 번째 쿼리에는 생기는 현상(유령 레코드가 생기는 것)