본문 바로가기
JAVA SPRING

커넥션, 트랜잭션 간단 정리

by 임만율 2024. 6. 2.

김영한 선생님의 강의로

DB 커넥션, 커넥션 풀, DataSource, Transaction, Transaction Manager 에 대해 공부한 내용을 정리해본다.

 

DB 커넥션과 세션

애플리케이션(스프링부트) 에서 데이터베이스 에 접근할 때 데이터베이스 서버와 커넥션을 가지게 된다.

커넥션이 생기면 데이터베이스 서버에는 세션을 만든다. 이 세션은 커넥션과 1:1 이고 커넥션을 통한 요청을 맺어진 세션에서 처리한다.

세션은 DB 에서의 트랜젝션을 시작하고 sql 을 실행하고 커밋, 롤백을 해서 트랜젝션을 종료한다.

 

커넥션 풀

커넥션을 만들 때 비용이 드는데, 모든 sql 에 대해 커넥션을 만들면 성능이 떨어진다. 그래서 커넥션 풀 이라는 것을 사용한다.

커넥션 풀은 커넥션을 얻는 방법 중 하나이다.

애플리케이션이 시작할 때 커넥션을 미리 풀에 여러개 만들어두고 커넥션이 필요할 때마다 풀에서 꺼내서 쓴다.

커넥션을 모두 사용하면 커넥션을 종료하는게 아니라 풀에 다시 반환한다. 

 

DataSource

커넥션을 얻는 방법은 커넥션 풀, DriverManager 등이 있다. 

커넥션을 얻는 방법이 여러가지라서 이를 추상화한 것이 DataSource 이다.

DataSource 의 핵심 기능은 커넥션 조회이다. 

구현체는 이미 거의 다 만들어져있어서 DataSource 인터페이스에만 의존하여 사용하면 된다.

 

즉, DataSource 를 통해 커넥션 풀을 사용한다!

 

트랜잭션

데이터베이스에서의 트랜젝션은 하나의 거래를 안전하게 처리하도록 보장하는 것이다. 

트랜젝션은 비즈니스 로직이 있는 서비스 계층에서 시작해야 한다. 

같은 비즈니스 로직이 같은 트랜젝션에서 실행되어야 문제가 터졌을 때 롤백을 해도 문제가 없다. 

트랜젝션을 시작하려면 커넥션이 필요하다. 트랜젝션 커밋 이후에 커넥션을 종료해야 한다.

커넥션은 연결되는 서비스 로직이 끝날 때까지 유지하면서 사용해야 한다.

 

Service 클래스 

private final DataSource dataSource;

public void test(String param) throws SQLException {
    Connection con = dataSource.getConnection();
    try {
        con.setAutoCommit(false); //트랜잭션 시작
        // 비즈니스 로직
        bizLogic(param);
        con.commit(); // 성공시 커밋
    } catch (Exception e) {
        con.rollback(); // 실패시 롤백
        throw new IllegalStateException(e);
    } finally {
        release(con);
    }
}

 

 

그러면 서비스 계층에서 트랜젝션 처리를 해야하고 코드가 지저분해진다. (DataSource와 같이 DB 코드가 포함됨) 그리고 JDBC 에서 JPA 로 기술을 변경하면 서비스 계층 코드도 모두 변경이 필요해진다. 

-> 트랜젝션 추상화. 

 

DB 락

lock 은 여러 세션이 같은 row 또는 테이블에 대해 작업할 때 순서를 주기 위한 것이다.

트랜젝션을 먼저 시작한 세션이 lock 을 획득하고 먼저 작업을 하고 그 뒤에 온 세션은 lock 이 없으니까 대기한다.

커밋하고 트랜젝션 종료되면 lock 을 반납한다.

 

 

트랜잭션 매니저

주요 역할 2개

  • 트랜잭션 기술 추상화
  • 리소스 동기화

스프링의 트랜잭션 추상화 기술 : PlatformTransactionManager <- 서비스 로직은 이것만 의존한다. 

구현제도 이미 거의 다 있어서 필요한 것을 사용하면 된다. 

 

스프링의 트랜잭션 동기화 매니저 : ThreadLocal 로 커넥션을 동기화한다. 트랜잭션 매니저 내부에서 동기화 매니저를 사용한다.

동기화 매니저로 커넥션을 획득하면 된다. 멀티 쓰데드 상황에서 커넥션을 동기화해준다. 

 

  1. 트랜잭션 매니저는 DataSource 를 통해 커넥션을 만들고 트랜잭션을 시작한다.
  2. 트랜잭션이 시작된 커넥션을 트랜잭션 동기화 매니저에 보관한다.
  3. 리포지토리는 동기화 매니저에 보관된 커넥션을 꺼내서 사용한다.
  4. 트랜잭션 종료되면 매니저는 동기화 매니저에 보관된 커넥션을 통해 트랜잭션을 종료하고 커넥션도 닫는다.

 

트랜잭션 매니저를 사용하는 Service

private final PlatformTransactionManager transactionManager;

public void test(String param) throws SQLException {
    // 트랜잭션 시작
    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

    try {
        // 비즈니스 로직
        bizLogic(param);
        transactionManager.commit(status); // 성공시 커밋

    } catch (Exception e) {
        transactionManager.rollback(status); // 실패시 롤백
        throw new IllegalStateException(e);
    }
}

 

커밋, 롤백 하면 트랜잭션 매니저 내부에서  커넥션 정리해준다.

 

 

DataSourceTransactionManager 전체 흐름!!! 여기 전까지 참 헷갈렸는데 이제 조금 이해가 됨. 

  1. 서비스 계층에서 트랜잭션 시작한다. -> transactionManager.getTransaction()
  2. 트랜잭션 매니저는 DataSource 로 커넥션을 생성한다.
  3. 커넥션을 수동 커밋 모드로 변경해서 실제 데이터베이스 트랜잭션을 시작한다.
  4. 커넥션을 트랜잭션 동기화 매니저에 보관한다.
  5. 서비스는 비즈니스 로직을 실행하면서 리포지토리의 메서드들을 호출한다. 
  6. 리포지토리 메서드들은 '트랜잭션이 시작된 커넥션' 이 필요하다. 리포지토리는 동기화 매니저에 보관된 커넥션을 사용한다. -> 같은 커넥션 사용하면서 트랜잭션 유지.
  7. 커넥션을 사용해서 SQL 을 데이터베이스에 전달해서 실행한다. 
  8. 비즈니스 로직이 끝나고 트랜잭션 종료한다. => 커밋 or 롤백 
  9. 종료하기 위해 동기화 매니저에서 동기화된 커넥션 획득한다.
  10. 획득한 커넥션을 통해 데이터베이스에 트랜잭션을 커밋하거나 롤백한다.
  11. 전체 리소스를 정리한다. => 동기화 매니저 정리, 커넥션 풀에 다시 반납하기 때문에 자동 커밋 모드로 변경.

 

 

트랜잭션 템플릿

계속 반복되는 코드가 템플릿으로 만들어져있다.

private final TransactionTemplate txTemplate;

public Service(PlatformTransactionManager transactionManager) {
    this.txTemplate = new TransactionTemplate(transactionManager);
}

public void test(String param) throws SQLException {
    txTemplate.executeWithoutResult((transactionStatus -> {
        // 비즈니스 로직
        try {
            bizLogic(param);
        } catch (SQLException e) {
            throw new IllegalStateException(e);
        }
    }));
}

 

 

@Transactional

서비스계층에 순수한 비즈니스 로직만 남기기 위해 사용 => 스프링 AOP 

트랜잭션 처리가 필요한 곳에 @Transactional 어노테이션만 붙이면 된다.

@Transactional
public void test(String param) throws SQLException {
    bizLogic(param);
}

 

 

 

기억해두면 좋을 내용

- 스프링 부트는 DataSource 를 스프링 빈에 자동으로 등록 -> application.properties 속성 사용

- 스프링 부트는 트랜잭션 매니저도 자동으로 등록 -> 어떤 트랜잭션 매니저를 쓸 지는 라이브러리 보고 판단 

(만약 직접 빈 등록하면 자동 등록하지 않는다.)

 

'JAVA SPRING' 카테고리의 다른 글

API 부하테스트 툴 ngrinder  (0) 2024.07.08
Spring Batch 5 ItemProcessor 에서 return list  (0) 2024.06.02
Java 예외 처리  (0) 2024.05.27
Spring Boot CORS 설정  (0) 2023.09.03