springframework/Spring Data JPA

Spring Data JPA : Transaction

Jungsoomin :) 2020. 11. 19. 19:32

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 으로 나뉜다. 필수 적인 예외처리 코드를 기술하지 않기위해 DataAccessExceptionRuntimeException 이다.
  • @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 시키는 것이다.

학습시 JdbcTemplate 를 사용했었다.

 


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 순위