[Spring] 트랜잭션 문제 해결

easyone·2024년 9월 11일
0

Spring

목록 보기
4/11

애플리케이션 구조

  • 프레젠테이션 계층: ui와 관련된 처리, 웹 요청과 응답, 사용자 요청 검증
  • 서비스 계층: 비즈니스 로직 담당
  • 데이터 접근 계층: 실제 데이터베이스에 접근하는 코드

서비스 계층

  • 서비스 계층에 핵심 비즈니스 로직이 들어있으므로, 최대한 변경 없이 유지되도록 하기 위해 계층을 분리
  • 데이터 접근 기술로부터 서비스 계층에 직접 접근하는 것이 아니라 인터페이스에 의존
  • 서비스 계층이 특정 기술에 종속되지 않기 때문에 비즈니스 로직 유지보수, 테스트 용이

서비스 계층에서 발생하는 문제

-JDBC 기술에 의존하게 되는 문제 발생 ex ) SQL Exception, Datasource, connection
-비즈니스 로직보다 JDBC를 사용해서 트랜잭션을 처리하는 코드가 많음

트랜잭션 문제

  • JDBC 구현 기술이 서비스 계층에 누수됨
  • 서비스 계층은 순수해야 하며 구현 기술 변경해도 서비스 계층 코드를 최대한 유지할 수 있어야 함
  • 트랜잭션 동기화 문제 : 같은 트렌잭션을 유지하려면 커넥션을 파라미터로 넘겨야 하는데, 트렌잭션을 유지하지 않아도 되는 기능은 분리해야 하는 문제 발생
  • 트랜잭션 적용이 반복되는 문제: try, catch, finally 등의 반복

예외 누수 문제

  • 데이터 접근 계층의 JDBC 구현 기술 예외가 서비스 계층으로 전파됨
  • SQL Exception은 JDBC 전용 기술이므로 다른 기술을 사용하면 그에 맞는 다른 예외로 변경해야함

JDBC 반복 문제

  • 유사한 코드의 반복이 많음

트랜잭션 추상화

개념

  • 트랜잭션 사용하는 코드가 데이터 접근 기술마다 다르므로 JDBC기술을 사용하다가 다른 기술을 사용하려 하면 의존 관계 등의 코드를 수정해야함 -> 트랜잭션 추상화를 통해 해결
  • 트랜잭션 추상화 인터페이스를 생성해서 사용
  • 서비스는 특정 트랜잭션 기술에 직접 의존하지 않고 추상화된 인터페이스에 의존 -> 원하는 구현체를 DI를 통해 주입하면 됨

스프링의 트랜잭션 추상화

  • 스프링에서 구현한 인터페이스의 구현체를 가져다가 사용하기만 하면 됨
  • 트랜잭션 매니저: 트랜잭션을 시작, 커밋, 롤백하는 것을 관리해줌

트랜잭션 동기화

  • 리소스 동기화: 트랜잭션을 유지하려면 트랜잭션의 시작과 끝까지 같은 데이터베이스 커넥션을 유지해야 하는데, 동기화하기 위해서 파라미터로 커넥션을 전달해야 하고 커넥션을 넘기는 메서드와 넘기지 않는 메서드를 중복해서 생성해야 하는 등의 단점이 존재
  • 트랜잭션 동기화 매니저: 스프링에서 쓰레드 로컬을 사용해서 커넥션을 동기화해줌 -> 멀티스레드 상황에서도 안전하게 커넥션 동기화 -> 파라미터로 커넥션을 전달하지 않아도 됨
  • 스레드 로컬 사용 시 각각의 스레드마다 별도의 저장소가 있어서 해당 스레드만 데이터에 접근 가능
  • 리포지토리에서 필요한 데이터를 트랜잭션 동기화 매니저를 통해 접근

트랜잭션 매니저

  • 트랜잭션 동기화 사용 시 DataSourceUtils를 사용해야함 -> 트랜잭션 동기화 매니저가 관리하는 커넥션이 있으면, 해당 커넥션 반환 -> 없는 경우 새로운 커넥션 생성해서 반환
  • close()를 사용해서 커넥션을 직접 닫아버리면 커넥션이 유지되지 않는 문제 발생 -> DataSourceUtils는 커넥션 사용 뒤에도 닫지 않고 유지함

트랜잭션 시작

  • 서비스 계층에서 transactionManager.getTransaction() 호출을 통해 트랜잭션 시작
  • 트랜잭션 매니저가 내부에서 데이터소스 사용해서 커넥션 생성
  • 커넥션을 수동 커밋 모드로 변경하면서 트랜잭션 시작
  • 커넥션을 트랜잭션 동기화 매니저에 안전하게 보관

로직 실행

  • 리포지토리는 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용함
  • 획득한 커넥션을 사용해서 SQL을 데이터베이스에 전달해서 실행

트랜잭션 종료

  • 비즈니스 로직이 끝나고 커밋하거나 롤백을 통해 트랜잭션 종료
  • 트랜잭션을 종료하기 위해 동기화 매니저에서 동기화된 커넥션 획득
  • 전체 리소스 정리 : 트랜잭션 동기화 매니저 정리, 커넥션 종료

트랜잭션 템플릿

  • 트랜잭션 사용하는 로직에서 반복되는 코드가 많음
  • 트랜잭션 템플릿 콜백 패턴 사용 시 반복 문제를 해결할 수 있음: 트랜잭션 시작, 성공시 커밋, 실패시 콜백하는 내용을 대신 처리해줌
  • execute(): 응답 값이 있을 때 사용
  • executeWithoutResult(): 응답 값이 없을 때 사용
  • 기본 동작: 비즈니스 로직이 정상 실행되면 커밋, 언체크 예외 발생 시 롤백
  • 트랜잭션 처리와 비즈니스 로직 처리하는 코드가 한 곳에 있으면 하나의 클래스에서 처리하게 되므로 유지보수가 어려워짐

트랜잭션 AOP

  • 스프링 AOP 적용하려면 스프링 컨테이너가 필요 -> @SpringBootTest 애노테이션이 있으면 테스트 시 스프링 부트를 통해 스프링 컨테이너 생성, @Autowired 를 통해 스프링 컨테이너가 관리하는 빈들 사용 가능
  • @TestConfiguration: 자동으로 추가되는 빈 외에 추가로 필요한 스프링 빈 등록 가능
  • AOP 프록시 적용: 프록시 도립 시 스프링이 서비스 로직을 상속받아서 프록시 코드를 생성, 트랜잭션 프록시가 트랜잭션 처리 로직을 모두 가져가고 트랜잭션 시작 후에 실제 서비스 대신 호출 -> 비즈니스 로직 처리와 트랜잭션 처리 객체를 명확하게 분리 가능
  • 트랜잭션 처리가 필요한 곳에 @Transactional 애노테이션을 붙여주면 됨

자동 리소스 등록

  • 스프링 부트에서는 데이터소스와 트랜잭션 매니저를 자동으로 등록해줌
  • 개발자가 직접 데이터소스를 빈으로 등록하면 스프링 부트에서 데이터소스 자동으로 등록하지 않음
  • 스프링부트에서는 HikariDataSource를 기본으로 사용하고, application.properties를 통해 지정 가능
profile
백엔드 개발자 지망 대학생

0개의 댓글