[스프링 DB 1편] #4. 스프링과 문제 해결 - 트랜잭션 #463
Develop-KIM
started this conversation in
동환
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
1️⃣ 문제점들
애플리케이션 구조
프레젠테이션 계층
서비스 계층
데이터 계층
순수한 서비스 계층
JDBC, JPA와 같은 구체적인 데이터 접근 기술로부터 서비스 계층을 보호해준다.문제점들
MemberServiceV1은 특정 기술에 종속적이지 않고, 순수한 비즈니스 로직만 존재한다.사실 아직 문제가 있다.
SQLException이라는 JDBC 기술에 의존한다는 점이다.memberRepository에서 올라오는 예외이기 때문에memberRepository에서 해결해야 한다.MemberRepositoryV1이라는 구체 클래스에 직접 의존하고 있다.MemberRepository인터페이스를 도입하면 향후 서비스의 코드 변경 없이 다른 구현 기술로 쉽게 변경이 가능하다.MemberServiceV2트랜젹선을 적용한 코드이다.javax.sql.DataSource,java.sql.Connection,java.sql.SQLException같은 JDBC 기술에 의존해야한다는 점이다.
문제 정리
트랜잭션 문제
예외 누수
SQLException은 체크 예외이기 때문에 접근 계층을 호출한 서비스 계층에서해당 예외를 잡아서 처리하거나 명시적으로
throws를 통해 던져야 한다.SQLException같은 경우는 JDBC 전용 기술이다.JDBC 반복 문제
MemberRepository코드는 순수한 JDBC를 사용했다.PreparedStatement를 사용하고, 매핑하고 실행하고, 커넥션과 리소스를 정리한다.스프링과 문제 해결
2️⃣ 트랜잭션 추상화
구현 기술에 따른 트랜잭션 사용법
con.setAutoCommit(false)transaction.begin()JDBC 트랜잭션 코드 예시
JPA 트랜잭션 코드 예시
서비스 계층의 트랜잭션을 처리하는 코드 모두 변경해야 한다.
JDBC 트랜잭션 의존
트랜잭션 추상화
트랜잭션 추상화 인터페이스
TxManager인터페이스를 기반으로 각각의 기술에 맞는 구현체를 만들면 된다.JdbcTxManager: JDBC 트랜잭션 기능을 제공하는 구현체JpaTxManager: JPA 트랜잭션 기능을 제공하는 구현체TxManager라는 추상화된 인터페이스에 의존한다.이제 원하는 구현체를 DI를 통해서 주입하면 된다.
JdbcTxManager를 서비스에 주입하고JPA 트랜잭션 기능으로 변경해야 하면
JpaTxManager를 주입하면 된다.이제 트랜잭션을 사용하는 서비스 코드를 전혀 변경하지 않고, 트랜잭션 기술을 변경할 수 있게 되었다.
스프링의 트랜잭션 추상화
PlatformTransactionManager 인터페이스
getTransaction(): 트랜잭션을 시작한다.getTransaction()인 이유는 기존에 이미 진행중인 트랜잭션이 있는 경우 해당 트랜잭션에 참여할 수 있기 때문이다.commit(): 트랜잭션을 커밋한다.rollback(): 트랜잭션을 롤백한다.PlatformTransactionManager인터페이스와 구현체를 포함해서 트랜잭션 매니저로 줄여서 부르겠다.3️⃣ 트랜잭션 동기화
스프링에서 제공하는 트랜잭션 매니저는 크게 2가지 역할을 한다.
트랜잭션 추상화
PlatformTransactionManager를 주입하여 사용한다.리소스 동기화
같은 커넥션을 동기화하기 위해서 이전에는 파라미터로 커넥션을 전달하는 방법을 사용했다.
MemberRepositoryV2)커넥션과 세션
ThreadLocal)을 사용해서 커넥션을 동기화해준다.동작 방식을 간단하게 설명하면 다음과 같다.
트랜잭션 동기화 매니저
다음 트랜잭션 동기화 매니저 크래스를 열어보면 쓰레드 로컬을 사용하는 것을 확인할 수 있다.
org.springframework.transaction.support.TransactionSynchronizationManager4️⃣ 트랜잭션 문제해결 - 트랜잭션 매니저1
DataSourceUtils.getConnection()
getConnection()에서DataSourceUtils.getConnection()를 사용하도록 변경된 부분을 특히 주의해야 한다.DataSourceUtils.getConnetion()는 다음과 같이 동작한다.DataSourceUtils.releaseConnection()
close()에서DataSourceUtils.releaseConnection()를 사용하도록 변경된 부분을 주의 깊게보아야 한다.커넥션을
con.close()를 사용해서 직접 닫아버리면 커넥션이 유지되지 않는 문제가 발생한다.이 커넥션은 이후 로직은 물론이고, 트랜잭션을 종료할 때 까지 살아있어야 한다.
DataSourceUtils.releaseConnection()을 사용하면 커넥션을 바로 닫는 것이 아니다.아래는 트랜잭션 매니저를 사용한 서비스 코드이다.
private final PlatformTransactionManager transactionManagerDataSourceTransactionManager구현체를 주입 받아야 한다.JpaTransactionManager를 주입 받으면 된다.transactionManager.getTransaction()TransactionSatus status를 반환한다. 현재 트랜잭션의 상태 정보가 포함되어 있다.new DefaultTransactionDefinition()transactionManager.rollback(status)이제 트랜잭션 매니저를 이용해서 서비스 코드를 작성해보았으니 테스트 코드도 매니저를 이용해서 작성해보자.
DataSource가 필요하다.트랜잭션 문제 해결 - 트랜잭션 매니저2
이제 그림으로 트랜잭션 매니저의 전체 동작 흐름을 알아보자.
트랜잭션 매니저1 - 트랜잭션 시작
transactionManager.getTransaction()을 호출해서 트랜잭션을 시작한다.멀티 쓰레드 환경에 안전하게 커넥션을 보관한다.트랜잭션 매니저2 - 로직 실행
리포지토리는
DataSourceUtils.getConnection()을 사용해서 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용한다.이 과정을 통해서 자연스럽게 같은 커넥션을 사용하고, 트랜잭션도 유지된다.
트랜잭션 매니저3 - 트랜잭션 종료
con.setAutoCommit(true)로 되돌린다. 커넥션 풀을 고려해야 하기 때문이다.con.close()를 호출해서 커넥션을 종료한다. 커넥션 풀을 사용하는 경우con.close()를 호출하면 커넥션 풀에 반환된다.정리
DataSourceTransactionManager에서JpaTransactionManager로 변경해주면 된다.6️⃣ 트랜잭션 문제 해결 - 트랜잭션 템플릿
현재 트랜잭션을 사용하는 로직을 보게 되면 같은 패턴이 반복되는 것을 확인할 수 있다.
try,catch,finally를 포함한 성공시 커밋, 실패시 롤백 코드가 반복될 것이다.템플릿 콜백 패턴을 활용하면 이러한 반복 문제를 깔끔하게 해결할 수 있다.트랜잭션 템플릿
템플릿 콜백 패턴을 적용하려면 템플릿을 제공하는 클래스를 작성해야 하는데, 스프링은
TransactionTemplate라는 템플릿 클래스를 제공한다.execute(): 응답 값이 있을 때 사용한다.executeWithoutResult(): 응답 값이 없을 때 사용한다.트랜잭션 템플릿을 사용해서 반복하는 부분을 제거해보자
TransactionTemplate을 사용하려면transactionManager가 필요하다.생성자에서
transactionManager를 주입 받으면서TransactionTemplate을 생성했다.트랜잭션 템플릿 사용 로직
try~catch가 들어갔는데,bizLogic()메서드를 호출하면SQLException체크 예외를 넘겨준다.해당 람다에서 체크 예외를 밖으로 던질 수 없기 때문에 언체크 예외로 바꾸어 던지도록 예외를 전환했다.
테스트 코드
정리
7️⃣ 트랜잭션 문제 해결 - 트랜잭션 AOP 이해
프록시를 통한 문제 해결
서비스 계층의 트랜잭션 사용 코드 예시
프록시를 사용하면 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 서비스 객체를 명확하게 분리할 수 있다.
트랜잭션 프록시 코드 예시
트랜잭션 프록시 적용 후 서비스 코드 예시
트랜잭션 프록시 덕분에 서비스 계층에는 순수한 비즈니스 로직만 남길 수 있다.
스프링이 제공하는 트랜잭션 AOP
스프링 부트를 사용하면 트랜잭션 AOP를 처리하기 위해 필요한 스프링 빈들도 자동으로 등록해준다.
@Transactional애노테이션만 붙여주면 된다.스프링의 트랜잭션 AOP는 이 애노테이션을 인식해서 트랜잭션 프록시를 적용해준다.
7️⃣ 트랜잭션 문제 해결 - 트랜잭션 AOP 적용
@Transactional애노테이션을 추가했다.@Transactional애노테이션은 메서드에 붙여도 되고, 클래스에 붙여도 된다.클래스에 붙이면 외부에서 호출 가능한
public메서드가 AOP 적용 대상이 된다.테스트 코드
@SpringbootTest: 스프링 AOP를 적용하려면 스프링 컨테이너가 필요하다.이 애노테이션이 있으면 테스트 시 스프링 부트를 통해 스프링 컨테이너를 생성한다.
그리고 테스트에서
@Autowired등을 통해 스프링 컨테이너가 관리하는 빈들을 사용할 수 있다.@TestConfiguration: 테스트 안에서 내부 설정 클래스를 만들어서 사용하면서 이 애노테이션을 붙이면스프링 부트가 자동으로 만들어주는 빈들에 추가로 스프링 빈들을 등록하고 테스트를 수행할 수 있다.
TestConfigDataSource스프링에서 기본으로 사용할 데이터소스를 스프링으로 등록한다. 추가로 트랜잭션 매니저에서도 사용한다.DataSourceTransactionManager트랜잭션 매니저를 스프링 빈으로 등록한다.트랜잭션 매니저를 스프링 빈으로 등록해두어야 한다.
AOP 프록시 적용 확인
실행 결과 - AopCheck()
AopCheck()의 실행 결과를 보면memberService에EnhancerBySpringCGLIB..라는 부분을 통해프록시(CGLIB)가 적용된 것을 확인 할 수 있다.
memberRepository에는 AOP를 적용하지 않았기 때문에 프록시가 적용되지 않는다.8️⃣ 트랜잭션 문제 해결 - 트랜잭션 AOP 정리
트랜잭션 AOP 적용 전체 흐름
선언적 트랜잭션 관리 vs 프로그래밍 방식 트랜잭션 관리
@Transactional애노테이션 하나만 선언해서 매우 편리하게 트랜잭션 적용하는 것을 선언적 트랜잭션 관리라 한다.이름 그대로 해당 로직에 트랜잭션을 적용하겠다 라고 어딘가에 선언하기만 하면 트랜잭션이 적용되는 방식이다.
실무에서는 대부분 스프링 컨테이너와 스프링 AOP를 사용하기 때문에 거의 사용하지 않는다. (테스트 시 가끔 사용된다.)
정리
@Transactional애노테이션 하나만 추가하면 된다.9️⃣ 스프링 부트의 자동 리소스 등록
데이터소스와 트랜잭션 매니저를 스프링 빈으로 직접 등록
기존에는 이렇게 데이터소스와 트랜잭션 매니저를 직접 스프링 빈으로 등록해야 했다. 그런데 스프링 부트가 나오면서 많은 부분이 자동화되었다.
데이터소스 - 자동 등록
DataSource)를 스프링 빈에 자동으로 등록한다.dataSource이때 스프링 부트는 다음과 같이
application.properties에 있는 속성을 사용해서DataSource를 생성하여 스프링 빈에 등록한다.HikariDataSource이다.커넥션 풀과 관련된 설정도
application.properties를 통해서 지정할 수 있다.spring.datasource.url속성이 없으면 내장 데이터베이스(메모리 DB)를 생성하려고 시도한다.트랜잭션 매니저 - 자동 등록
PlatformTransactionManager)를 자동으로 스프링 빈에 등록한다.transactionManager어떤 트랜잭션 매니저를 선택할 지는 현재 등록된 라이브러리를 보고 판단
DataSourceTransactionManager를 빈으로 등록JpaTransactionManager를 빈으로 등록한다.JpaTransactionManager를 등록한다.JpaTransactionManager는DataSourceTransactionManager가 제공하는 기능도 대부분 지원한다.데이터소스, 트랜잭션 매니저 직접 등록
이런 식으로 데이터소스와 트랜잭션 매니저를 직접 등록하면 스프링 부트는 데이터소스와 트랜잭션 매니저를 자동으로 등록하지 않는다.
스프링 부트가 제공하는 자동 등록을 이용하여 데이터소스와 트랜잭션 매니저를 편리하게 적용
MemberServiceV3_4Test
MemberServiceV3_3Test)과 같은 코드이고TestConfig부분만 다르다.따라서 스프링 부트가
application.properties에 지정된 속성을 참고해서 데이터소스와 트랜잭션 매니저를 자동으로 생성해준다.정리
application.properties를 통해 설정도 편리하게 할 수 있다.Beta Was this translation helpful? Give feedback.
All reactions