@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)
}
- 데이터베이스 커넥션
- 자바에서 데이터 베이스의 트랜잭션을 시작하는 유일한 방법입니다 .setAutoCommit(true)는 모든 sql statement를 래핑합니다. 즉 자동커밋,롤백 시전
false는 이와 반대로 트랜잭션의 주인이 내가 된다. 즉 제어를 내가하고 내가 원할대 커밋 또는 롤백합니다. - commit을 합니다.
- 또는 롤백을 합니다. 물론 예외가 발생했을때에만
이것이 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) 를 처리하는 것은 프록시 자체에서가 아니라 트랜잭션 매니저에 위임하여 처리하는 것!
- 스프링은 @Transactional 을 발견하면 그 빈의 다이나믹 프록시를 만들어준다.
- 그 프록시 객체는 트랜잭션 매니저에 접근하고 트랜잭션이나 커넥션을 열고 닫도록 요청한다
- 트랜잭션 매니저는 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로직까지 실행되지 않고 끝나버리기 때문에
주의하셔야합니다.
참고 사이트
'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 |