SimplaJpaRepository 에 @Transactional 이 명시되어 있기 때문에
해당 내용은 SpringFramework 의 @Transactional 어노테이션과 관련이 깊다.
개인적으로 알고 있는 내용이었는데, 다시 찾아보는 과정에서 책 2권정도 다시 펴보고 기록한다.
- 우선순위에 따라 클래스에 있는 @Transactional(readOnly = true) 보다 메서드에 선언된 @Transactional 의 설정이 우선시 된다.
- 결과적으로 select 쿼리를 사용하는 메서드에는 readOnly 속성이 true 잡히고, update insert delete 의 DML 에는 @Trasactional 의 readOnly 속성이 false 이므로 성능 상 이득을 가져다 주는 것으로 기억한다.
@Repository
@Transactional(
readOnly = true
)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
...
@Transactional
public void delete(T entity) {
Assert.notNull(entity, "Entity must not be null!");
if (!this.entityInformation.isNew(entity)) {
Class<?> type = ProxyUtils.getUserClass(entity);
T existing = this.em.find(type, this.entityInformation.getId(entity));
if (existing != null) {
this.em.remove(this.em.contains(entity) ? entity : this.em.merge(entity));
}
}
}
...
}
전의 기록에서 기술했듯 JpaRepository 의 구현체인 SimpleJpaRepository 에 @Transactional 이 붙어있는데 붙이는 것은 중복이다.
그러므로 우선순위가 가장 높은 메서드 레벨에서 @Transactional 을 적어주는 것이 좋아보인다.
- Spring Data JPA 에서는 @Transactional 의 readOnly 속성이 true 일 경우 Flush(데이터베이스 싱크) 모드를 NEVER 로 설정해준다. 즉, 데이터베이스에 Entity 데이터를 동기화하지 않는다.
- 데이터베이스에 싱크(Flush) 하지 않아 DirtyChecking(Entity 객체 변화감지)을 할 필요가 없어지니 결과적으로 성능 상의 이득을 가져가게 된다.
public interface AccountRepository extends JpaRepository<Account, Long> {
@Transactional(readOnly = true)
// readOnly 가 true 일 경우 Flush 모드를 NEVER 로 설정해서 데이터베이스 싱크를 안하게 된다.
// 그러므로 DirtyChecking 으로 Entity 변경감지를 할 필요가 없다.
// 성능 상의 이득을 취할 수 있다.
Optional<Account> findByName(String name);
}
@Transcational 어노테이션에 대한 복습 과정
여기서 부터는 Spring 5 기준 @Transactional 어노테이션에 대한 개인 복습 과정을 다룬다.
참고한 서적은 최범균 님의 Spring5 프로그래밍 입문, 구멍가게 코딩단 님의 코드로 배우는 스프링 웹 프로젝트 개정판, 개인적으로 알게 된 지식이다.
- 스프링은 내부적으로 SQL Exception 이나 데이터베이스나 프로그램 고유(Mybatis, JdbcTemplate, JPA, Hibernate) Exception 들을 DataAccessException 을 상속한 예외로 변환해서 전파시킨다.
- 이유는 Exception 처리 과정을 하나로 통일하기 위함이다. 각 예외마다 개발자가 처리해야할 예외처리 코드를 기술에 상관없이 하나로 통일하기 위함이다.
- 이 쯤에서 개발자가 처리할 예외, 라고하는 것은 RuntimeException , CheckedExcpetion 으로 나뉜다. 필수 적인 예외처리 코드를 기술하지 않기위해 DataAccessException 은 RuntimeException 이다.
- 즉 @Transactional 을 기술한다는 것은, 해당 메서드를 하나의 Transaction 으로 간주하겠다는 것과 같다. 또한 TransactionManager 의 롤백 기본 값은 DataAccessException, 즉 RuntimeException 상속 예외 발생 시에 기본 적으로 발생되는 것이다.
- @Transcational 이 기술 되면 TransactionManager가 트랜젝션을 관리한다. ( 개인적으로 사용해본 것은 PlatformTransactionManager의 구현체인 DataSourceTransactionManager 이며, JPA는 내부적으로 JpaTranscationManager 를 사용한다고 한다. 소스를 열어보니 AbstractPlatformTranactionManager 를 상속 중이다. AbstractPlatformTranscationManager 는 PlatformTranasctionManager, Serializable 을 구현 중인 것으로 확인되었다.)
@Transactional , Proxy
- 로그를 자세히보면 TransactionManager 가 트랜잭션을 Commit 하거나 Rollback 하는 로깅을 볼 수 있다.
- 이 과정을 실행하는 것이 Proxy Bean 이며 이는 Spring 이 Transaction 을 처리할 때 AOP를 사용한다는 것으로 이해할 수 있다.
- Transaction 도 공통적으로 일어나는 Cross-Concern 이니 충분히 이해할 수 있는 대목이다.
- @EnableTransactionManagement 선언 시 Spring 은 @Transactional 이 기술된 Spring Bean 을 찾아 Proxy 객체를 생성한다.
- @Transactional 이 붙은 메서드가 실행되면 PlatformTransactionManager 구현체가 작동하여 트랜잭션을 시작하고 PlatformTransactionManager Bean 이 감싸고 있던 @Repository 객체의 메서드를 호출한 뒤 작업 여하에 따라 Commit , Rollback 시키는 것이다.
SQLException은 RuntimeException 이 아니다.
즉 TransactionManager 에 의해 기본 적으로 롤백처리가 되지않는다는 것이다.
- @Transactional 의 속성인 rollbackFor , noRollbackFor 속성에 Exception 클래스를 선언하면 원하는 Exception 에 대해 롤백 여부를 지정해 줄 수 있게 된다.
@Transactional(rollBackFor = SQLException.class)
public void someMethod(){
...
}
사실 알아야할 것의 중요도는 Isolation(격리레벨) , Propagation(전파레벨) 설정이라고 생각한다.
@Transactional 의 Isolation , Propagation 레벨 설정
Propagation 열거 타입
- 기본 값은 Propagation.REQUIRED.
- Propagation.REQUIRED : 메서드 수행 시 트랜젝션이 필요하다. 진행 중인 트랜잭션이 있다면 진행하며 없다면 새로운 트랜잭션을 만든다.
- Propagation.MANATORY : 메서드 수행 시 반드시 트랜잭션이 필요하다. 진행 중인 트랜잭션이 있다면 진행하며, 없다면 예외를 던진다.
- Propagation.REQUIRES_NEW : 메서드 수행 시 언제나 새로운 트랜잭션을 생성한다. 진행 중인 트랜잭션이 있다면 중단하고 새로운 트랜잭션이 끝나면 다시 실행한다.
- Propagation.SUPPORTS : 트랜잭션이 반드시 필요하지는 않다. 진행 중인 트랜잭션이 있다면 진행하며 없어도 동작한다.
- Propagation.NOT_SUPPORTS : 트랜잭션이 필요하지 않다. 진행 중인 트랜잭션이 있다면 중단하고 메서드 종료 후 다시 실행한다.
- Propagation.NEVER : 트랜잭션이 반드시 필요하지 않다. 진행 중인 트랜잭션이 있다면 예외를 던진다.
- Propagation.NESTED : JDBC 3.0 드라이버 사용시에만 적용, 진행 중인 트랜잭션이 있다면 중첩된 트랜잭션을 실행하며 없다면 새로운 트랜잭션을 만든다.
Isolation 열거 타입
- 기본 값은 Isolation.DEFAULT. 사실 상 격리레벨을 풀면 풀 수록 성능은 증가한다. 반대로 격리레벨이 올라갈 수록 성능은 저하 된다.
- Isolation.DEFAULT : 데이터 베이스의 기본 격리 레벨을 사용한다. H2, PostgresQL 기준 기본 격리 레벨은 READ_COMMITED 이다.
- Isolation.UNCOMMITED : 다른 트랜젝션이 Commit 시키지 않은 데이터를 읽어올 수 있다.
- Isolation.READ_COMMITED : 다른 트랜잭션이 Commit 한 데이터를 읽을 수 있다.
- Isolation.REPEATABLE_READ : 동일한 필드에 대해 다중 접근 시 모두 같은 값을 보장한다.
- Isolation.SERIALIZABLE : 동일한 데이터에 대하여 동시에 2개 이상의 트랜잭션을 진행 할 수 없다. 즉 하나의 트랜잭션 완료시 까지 모든 트랜잭션들은 블로킹 상태가 된다.
@Transcational 의 우선 순위
- 메서드에 기술된 @Transcational = 1 순위
- 클래스에 기술된 @Transcational = 2 순위
- 인터페이스에 기술된 @Transactional = 3 순위
'springframework > Spring Data JPA' 카테고리의 다른 글
Spring Data JPA : Enumeration Mapping (0) | 2020.11.19 |
---|---|
Spring Data JPA : Auditing, Life Cycle Event (0) | 2020.11.19 |
Spring Data JPA : Query By Example (0) | 2020.11.19 |
Spring Data JPA : Specifications (0) | 2020.11.19 |
Spring Data JPA : Projection (0) | 2020.11.19 |