Springboot

@Transactional원리

25G 2023. 8. 14. 18:11

@Transactional

spring에서 디비접근시에 트랜잭션을 관리하는 방법은 아주 추상화가 잘 돼 있습니다. 바로 @Transactional 을 사용해주면 트랜잭션이 되기 때문입니다.
이 마법의 내부를 공부해 봤습니다.

Transaction 동작 원리

jdbc가 트렌잭션을 다루는 코드

import java.sql.Connection; Connection connection = dataSource.getConnection(); // (1) 
try (connection) { connection.setAutoCommit(false);
 // (2) 
 // execute some SQL statements... 
 connection.commit(); 
 // (3)
  } catch (SQLException e) 
  { connection.rollback(); // (4) 
  }
  1. 데이터베이스 커넥션
  2. 자바에서 데이터 베이스의 트랜잭션을 시작하는 유일한 방법입니다 .setAutoCommit(true)는 모든 sql statement를 래핑합니다. 즉 자동커밋,롤백 시전
    false는 이와 반대로 트랜잭션의 주인이 내가 된다. 즉 제어를 내가하고 내가 원할대 커밋 또는 롤백합니다.
  3. commit을 합니다.
  4. 또는 롤백을 합니다. 물론 예외가 발생했을때에만
    이것이 jdbc의 기본적인 트랜잭션 방법, 자바에서 트랜잭션을 시작하는 유일한 방법이기때문에 @Transactional또한 이렇게 동작합니다.
    @Transactional(propagation=TransactionDefinition.NESTED, isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
    
트랜잭션 격리 레벨이 중첩되었을 때의 동작보다 격리레벨이 아래와 같은 기본 jdbc코드로 정리된다는 것을 아는게 중요하다.

## Spring Transaction Management의 동작방식
프로그래밍스타일 트랜잭션 관리 방법.

@Service
public class UserService {
@Autowired private
TransactionTemplate template;

public Long registerUser(User user) {
Long id = template.execute(status -> {
// SQL 실행
// ex) inserts the user into the db and returns the autogenerated id
return id;
});
}
}

위와같이 TransactionTemplate를 사용하거나 직접 PlatfromTransactionManager를 이용하면 된다.
- 데이터베이스 커넥션을 직접 열고 닫을 필요가 없습니다. 대신에 트랜잭션 콜백을 사용합니다.
- SQLExceptions를 잡을 필요가 없습니다. 스프링이 알아서 RuntimeException으로 변환해줍니다.
- 스프링 환경에 더 적절하고 TrancationTemplate은 내부적으로 PlatfromTransactionManager를 사용합니다. 모든것이 Spring context configuration에서
지정해야하는 빈들이지만 나중에 수정할 필요가 없습니다.

프로그래밍적인 방식은 잘 사용안한다. 예시일뿐
## @Transactional 사용시 내부 코드
```java
public class UserService {
    public Long registerUser(User user) {
        Connection connection = dataSource.getConnection(); // (1)
        try (connection) { connection.setAutoCommit(false); 
            // (1) 
            // execute some SQL that e.g. 
            // inserts the user into the db and retrieves the autogenerated id 
            // userDao.save(user); <(2) 
            connection.commit(); // (1) 
             } catch (SQLException e) {
            connection.rollback(); 
            // (1)
            }
    }
}

위와같이 @Transactional을 사용하면 위에 트랜젝션을 관리하는 코드가 자동으로 삽입이되면서 트렌젝션 관리를 해주게 됩니다.

스프링이 위와같이 코드를 넣는 방법

스프링은 기본적으로 ioc를 사용한다.
그럼 위 UserService를 인스턴스화 할때 해당 클래스의 트랜잭션 프록시도 인스턴스화 하는것입니다.그렇게 CGLIB라이브러리(인터페이스가 없는 클래스를 프록시패턴적용시사용하는 라이브러리)
의 도움을 받아서 프록시를 통하는 방식으로 마치 코드를 넣은 것 처럼 동작하게 한다.
모든 트랜잭션(open,commit,close) 를 처리하는 것은 프록시 자체에서가 아니라 트랜잭션 매니저에 위임하여 처리하는 것!

  1. 스프링은 @Transactional 을 발견하면 그 빈의 다이나믹 프록시를 만들어준다.
  2. 그 프록시 객체는 트랜잭션 매니저에 접근하고 트랜잭션이나 커넥션을 열고 닫도록 요청한다
  3. 트랜잭션 매니저는 JDBC 방식으로 코드를 실해해 준다.

물리적 트랜잭션과 논리적 트랜잭션 차이

  • 물리적 트랜잭션: 실제 jdbc트랜잭션
  • 논리적 트랜잭션 : @Transactional 로 중첩된 메서드

@Transactional Propagtion Level

트랜잭션 전파 레벨에는 여러가지가 있다.

  • Required(디폴트) : 메소드는 트랜잭셔능ㄹ 필요로 해. 트랜잭션을 새로 하나 열든지, 기존에 있던 것을 쓰든지 한다.
  • Supports : 트랜잭션을 열든지 말든지 상관안하고 잘 실행한다 = jdbc는 아무것도 안한다.
  • Mandatory: 스스로 트랜잭션을 열진 않을거지만, 아무도 트랜잭션을 열지 않으면 안됨 = jdbc는 아무것도 안한다.
  • Required_new: 온전히 내 소유의 트랜잭션이 필요
  • Not_Supported: 이미 실행중인 트랜잭션이 있으면 중지 = jdbc는 아무것도 안한다.
  • Never: 트랜잭션 시작하면 안됨 = jdbc아무것도 안한다.
  • Nested: 저장점을 잡아줌 savapoints
    결과적으로 jdbc가 들어가냐 안들어가냐만 이해하면된다.

@Transactional Isolation Level

데이터베이스의 격리 수준은 복잡한 주제다. 트랜잭션중에 격리 수준을 전환할때 데이터베이스나 jdbc드라이버에서 기능이 지원되는지를 분명하게 먼저 확인해야할 필요가
있습니다. 프록시를 통하지 않고 다시말해 ioc에게 제어권을 넘기지 않고 @Transactional이 붙은 내부 메소드를 호출하면 ioc에 등록되기전 가로첼때 프록시가
작동할 수 없기때문에 적용되지 않습니다.

  • db격리 수준
    • READ UNCOMMITTED
    • READ COMMITTED
    • REPEATABLE READ
    • SERIALIZABLE
      아래로 내려갈수록 트랜 잭션간 고립 정도가 높아지며 성능이 떨어지는 것이 일반적이다.
      일반적인 온라인 서비스에서는 READ COMMITTED나 REPEATABLE READ 중 하나를 사용한다.
      (oracle = READ COMMITTED, mysql = REPEATABLE READ)

spring과 jpa(hibernate) Transaction Management동작

  • 하이버네이트는 스프링의 @Transactional을 모른다.
  • 스프링의 @Transactional 은 하이버네이트 트랜잭션을 모른다.
    위와같은 생각을 할 수 있지만 저 둘은 서로 알고 있습니다. 둘다 유일한 방법인 jdbc 기본 방식을 사용하기 때문!
    다만 서로 인지할수 있게 스프링과 하이버네이트의 통합은 이뤄저야 한다.HibernateTransactionManager 사용두트랜 잭션 통합하는 문제를 고치는 방법은 DataSourcePlatformTransactionManager 대신 HibernateTransactionManager 를 쓰는 것이다.
    JPA를 통해 Hibernate를 사용한다면 JpaTransactionManager를 사용하면 된다.
    HibernateTransactionManager 는 하이버네이트를 직접 사용할 때 트랜잭션을 관리하고 JpaTransactionManager는 jpa를 통해서 간접적으로 사용할 때 트랜
    잭션 관리를 합니다.
    스프링에서는 spring-boot-starter-data-jpa같은 라이브러리를 쓰면 자동으로 JpaTransactionManager를 씁니다.

정리

어떤 기술을 쓰든 jdbc 기본 (getConnection(), setAutoCommit(false), commit())이라는 것이다.

주의 사항

트랜잭션이 적용되는 method에서는 try - catch문을 사용하시면 안됩니다.
RuntimeException이 발생해 롤백이 되어야하는 상황에서 catch문에 걸려버리면 spring transaction rollback로직까지 실행되지 않고 끝나버리기 때문에 
주의하셔야합니다.

참고 사이트

https://jeong-pro.tistory.com/228

'Springboot' 카테고리의 다른 글

Springboot JUnit and Mockito  (0) 2023.08.14
@Deprecated와 @deprecated  (0) 2023.08.14
Spring boot AOP  (0) 2023.08.14
Spring Data REST  (0) 2023.08.14
@Aspect Springboot AOP 시작  (0) 2023.08.14