SpringBoot

[Spring] 트랜잭션(Transaction) 이란?

EJUN 2023. 7. 2. 15:03

트랜잭션(Transaction)이란?
여러 작업을 진행하다가 문제가 발생한 경우 문제가 발생하기 전의 상태로 롤백(RollBack)하기 위해 
사용되는 것이다.
다른 말로 더 이상 쪼갤 수 없는 최소 작업 단위를 의미

 

트랜잭션의 작업에는 크게 2가지가 있다.

  • 커밋(Commit) -> 작업이 마무리 된 것을 의미
  • 롤백(RollBack) -> 작업을 진행 중 문제가 발생하여 작업중이던 작업을 취소하고 이전의 상태로 되돌리는 것을 의미

 

Spring에서는 트랜잭션과 관련해서 몇 가지 기술을 제공하고 있다.

  • 트랜잭션 동기화
  • 트랜잭션 추상화
  • AOP를 이용한 트랜잭션 분리(선언적 트랜잭션)

 

트랜잭션 동기화

개발자가 여러개의 작업을 하나의 트랜잭션으로 관리하려면 Connection객체를 공유해야하는 불필요한 작업이 생긴다.

그래서 Spring은 이를 방지하기 위해 트랜잭션 동기화 기술을 제공한다.

 

트랜잭션 동기화란?

트랜잭션을 시작하기 위한 Connection객체를 특별한 저장소에 저장하고 필요할 때마다 꺼내서 쓸 수 있도록

해주는 기술을 의미한다.

 

public class UserService {
    public void getUser(final Connection connection,String name){
        try{
            String str="SELECT * FROM user";
            PreparedStatement preparedStatement=connection.prepareStatement(str);
        }
        catch (SQLException e){
            e.printStackTrace();
        }
    }
}

위의 코드처럼 트랜잭션 동기화를 사용하지 않으면 파라미터로 Connection객체를 Service로직뿐만 아니라

Repository로직에서도 파라미터를 통해 Connection객체를 공유하는 상황이 발생한다.

이때 트랜잭션 동기화를 사용하는데 Connection을 가져올 때 Datasource가 필요하므로 

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=1234

위처럼 applicartion.properties에 설정을 해주어야 한다.

 

이렇게 하면 Connection객체를 파라미터로 받지않고 코드를 작성할 수 있다.

TransactionSynchronizationManager(트랜잭션 동기화 매니저)을 이용하여 Connection객체를 저장한 저장소를 이용할 수 있다.

public class UserService {
    private final DataSource dataSource;

    public UserService(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void getUser(String name) throws SQLException {
        TransactionSynchronizationManager.initSynchronization();//트랜잭션 동기화 초기화
        
        Connection connection= DataSourceUtils.getConnection(dataSource); //커넥션 획득
        
        try{
            connection.setAutoCommit(false); 
            connection.commit();
        } catch (SQLException e) {
            connection.rollback();
            e.printStackTrace();
        }
        finally {
            DataSourceUtils.releaseConnection(connection,dataSource); //커넥션 해제
        }
    }
}

위의 코드를 설명하면

TransactionSynchronizationManager.initSynchronization();

위 코드를 통해 트랜잭션 동기화를 한다.

Connection connection= DataSourceUtils.getConnection(dataSource);

그런 다음 DataSourceUtils를 통해 Connection객체를 생성한다.

이렇게 하면 트랜잭션 동기화를 사용할 수 있다.

트랜잭션 동기화 과정

처음에 TransactionSynchronizationManager클래스를 생성하면 오른쪽처럼 connection객체를 저장하는 저장소가 생기고, DataSourceUtils.getConnection()을 이용하면 트랜잭션 동기화매니저에 저장한 connection객체를 로직에서 꺼내서 사용한다.

이 덕분에 여러개의 도메인 로직에서는 동일한 connection객체를 파라미터로 전달받지 않고 사용할 수 있다.

이 connection객체를 다 사용했으면 다시 저장소에 보내야할 때도 DataSourceUtils.releaseConnection()을 통해

트랜잭션 동기화 매니저에 있는 connection객체를 지울 수 있다.

connection객체 반환

하지만 트랜잭션 동기화를 통해 파라미터를 통해 connection객체를 받는 것은 해결되었지만 아직 문제가 남았다.

바로 위의코드는  JDBC에 의존적인 코드라는 점이다.

이를 해결하기 위해 Sping에서 제공하는 트랜잭션 추상화 라는 기술이 있다.

 

트랜잭션 추상화

트랜잭션 동기화 기술의 문제는 데이터 접근 기술이 의존적이라는 문제가 발생한다.

즉, 개발자가 사용하는 DB가 달라자면 Service로직의 코드 또한 변경이 생긴다. 이는 자바의 OCP원칙에 위배된다.

 

현재 위의 코드에서 사용하는 DataSourceUtils는 JDBC에서 사용하는 클래스이다.

트랜잭션 동기화 문제

위의 사진처럼 DB마다 사용하는 접근클래스들이 다르기 때문에 사용하는 DB가 변경되면 데이터에 접근하는 클래스 또한

변경을 해주어야 한다.

 

위의 3가지 DB의 트랜잭션과정을 보면 모두 다 트랜잭션을 가져와서 생성, Commit, Rollback 하는 것처럼 동일한 작업을 하고 있다. 즉 실제로 수행하는 일은 모두 똑같은 것을 알 수 있다.

트랜잭션 추상화

위처럼 빨간색 상자 안에 있는 부분이 바로 트랜잭션 추상화 계층의 부분이다.

어댑터패턴이 이용된 것을 확인할 수 있다.

즉 사용자가 어떤 DB를 사용하든지 UserService클래스는 PlatformTransactionManager인터페이스를 통해 변경할 필요가 없다.

이렇게 되면 특정 데이터 접근기술에 의존하지 않고 사용할 수 있게 된다.

하지만 여기에서 또 하나의 문제가 발생한다.

바로 핵심관심사의 일 말고 다른 관심사의 일도 한 메소드에서 같이 하고 있다는 것이다.

여기서 핵심관심사는 getUser() 서비스메소드에서는 DB에 저장되어 있는 사용자들의 정보만 읽어오는 역할을 해야하는데

트랜잭션추상화하는 과정이 추가적으로 들어가 있다.

이를 방지하기 위해 Spring은 선언적 트랜잭션이라는 기술을 제공한다.

 

@Transactional

Spring에서는 클래스나 인터페이스, 메소드에 부여할 수 있는 @Transactional어노테이션을 지원한다.

해당 어노테이션이 붙으면 Spring은 해당 타깃을 Pointcut대상으로 등록하고 자동으로 트랜잭션 관리 대상이 된다.

즉, AOP를 이용하여 코드외부에서 트랜잭션의 기능을 부여하고 속성을 지정할 수 있게 해주는 것을

선언적 트랜잭션 이라고 한다.

@Service
@Transactional
public class UserService{
    public void getUser(String name) {
    }
}

위처럼 @Transactional어노테이션을 사용하면 트랜잭션의 속성을 사용할 수 있다.

 

주로 @Transactional은 비즈니스 로직과 결합하는 것을 추천한다.

왜냐하면 DB로부터 읽어온 데이터를 사용하고 변경하는 등의 작업을 수행하는 곳이 서비스 로직이기 때문이다.

이렇게 @Transactional이 적용된 범위에서는 트랜잭션 기능이 포함된 프록시 객체가 생성되어 자동으로

Commit, Rollback을 실행해준다.

 

그렇다면 언제 @Transactional을 사용하면 좋을까?

내 생각은 어떠한 프로젝트를 생성하고 진행하면 하나의 DB테이블만 사용하는 것이 아니라 다양한 테이블을 이용하는 

경우가 존재한다.

만약 여기서 하나의 테이블에서 이상이 생겼다고 가정하였을 때,

@Transactional이 없다면 다른 테이블들은 오류와 상관없이 쭉 진행을 하게 되고, 컴파일할때 오류가 발생하지 않고

개발자들의 가장 최악의 오류인 런타임 오류가 발생하게 된다.

 

즉, 정리하면 @Transactional은 동시에 여러개의 테이블이 서로 연관되어 있는 경우 사용하는게 적합하다고 생각한다.

테이블간 연관이 있을 때 @Transactional을 이용하면 만약 A, B, C 테이블이 연관되어 있는데 A테이블에서 문제가 발생한 경우 연관되어 있는 B, C테이블 또한 오류를 발생하게 되어 런타임이 아닌 컴파일시 오류가 발견된다는 장점이 있다.

'SpringBoot' 카테고리의 다른 글

[Spring] 스프링 시큐리티(Security)  (0) 2023.07.24
Spring Quick - DAY1(2)  (0) 2023.02.06
Spring Quick - DAY1(1)  (0) 2023.02.06