728x90
반응형

1. Transaction이란?

저장소(일반적으로 DB)에 데이터를 저장하거나 삭제, 갱신할때 일어나는 변경의 단위를 말한다.

Oracle의 insert 쿼리가 각각 하나씩 실행될때는 중간에 데이터가 추가되다가 잘 못되는 경우가 있어도 Oracle DB가 알아서  rollback을 해준다.

이것을 API의 ACID 속성이라고 한다.

-  A: atomicity-원자성, C:Consistency - 일관성, I:Isolation:고립성, D:Durability:고립성

- 자세한 것은 위키(https://ko.wikipedia.org/wiki/ACID) 참조


그러나 여러개의 API가 연속적으로 실행될때는 어떻게 해야 할까?

예를 들어, 웹서버에서는 하나의 request를 받아ㅍ 2개의 DB table에 각각 레코드를 하나씩 추가해야 하는 상황이 있을 수 있다.

 첫번째 DB에서는 정보가 정상적으로 입력되었지만, 두번째 레코드에 데이터를 입력하려고 할때 Primary Key가 이미 존재해 Exception이 발생했다고 하자. 결과적으로 두번째 정보는 정상적으로 DB에 저장되지 않았기 때문에 시스템이 정상적으로 동작한 이후에 다시 사용자 데이터를 저장하도록 해야 할것이고, 응당 첫번째 DB에 입력된 사용자 정보는 다시 기존상태로 돌아가야 할 것이다. 

 이런 상황을 처리 하기 위해, 개발자 레벨에서 transaction의 단위를 설정할 수 있는 방법이 필요하다.



2. Transaction in Spring MVC

Spring에서는 여러가지 방법으로 Transaction을 제어할 수 있다.

*. Transactional  어노테이션 - 가장 간편한 방법중에 하나. Service 레이어에서 @Transactional 어노테이션을 사용하는 방식이다.

: 장점:간단하다. 단점:일일히 메소드마다 @Transactional을 걸어야 한다.


*. AOP 방식 - Service 영역에서 나타는 특정 함수들에 대해서, Exception이 발생하는 경우 Rollback시키는 방식

: 최초 설정은 복잡하다. 하지만 한번 설정을 잘 해 놓으면 메소드마다 뭔가를 할 필요가 없다. 약간의 성능 저하가 있을수 있다. transaction을 원하는 method들의 이름들을 잘 정해야 한다. (이름으로 transaction설정을 하므로) 이 글에서 다룰 방식이다.


*. 직접 start / commit / rollback 함수를 부르는 방식

: 코드량이 늘어난다. 하나의 transaction마다 일일히 commit/rollback 함수를 호출해야 한다. 기피하는 방식이다.


3. AOP를 이용한 Transaction처리

AOP는 관점지향적 프로그래밍 방식이다. AOP의 개념이 익숙하지 않다면 한번쯤 이 링크로 확인해 보길 바란다.

(http://isstory83.tistory.com/m/post/90 AOP는 xml설정파일과 약간의 script언어를 이용해, 자바 핵심 로직의 시작 전과 종료 직후에 부가적인 일을 할 수있도록 하는 방식이다. Spring의 Service 메소드들이 여러개의 DB명령을 처리하는 구조인 것을 생각할 때 transaction의 단위로 생각할 수 있다. Service method들이 불린 직후에 Transaction commit혹은 rollback 처리하는 방식은 매우 깔끔한 조합으로 보인다. 


4. 코딩에 들어가기 전에

mybatis에서는 sql 명령행이 하나 끝나면 기본적으로 commit이 된다. 이것을 auto commit이라고 부르고, commit된것을 다시 돌리기 위해서는 rollback 명령을 해줘야한다. 

* mysql을 사용하고 있다면, rollback을 하고자 하는 table의 DB engine이 innoDB인지 확인해야 한다. MyISAM은 rollback이 지원이 안되기 때문이다. 

* 만약 JUnit을 사용하지 않고 있다면 가능한 사용하는 것이 좋다. transaction rollback 관련 test들은 대부분 정상적이지 않는 DB의 상황을 고려하여 작업하는 것이기 때문에, 다양하게 에러가 나는 상황을 만들어 주는 것이 좋다. 그러기에 JUnit을 사용하기 바란다. Spring MVC을 사용하는 경우에 Control,Service,Repository 등의 annotation으로 생성된 Bean들을 어떻게 접근할 지 고민할수 있는데, spring-test라는 모듈을 활용하면 얼마든지 가능하다. 다음글에서 sping-test 활용법을 올리도록 하겠다.


5. 실제 코드 작성하기

* servlet-context.xml


<!-- 1) aop,tx 관련된 규칙들을 가져와야 한다.-->

<beans:beans xmlns="http://www.springframework.org/schema/mvc"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xmlns:tx="http://www.springframework.org/schema/tx"

xmlns:beans="http://www.springframework.org/schema/beans"

xmlns:context="http://www.springframework.org/schema/context"

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:p="http://www.springframework.org/schema/p"

xsi:schemaLocation="http://www.springframework.org/schema/mvc 

        http://www.springframework.org/schema/mvc/spring-mvc.xsd

http://www.springframework.org/schema/beans 

        http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/context 

        http://www.springframework.org/schema/context/spring-context.xsd

        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd

        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd

 

        ">


<!-- 2) Transaction을 관리하기위한 Bean을 생성하고 id를 부여한다. 

          dataSource는 applicationContext-mybatis.xml 안에 mySQL로 지정되어 있다 -->

<beans:bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

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

</beans:bean>


<!-- 3) AOP 설정부분. -->

<!-- proxy-target-class 는 proxy(일종의 wrapper)가 interface가 정의되지 않은 method들에 대해서 transaction을 사용하는 경우를 위한 것이다. 기본적으로 spring의 proxy-target-class 값은 false로, 타겟은 interface여야 하지만, serviceImpl.java 류들의 파일안의 모든 메소드들이 interface로 정의되어 있지 않기에 proxy-target-class를 true로 한것이다. 이를 가능하게 하기위해 cglib이라는 모듈을 사용한다. -->

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

    <!-- pointcut은 원하는 동작을 적용하고자 하는 클래스와 메소드의 이름을 기술하는 부분이다.  expression은 AspectJ의 pointcut 언어를 사용한다.  자신의 클래스 패키지의 service 폴더까지 지정하고 그 이후에 ..*Service.*(..)) 와 같이 그아래 모든 Service가 들어가는 파일명과 모든 메소드들에 대해서 pointcut을 지정했다. -->

    <aop:pointcut id="serviceOperation" expression="execution(* com.myapp.service..*Service.*(..))" /> 

    <!-- advisor는 우리말로 하면 충고인데.. pointcut에 대해 무슨일을 할지 정하는 것이다.  -->

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

</aop:config>


 <!--tx:advice 는 advisor에서 설정한 advice-ref를 기술해주는 부분이다. 처리를 담당하는 빈은  위에서 생성한 transactionManager 이며,  Exception이 발생할때 rollback 을 발생시키는 transaction rule을 지정했다. pointcut으로 지정된 모든 메소드들중에서도 특정 이름이 포함된것으로 한정할수 있다. db저장과 관련된 이름의 메소드들(register,save,update,remove로 시작하는 함수들)을 tx-의 method name으로 지정했다.->   

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

    <tx:attributes>

        <tx:method name="register*" rollback-for="Exception"/>

        <tx:method name="save*" rollback-for="Exception"/>

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

        <tx:method name="remove*" rollback-for="Exception"/>

    </tx:attributes>

 

</tx:advice>


 <!-- 아래부분은 굳이 안해줘도 되는 부분같다. 혹시 정상 동작 안할 경우에 시도해보시길-->

<context:component-scan base-package="com.myapp" use-default-filters="false">

    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />

</context:component-scan>


* pom.xml

<!-- pointcut expression을 사용하기 위해 aspectjweaver 가 필요하고, transaction 프락시인자로 클래스를 넣기위해 cglib을 가져왔다.-->

<dependency>

    <groupId>org.aspectj</groupId>

    <artifactId>aspectjweaver</artifactId>

    <version>1.6.8</version>

</dependency>

<dependency>

    <groupId>cglib</groupId>

    <artifactId>cglib-nodep</artifactId>

    <version>2.1_3</version>

</dependency>


* @Controller class의 request 처리 함수. 

/* catch로 DB exception을 잡아줘야 rollback이 완료된다.*/

RequestMapping(value = "/registerUserXXX", method = RequestMethod.POST )

 

public String registerWithFB(

    try {

        userViewService.registerUser(userId,nickname,email);

    }

    catch(Exception e) {

        System.out.println("Exception catched. do rollback e="+e.getMessage());

        return "redirect:login";

 

     }

}



참고글:

http://isstory83.tistory.com/m/post/90

http://roqkffhwk.tistory.com/m/post/112

 

728x90
반응형
블로그 이미지

nineDeveloper

안녕하세요 현직 개발자 입니다 ~ 빠르게 변화하는 세상에 뒤쳐지지 않도록 우리모두 열심히 공부합시다 ~! 개발공부는 넘나 재미있는 것~!

,