본문 바로가기

Programming/Spring

[Spring] 스프링 트랜잭션 적용하기 (Spring + MyBatis + MySQL)

AOP를 이용한 Transaction 적용 (Spring + MyBatis + MySQL)



스프링 트랜잭션은 설정 파일이나 Annotation을 이용해서 

트랜잭션의 범위, 롤백 규칙 등을 정의한다.

<tx:advice> 태그를 이용한 트랜잭션 처리

@Transaction Annotation을 이용한 트랜잭션 설정


나는 AOP를 이용한 트랜잭션을 적용하였는데, 먼저 확인해봐야 할 것은

인터페이스 코드 유무 확인


스프링의 트랜잭션 AOP는 기본적으로 서비스 계층의 Interface를 JDK Dynamic Proxy 

기술을 이용하여 AOP를 지원한다고 한다. 하지만 Interface가 없다면 CGLib를 이용하여 

클래스 Proxy를 생성해야 한다고 한다.


aspectj dependency

<dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjweaver</artifactId>

<version>1.6.11</version>

</dependency>

<dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjrt</artifactId>

<version>1.6.8</version>

</dependency>


CGLib

<dependency>

<groupId>cglib</groupId>

<artifactId>cglib</artifactId>

<version>2.2.2</version>

</dependency>


DataSource (database 접속정보)

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<property name="driverClassName" value="#{config['db.jdbc.driver']}" />

<property name="url" value="#{config['db.jdbc.url']}" />

<property name="username" value="#{config['db.jdbc.username']}" />

<property name="password" value="#{config['db.jdbc.password']}" />

</bean>


<!-- sqlSessionFactory -->

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<property name="dataSource" ref="dataSource"/>

<property name="configLocation" value="classpath:sql/SqlMapConfig.xml"/>

<property name="mapperLocations" value="classpath:sql/*_sql.xml"/>

</bean>


<!-- sqlSessionTemplate -->

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">

<constructor-arg ref="sqlSessionFactory" />

</bean>

스프링 트랜잭션에 관련해서 dataSource는 jdbc DriverManagerDataSource를 사용하는 것이 

좋다고 어느 글에서 읽었는데.. 이유는 기억이 안난다...



트랜잭션 설정

<bean id="transactionManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource"></property>

</bean>


<aop:config proxy-target-class="true">

<aop:pointcut id="txAdvisePointCut" expression="execution(public * com.project..service.*Service.*(..))"/>

<aop:advisor id="transactionAdvisor" pointcut-ref="txAdvisePointCut" advice-ref="txAdvice"/>

</aop:config>


<tx:advice id="txAdvice" transaction-manager="transactionManager">

<tx:attributes>

<tx:method name="get*" read-only="true" />

<tx:method name="find*" read-only="true" />

<tx:method name="insert*" propagation="REQUIRED" rollback-for="Exception"/>

<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>

<tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>

</tx:attributes>

</tx:advice>


AspectJ의 Pointcut 표현식

execution(수식어패턴? 리턴타입패턴 클래스이름패턴?이름패턴(파라미터패턴)

수식어패턴 : public, private 등등의 수식어를 명시 (생략가능)

리턴타입 : 리턴 타입을 명시

클래스이름, 이름패턴 : 클래스 이름 및 메서드 이름을 패턴으로 명시

파라미터패턴 : 매칭될 파라미터에 대해 명시

'*' : 모든 값을 표현

'..' : 0개 이상을 의미


EX.

excution : Advice를 적용할 메서드를 명시할 때 사용

execution(public void set*(..))

- 리턴타입이 void이고 메서드 이름이 set으로 시작하며, 파라미터가 0개 이상인 메서드


execution(* com.project.service.*.*())

- com.project.service 패키지의 파라미터가 없는 모든 메서드


excution(* com.project.service.service..*.*(..))

- com.project.service 패키지 및 하위 패키지에 있는 파라미터가 0개 이상인 모든 메서드


execution(* get*(*))

- get으로 시작하고 1개의 파라미터를 갖는 메서드


bean : 스프링 빈 이름을 이용하여 Pointcut을 정의

bean(noticeBoard)

- 이름이 noticeBoard인 빈의 메서드 호출

bean(*Board)

- 이름이 Board로 끝나는 빈의 메서드 호출

Pointcut 표현식으로 검색하면 더 많은 정보를 알 수 있다.


<tx:advice>의 <tx:attributes> 하위 속성

<tx:mehod> 태그의 속성

name : 트랜잭션이 적용될 메서드 이름 '*' 사용 설정이 가능

propagation : 트랜잭션의 전파 규칙

isolation : 트랜잭션의 격리 레벨

read-only : 읽기 전용 여부

no-rollback-for : 트랜잭션을 롤백하지 않을 예외 타입

rollback-for : 트랜잭션을 롤백할 예외 타입

timeout : 트랜잭션의 타임 아웃 시간을 초 단위로 설정


<tx:method> 안에 propagation 속성과 규칙

REQUIRED : 메서드를 수행하는데 트랜잭션이 필요하다는 것을 의미. 

현재 진행중인 트랜잭션이 존재하면, 해당 트랜잭션을 사용하고 존재하지 

않는다면 새로운 트랜잭션을 생성

MANDATORY : 메서드를 수행하는데 트랜잭션이 필요하다는 것을 의미. REQUIRED와 달리,

진행중인 트랜잭션이 존재하지 않을 경우 예외를 발생

REQUIRES_NEW : 항상 새로운 트랜잭션을 시작. 기존 트랜잭션이 존재하면 

기존 트랜잭션을 일시 중지하고 새로운 트랜잭션을 시작. 새로 시작된 트랜잭션이 

종료된 뒤에 기존 트랜잭션이 계속됨

SUPPROTS : 메서드가 트랜잭션을 필요로 하지는 않지만, 기존 트랜잭션이 존재할 경우 

트랜잭션을 사용한다는 것을 의미. 진행 중인 트랜잭션이 존재하지 않더라도 메서드는 

정상적으로 동작

NOT_SUPPORTED : 메서드가 트랜잭션을 필요로 하지 않음을 의미. SUPPORTS와 

달리 진행중인 트랜잭션이 존재할 경우 메서드가 실행되는 동안 트랜잭션을 일시 

중지되며, 메서드 실행이 종료된 후에 트랜잭션을 계속 진행

NEVER : 메서드가 트랜잭션을 필요로 하지 않으며, 만약 진행중인 트랜잭션이 존재하면 

예외발생

NESTED : 기존 트랜잭션이 존재하면, 기존 트랜잭션에 중첩된 트랜잭션에섬 메서드를 

실행, 기존 트랜잭션이 존재하지 않으면 REQUIRED와 동일하게 동작한다. 

이 기능은 JDBC 3.0 드라이버를 사용할 때에만 적용된다고 한다.


<tx:method> 안에 rollback-for 속성과 no-rollback-for 속성

rollback-for : 예외 발생시 롤백 작업을 수행할 예외 타입

no-rollback-for : 예외가 발생하더라도 롤백하지 않을 예외 타입

예외 타입이 한 개 이상이면 콤마(,)로 구분

예외 클래스는 완전한 이름을 입력하거나, 패키지 이름을 제외한 클래스 이름만 입력

<tx:method name="insert" rollback-for="Exception" no-rollback-for="UserNotFoundException"/>

Exception 및 하위 타입의 예외가 발생할 경우 롤백을 수행하고, UserNotFoundException이 발생되는

경우에는 롤백을 하지 않는다.


트랜잭션 설정 에러나는 경우

1. MySQL 을 사용할 경우 테이블 타입 확인

- MySQL은 테이블타입이 InnoDB일때만 트랜잭션이 동작한다고 한다.

2. 인터페이스 코드 유무 

- @Transactional Annotation은 Spring AOP를 이용하게 되는데 이 AOP는 기본적으로 Dynamic Proxy를

이용한다고 한다. 그러므로 인터페이스가 없으면 동작하지 않는다고 한다.

(인터페이스 없이 동작하게 하려면 CGLib(Code Generation Library) Proxy 사용!!

3. servlet-context.xml AOP 설정

- 나의 경우는 각 메서드에 AOP가 걸렸다고 표시가 되어있지만 롤백이 안되는 상황이였다..

servlet-context.xml로 AOP 설정을 옮겨보니 롤백 정상 작동~!