* Spring 5.1.2

* jdk 1.8

* jboss/wildfly 9

* jndi 

 

1. xml + aop pointcut 을 사용한 설정 (context xml 설정)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- transactionManager 적용 범위 지정(advice) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="get*" read-only="true" rollback-for="Exception" />
        <tx:method name="select*" read-only="true" rollback-for="Exception" />
        <tx:method name="insert*" read-only="false" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="update*" read-only="false" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="delete*" read-only="false" propagation="REQUIRED" rollback-for="Exception"/>
    </tx:attributes>
</tx:advice>
 
<!-- transactionManager pointcut  -->
<aop:config>
    <aop:pointcut id="txMethod" expression="execution(* com.sample..*Service.*(..))" />
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txMethod" />
</aop:config>
 
<!-- transactionManager bean -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
 
<!-- datasource bean (jndi 사용) -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:/jdbc/AdmJNDI" />
</bean>
     
cs

* aop pointcut 으로 선언 및 설정할 경우 위와 같이 적용할 pointcut 범위를 지정.

(* com.sample..*Service.*(..)) : 모든 접근제한자의 com 패키지 밑의 sample 패키지 밑의 ~Service로 끝나는 모든 클래스 밑의 모든(*) 메소드

* advice 설정에 메소드별로 제약 및 옵션을 걸어 줄 수 있다.

  get* : get으로 시작하는 모든 메소드

  select* : select 로 시작하는 모든 메소드

* read-only = true : insert / update / delete 쿼리를 내부적으로 실행시 exception 을 뱉는다.

* roll-backfor = Exception : Exception (Exception 밑의 모든 Exception 포함) 이 발생할 경우 rollback 처리. (no rollback for 옵션으로 특정 Exception이 발생시 rollback 처리를 하지 않게 처리 가능)

propagation : 트랜잭션의 전파속성으로써 메소드 내에서 다른 메소드를 사용할 때 하나의 트랜잭션으로 묶을지, 별도의 트랜잭션으로 분류할지 등 과 같은 설정을 지정하는 옵션. default 값이 required 로 알고 있어 위와 같이 선언할 필요는 없는 걸로 알고 있다.. 여러가지 설정이 있는데 나중에 정리하는걸로. 

 

>> com.sample..*Service 밑의 get*, insert*, delete*, select*, update* 이름을 가진 모든 메소드는 위의 트렌잭션 설정에 의해 관리된다.

 

 

2. annotation 을 사용한 설정

1
2
3
4
5
6
7
8
9
10
11
12
<!-- transactional annotation 설정 사용 -->
<tx:annotation-driven transaction-manager="txManager" />
 
<!-- transactionManager bean -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
 
<!-- datasource bean (jndi 사용) -->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:/jdbc/AdmJNDI" />
</bean>
cs

* txManager 와 관련된 database 설정, transaction manager bean 선언 부분은 1번과 동일.

 

>> Transaction 으로 관리하고 싶은 메소드위에 @Transactional 어노테이션을 달아주어 사용.

 

 

* 주의사항

1. @Transactional 은 public 메소드에서만 정상 작동한다.

 

2. @Transactional 을 달아놓은 메소드가 동일한 클래스 내의 다른 메소드에 의해 호출된다면 트랜잭션이 정상 작동하지 않는다.

ex: 퍼사드 패턴이나 템플릿 패턴처럼 여러 메소드를 내부적으로 묶어 사용하고 있는 메소드가 있다면 구성요소 메소드에 @Transactional 를 달지 않고 구성요소를 묶고 있는 상위개념의 메소드에 @Transactional 을 달아주어야 한다. 구성요소 메소드에 @Transactional 을 달아 주어 트랜잭션으로 관리 할 경우 rollback 이 정상적으로 작동하지 않는 경우가 발생한다.

 

위 내용과 같은 경우가 아래에 정리되어 있다.

https://woowabros.github.io/experience/2019/01/29/exception-in-transaction.html

propagation 속성이 required 인 경우, 트랜잭션안에서 호출되는 메소드가 트랜잭션으로 같이 묶이게 되어 예상치 못한 결과가 나올 수 있다는 내용이다.

※ required propagation 속성은 트랜잭션이 이미 존재하면 append를 하고, 트랜잭션이 존재하지 않다면 새로 생성한다. (공식 doc)

 

3. Spring Transaction 은 기본적으로 unchecked Exception (RuntimeException) 만 관리하며 checked Exception (IOException, SQLException 등) 은 관리하지 않는다.

처리방법 1: @Transactional(rollbackFor=Exception.class) 와 같이 설정하여 모든 Exception 발생시 rollback 이 발생하게 처리하거나(unchecked Exception, checked Exception 모두 Exception.class 밑에 있다.)

처리방법 2: checked Exception 이 발생할 가능성이 있는 부분을 try ~ catch (SQLException se){throw new RuntimeException(); } 과 같이 처리(checked Exception 발생시 unchecked Exception 으로 예외를 바꾸어 던지게 처리)하여 Transaction의 관리대상으로 묶어버릴 수 있다.

 

4. pointcut 을 사용한 Transaction 설정과 어노테이션을 사용한 Transaction 설정을 동시에 사용하게 될 경우, 어노테이션이 우선적용(precedence)되는 것 같다. (https://stackoverflow.com/questions/32057910/custom-spring-aop-around-transactional/33509777#33509777)

 

 

* 회고.

몇일 전, 정상적으로 트랜잭션이 관리되고 있다고 믿고 있던 내 소스가 특정 케이스에서 rollback 이 이뤄지지 않고 있다는 걸 발견했다.멘탈폭발

개발중인 소스는 4번과 같이 두 설정 모두를 사용하고 있던터라 advisor 와 어노테이션을 함께 사용하여 발생하는 문제인가 싶어 트랜잭션 관련 설정을 이렇게도 바꿔보고 저렇게도 바꿔보았다.

(stackoverflow 에서 두가지 설정 모두를 사용하여 충돌이 발생할 경우 order 설정에 의한 우선순위에 의해 트랜잭션이 관리되고, order 설정이 존재하지 않는다면 마지막에 선언된 설정을 따른다는 글을 봤던게 설정에 의심을 갖게된 결정적인 계기였다. (답변자가 부정확한 답변을 했거나, 내 해석이 잘못됐거나, 개발환경 차이에 의한 차이거나... 그중 하나겠지..))

퇴근 후 몇일간 탐구삽질해본 결과

동일한 프로젝트 내의 타 메소드에선 트랜잭션 관리가 정상적으로 되고 있다는 걸 알 수 있었고, 주말까지 잡아먹은 끝에 4번의 문제가 아닌, 2번의 문제였다는 걸 깨닫게 되었다..(어설프게 디자인 패턴 써보겠다고 소스 리팩토링 하면서 스스로 만들어낸 참사)

애초에 @Transactional 자체가 정상 작동할 수 없는 케이스에서 rollback이 이뤄지지 않는다고 다른 부분들을 전부 의심하기 시작한 셈이다.

덕분에? 관련 docs 도 많이 찾아 읽어보고, transaction 관련 설정부분 및 기본이자 핵심 부분들을 조금이나마 더 깊이있게 공부할 수 있었던 의미 있는 삽질의 시간이었다.

 

* 3번의 경우 위와 같이 설명되어 있는 글들을 여럿 봤는데, 내 설정과 코드에선 굳이 위처럼 처리하지 않더라도(단순히 @Transactional 어노테이션만 붙여도) rollback 이 잘 이루어 지고 있다.. 스프링 버전이나 jdbc 트랜잭션매니저 버전이 올라가면서 SQLException 을 스프링이 트랜잭션 관리 대상에 포함을 시킨건지.. (잘 모르겠으니 다시 구글링 좀 해보고, 테스트 코드도 작성해봐야겠다..)

 

 

반응형

+ Recent posts