1. 비즈니스 로직 층과 트랜잭션
비즈니스로직과 트랜잭션 처리는 뗄 수 없는 사이다. 그래서 자연스럽게 트랜잭션은 비즈니스로직층에서 처리한다. 그래서 이 글의 목적은, 트랜잭션 처리를 지원하는 스프링 기능을 살펴보기 위함이다.
1.1. 트랜잭션을 처리하는 경계
스프링은 메서드 호출에 대응해서 트랜잭션을 처리한다. 다시말해 트랜잭션은 하나의 요청을 받아내는 비즈니스 메서드 단위로 이루어진다. 그런 메서드는 비즈니스로직층에 구현된다.
그래서 프레젠테이션층과 비즈니스로직층 사이가 트랜잭션의 경계다. 다시 말해서, 컨트롤러가 호출하는 어떤 서비스층의 메서드의 시작이 트랜잭션 시작이다. 그 메서드를 마치고 컨트롤러로 돌아가는 시점이 트랜잭션의 종료다.
그러나 이렇게 경계에서 트랜잭션을 시작하고 종료할 때,
직접 트랜잭션을 손수 기술하지는 않는다.
문제가 생기기 때문이다.
문제점1: 의존성
가령 JDBC를 이용할 경우를 생각해보자.
- 비즈니스 로직 안에서 Connection을 얻어야 한다.
- 비즈니스 로직 안에서 커밋과 롤백을 해야 한다.
위 두가지는 JDBC의 트랜잭션 처리 관련 API가 은닉되지 않는 상황이다. 즉 비즈니스로직층은 특정 데이터액세스 기술인 JDBC의 API에 의존해버린다.
문제점2: 커넥션 전달
비즈니스 로직에서 Connection을 취득했을 때 문제점은 뭘까? 쿼리 기능을 가지는 DAO도 Connection을 사용해야 한다는 것이다. 그런데 비즈니스 로직 층에서 Connectiom을 얻어버렸으므로, 데이터 액세스 층의 DAO에게도 매개변수 등으로 해당 Conmection 객체를 전달해줘야 한다.
정리하자면 비즈니스로직에서 트랜잭션 처리를 위해 커넥션을 얻거나, 특정 데이터액세스 기술의 트랜잭션 처리 기능을 사용하는 것은 객체지향스럽지 않고 의존성도 높아진다는 문제점이 있다.
그래서 바로 다음에서 스프링이 트랜잭션 처리를 어떻게 도와주는지를 볼 것이다.
2. 스프링의 트랜잭션 처리
스프링은 트랜잭션 처리를 어떻게 도울까? 먼저 트랜잭션 정의할 때 어떤 것들을 설정할 수 있는지 보자. 그리고, 스프링에서 특정 메서드나 클래스나 인터페이스에 트랜잭션을 지정하려면 어떻게 할 수 있는지를 알아보자.
2.1. 트랜잭션 정의 정보
상황에 따라 다음과 같은 트랜잭션 설정을 할 수 있다.
- 전파 속성 (propagation)
- 독립성 수준 (isolation)
- 타임아웃
- 읽기 전용
- 롤백 대상 예외
- 커밋 대상 예외
* 전파속성과 독립성수준에 대한 설명은 여기서 하지 않겠다.
2.2. 트랜잭션 매니저
스프링은 트랜잭션 처리를 위하여 트랜잭션 매니저를 제공한다. 위와 같은 트랜잭션 정의 정보를 트랜잭션 매니저를 통해 설정할 수 있는 것이다. 트랜잭션 매니저의 공통 인터페이스는 PlatformTransactionManager라는 인터페이스다. 이것의 구현체는 데이터 액세스 기술(예를들어 하이버네이트.. JPA...)별로 존재한다. 개발자는 본인 상황에 맞게 구현클래스를 선택하면 된다. 그렇게 데이터 액세스 기술에 따라 선택한 트랜잭션매니저 구현클래스는 Bean 정의 파일에 등록하면 된다.
그럼 트랜잭션 기능을 사용할 수 있게 된다.
Bean 정의파일에 DataSourceTransactionManager라는 트랜잭션매니저 구현체를 선택하여 등록하는 예시이다.
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<propertiy name="dataSource" ref="dataSource" />
</bean>
2.3. 선언적 트랜잭션으로 사용한다
스프링에서 트랜잭션 기능을 사용하는 방법은 두가지가 있다.
- 선언적 트랜잭션
- 명시적 트랜잭션
명시적 트랜잭션은 코드 속에 트랜잭션 처리를 직접 명시한다. 잠시 우리 소스코드 속에서 트랜잭션 처리 메서드를 호출한다고 생각해 볼까? 코드가 혼잡해져 가독성을 해친다. 이런 명시적 트랜잭션은 단점이 크기 때문에 일단 아예 잊어두자.
선언적 트랜잭션은 트랜잭션 처리 대상 메서드를 Bean정의파일이나 어노테이션으로 선언(지정)하는 방법이다.
선언적 트랜잭션은 Proxy를 매개하여 트랜잭션을 처리한다는 것도 알아두자.
또한 선언적 트랜잭션 설정 정보 기본값으로 propagation은 PROPAGATOIN_REQUIRED이고, isolation은 ISOLATION_DEFAULT이다.
■ Bean 정의 파일
ⓐ 메서드에 트랜잭션처리 지정하기
트랜잭션 정의 정보를 Bean 정의 파일에 설정할 때 메서드 단위의 트랜잭션 설정을 한다. 아래와 같다
■ 아주 대략적으로 아래와 같은 구성을 가진다.
<tx:advice id="transactionAdvice" transaction-manager="transactionMangager">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>
■ 위와 같은 틀에다가 필요한 정보를 추가추가하면 된다.
트랜잭션을 적용할 method의 name을 지정한다든지, propagation이나 isolation 설정을 한다든지 등!
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="update*
propagation="REQUIRED"
isolation="READ_COMMITED"
timeout="10"
read-only="false"
rollback-for="BusinessException" />
</tx:attributes>
</tx:advice>
ⓑ 인터페이스나 클래스에 트랜잭션처리 지정하기
트랜잭션을 처리할 때 AOP를 이용하면 굉장히 간편하지 않을까? 그렇다 간편하다.
스프링은 트랜잭션 어드바이스를 제공하기 때문에, 그저 이용만 하면 된다.
- 아래와 같이 aop 스키마를 통해 트랜잭션을 지정할 수 있다.
- pointcut은 어떤 조건에 부합하는 것만 Advice를 수행하기 위한 jointpoint 필터링 조건이다.
- excution은 Advice를 지정할 메서드, 클래스, 인터페이스 등을 표현식으로 지정할 수 있다.
<aop:config>
<aop:advisor advice-ref:"transactionAdvice"
pointcut="execution(* *..*Service.*(..))" />
</aop:config>
이런 aop 스키마를 통해 메서드 단위를 지정할 수도 있으나, 트랜잭션 정의 정보를 설정할 때 메서드 단위의 설정이 이루어지므로, aop 스키마를 통해서는 인터페이스나 클래스 단위의 트랜잭션 처리를 선언한다는 것을 알아두자.
참고: 스프링 AOP에 대한 이해
https://splendidlolli.tistory.com/567
■ 어노테이션 (@Transactional)
Bean 정의 파일 말고, 간단히 @Transactional 어노테이션으로 트랜잭션을 선언할 수 있다.
트랜잭션 처리 대상 클래스, 메서드에 @Transactional을 지정한다. (인터페이스에는 제약이 있다.)
클래스에 지정한다면 private메서드 이외의 모든 메서드가 트랜잭션 제어 대상이 된다.
@Transactional만 붙인다면 트랜잭션 설정 기본값을 사용한다.
직접 지정하고 싶다면,
@Transactional(
propagatoin=Propagatoin.REQUIRED,
isolation="Isolation.READ_COMMITED",
...
)
이런 식으로 선언하면 된다
주의할 점은, 이런 @Transactional을 사용하기 위해 Bean정의파일에다가 @Transactional을 유효하게 하는 설정을 해야 한다.
<tx:annatation-driven transaction-manager="transactionManager" />
참고: JavaConfig에서 트랜잭션 설정시 annotaion-driven에 해당하는 것은 @EnableTransactionManagement이다.
'JAVA > Application' 카테고리의 다른 글
[Java] 시스템 환경변수는 System.getProperty가 아니라 System.getenv로 가져온다 (4) | 2024.02.22 |
---|---|
Spring의 캐싱 기능 (0) | 2023.08.27 |
[자바/스프링] 데이터 액세스 층 설계 (0) | 2023.08.11 |
[Springboot] thymeleaf-extras-springsecurity5 dependency 못 읽어오는 문제 (0) | 2023.02.03 |
[JPA] Entity는 영속성 컨텍스트에 저장/조회한다! (4) | 2023.01.25 |