스프링 DB(2)

Seung·2023년 2월 28일
0
post-thumbnail

트랜잭션

트랜잭션 개념

  • 쪼갤 수 없는 업무 처리의 최소 단위 (거래내역)
    하나의 거래를 안전하게 처리하도록 보장해주는 것을 뜻함.
  • 예를들면 계좌이체 경우 A계좌에서 B계좌로 이체시 A의 돈이 빠져나가고
    B의 돈이 들어온 거래내역을 하나의 작업으로 동작해야하는걸 보장해야한다.

트랜잭션 ACID

  1. 원자성(Atomicity) : 트랜잭션 내에서 실행한 작업들은 마치 하나의 작업인것 처럼 모두 성공 또는 실패해야한다.
  2. 일관성(Consistency) : 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야한다.
  3. 격리성(Isolation) : 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다. 예를 들면 동시에 데이터를 수정하지 못하도록 해야 한다.
  4. 지속성(Durability) : 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 한다.

트랜잭션 사용법

  • DB세션
  • 데이터 변경 쿼리를 실행하고 데이터베이스에
    그 결과를 반영하려면 'commit' 호출
    결과를 반영하고 싶지 않으면 'rollback' 호출

    commit, rollback을 진행하지 않으면 DB에 반영되지 않음.
    반영되지 않으므로 해당 쿼리를 실행한 세션이 아닌 다른 세션에는 반영이 안됨.
    쿼리를 실행한 세션은 적용된 것처럼 보이지만 적용된게 아님.

  • 자동커밋과 수동커밋
    자동커밋 : 쿼리 실행 직후에 자동으로 커밋을 호출한다.
    수동커밋 : 쿼리 실행후 commit 또는 rollback을 실행해야한다.

    트랜잭션 기능을 제대로 수행하려면 수동커밋을 사용해야한다.
    수동커밋모드로 설정하는 것을 트랜잭션을 시작한다고 표현할 수 있다.

DB 락

  • 세션1이 트랜잭션을 시작하고 데이터를 수정하는 동안 세션2가 같은 데이터를 수정하게 되면 문제가 발생하고, 트랜잭션ACID의 원자성이 깨지는 것이다.
    이런 문제를 해결하기 위해 트랜잭션 시작하고 commit 또는 rollback 전까지
    해당 데이터를 수정할수 없게 막는기능

  • 일반적으로 조회(select)는 락을 사용하지 않는다.
    select for update 구문을 사용하면 조회에서도 락 기능 사용가능

    @RequiredArgsConstructor
    @Slf4j
    public class MemberServiceV2 {
    
       private final DataSource dataSource;
       private final MemberRepositoryV2 memberRepository;
    
       public void accountTransfer(String fromId, String toId, int money) throws SQLException {
           Connection con = dataSource.getConnection();
           try {
               //수동커밋 - 트랜잭션 시작
               con.setAutoCommit(false);
               //비즈니스 로직
               bizLogic(con, fromId, toId, money);
               con.commit();
           } catch (Exception e) {
               con.rollback(); //실패시 롤백
               throw new IllegalStateException(e);
           } finally {
               release(con);
           }
       }
    
       private void bizLogic(Connection con, String fromId, String toId, int money) throws SQLException {
           Member fromMember = memberRepository.findById(con, fromId);
           Member toMember = memberRepository.findById(con, toId);
    
           memberRepository.update(con, fromId, fromMember.getMoney() - money);
           validation(toMember);
           memberRepository.update(con, toId, toMember.getMoney() + money);
       }
    
       private static void release(Connection con) {
           if (con != null) {
               try {
                   con.setAutoCommit(true); //자동커밋으로 변경 후 풀로 돌려주기
                   con.close();
               } catch (Exception e) {
                   log.info("error",e);
               }
           }
       }
    
       private static void validation(Member toMember) {
           if (toMember.getMemberId().equals("ex")) {
               throw new IllegalStateException("이체중 예외 발생");
           }
       }
    }

트랜잭션 추상화

  • 현재 서비스계층은 트랜잭션을 사용하기 위해서 JDBC의 기술을 의존함.
    예를들면 setAutoCommit 허나 JPA나 다른 트랜잭션 기술을 사용시 코드를 변경해야한다.
    스프링은 이러한 문제를 해결하기 위해 스프링 트랜잭션 추상화 인터페이스를 구현해두었다.
    public interface PlactformTransactionManager extends TransactionManager{
    	//트랜잭션 시작
    	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
       //커밋
       void commit(TransactionStatus status) throws TransactionException;
       //롤백
       void rollback(TransactionStatus status) throws TransactionException;
    }

트랜잭션 동기화

  • 트랜잭션을 유지하려면 트랜잭션의 시작부터 끝까지 같은 데이터베이셔 커넥션을 유지해야한다.

    트랜잭션 매니저는 데이터소스를 통해 커넥션을 만들고 트랜잭션 시작.
    트랜잭션 매니저는 트랜잭션 시작된 커넥션을 트랜잭션 동기화 매니저에 보관.
    리포지토리는 태랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용.
    트랜잭션이 종료되면 트랜잭션 매니저는 트랜잭션동기화매니저에 보관된 커넥션을 통해 트랜잭션을 종료하고 커넥션도 종료.

    리포지토리
    ...
    private void close(Connection con, Statement stmt, ResultSet rs) {
    
           JdbcUtils.closeResultSet(rs);
           JdbcUtils.closeStatement(stmt);
           //트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용
           DataSourceUtils.releaseConnection(con,dataSource);
           //JdbcUtils.closeConnection(con);
       }
    
       private Connection getConnection() throws SQLException {
           //트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용
           Connection con = DataSourceUtils.getConnection(dataSource);
           //Connection con = dataSource.getConnection();
           log.info("get connection={},class={}",con,con.getClass());
           return con;
       }
       
    서비스
    ...
    //트랜잭션 매니저 주입
    private final PlatformTransactionManager transactionManager;
    private final MemberRepositoryV3 memberRepository;
    
       public void accountTransfer(String fromId, String toId, int money) throws SQLException {
           TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    
           try {
               //비즈니스 로직
               bizLogic(fromId, toId, money);
               transactionManager.commit(status);
           } catch (Exception e) {
               transactionManager.rollback(status); //실패시 롤백
               throw new IllegalStateException(e);
           }
       }

트랜잭션 AOP

  • @Transactional
    애노테이션을 선언하여 매우 편리하게 트랜잭션 적용
    @Transactional
       public void accountTransfer(String fromId, String toId, int money) throws SQLException {
                   bizLogic(fromId, toId, money);
       }
  • 스프링 부트의 자동리소스 등록

    데이터소스 자동등록
    스프링부트는 데이터소스(DataSource)를 스프링빈에 dataSource라는 이름으로 자동등록한다.
    자동등록된 DataSource는 application.properties에 설정된 속성을 사용해서 생성한다.

    application.properties
    spring.datasource.url=
    spring.datasource.username=
    spring.datasource.password=

    트랜잭션매니저 자동등록
    스프링부트는 트랜잭션 매니저(PlatformTransactionManager)를 스프링 빈에 taransactionManager라는 이름으로 자동등록한다.

profile
한번 해봅시다.

0개의 댓글