본문 바로가기

개발/BACK

SpringFramework 트랜잭션 처리 예제 @Transactional

728x90

 


 

SpringFramework 개발 환경에서 서비스 로직을 구현할 때,

하나의 서비스에 여러 개의 서비스 로직이 존재해서 데이터 베이스 접근이 여러번 일어날 수 있다.

 

예 )

 

public int insertLib(TestVO testVO){
    ...
    int result = testService.updateBook(testVO);   --첫번째 접근
 
    if(result > 0){
        testService.insertPoint(testVO); -- 두번째 접근
    }
    
    testDao.insertLib(testVO); -- 세번째 접근
 
    return 1;
}
cs

insertLib 서비스 내에는 updateBook, insertPoint, insertLib 세 가지 처리가 있다

 

만약, updateBook 에서 등록을 진행하고

 

insertPoint에서 Exception이 발생했을 때, updateBook은 실행되어 데이터베이스에 입력되었을 것이다

 

이러한 현상을 해결하기 위한 방법으로 트랜잭션을 사용한다

 

트랜잭션이란 데이터베이스의 상태를 변화시키기 해서 수행하는 작업의 단위를 말한다

 

해당 작업의 단위를 위 3개의 기능을 묶음으로 설정해서 한가지라도 오류가 발생하면,

이미 실행된 쿼리를 롤백해주는 기능을 구현할 수 있다

 

이번 포스팅에서는 스프링 프레임워크에서 트랜잭션을 구현하는 방법을 포스팅한다


 

1

pom.xml [dependency] 추가

 

https://mvnrepository.com/

 

해당 페이지에서 

 

[aspectjweaver]

 

[cglib]

 

두가지 maven을 검색해 pom.xml에 추가해주자

 

    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
            <scope>runtime</scope>
    </dependency>
 
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>2.2</version>
        <type>jar</type>
        <scope>compile</scope>
    </dependency>
 
cs

@Transactional 태그는 Spring AOP 기반인데, 

 

Spring AOP는 dynamic Proxy 를 이용하고, dynamic Proxy는 인터페이스 구현체에서 사용할 수 있다

 

그래서 인터페이스 구현체에서 사용하지 않고, 클래스 파일에 사용하려면  CGLib Proxy 의존성이 필요하다

 


2

servlet-context.xml 

파일 수정

 

    application-context.xml OR app-context.xml

    
 
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="..."/>
        <property name="url" value="..."/>
        <property name="username" value="..."/>
        <property name="password" value="..."/>
        <property name="initialSize" value="..." />
            ...
      </bean>
  
      
      <bean id="dataSourceSQL" class="net.sf.log4jdbc.Log4jdbcProxyDataSource">
        <constructor-arg ref="dataSource" />
        <property name="logFormatter">
              <bean class="net.sf.log4jdbc.tools.Log4JdbcCustomFormatter">
                ...
              </bean>
        </property>
      </bean>
 
cs

 

servlet-context.xml

      <tx:annotation-driven proxy-target-class="true"/>
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSourceSQL"/>
    </bean>    
cs

절대로 app-context.xml 파일에 tx:annotation-driven 이하 코드들을 설정하면 안된다 !

 

app-context.xml에 설정한다면 현재 servlet-context.xml에 설정되어 있는 bean들에 트랜잭션이 적용되지 않는다

 

여기서 많은 이유로 트랜잭션이 미작동한다

 

절대 servlet-context.xml에 아래 코드를 설정해야하는 것을 잊지 말자

 

 또한, proxy-target-class 속성은 CGLib 메이븐을 받고서 사용해야 오류가 발생하지 않는다 

 

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

 

여기 property 에서 ref로 받고있는 dataSource는 

 

net.sf.log4jdbc.Log4jdbcProxyDataSource

 

를 참고하고 있는 dataSourceSQL을 참조하도록 하자

 


 

3

적용

 

트랜잭션을 적용하려는 메소드 위에 입력해주면 된다

 

참고로 트랜잭션은 메소드, 클래스, 인터페이스 모두 사용할 수 있다

 

[우선순위]

클래스의 메소드 > 클래스 > 인터페이스의 메소드 > 인터페이스

 

그리고 만약 @Transactional 태그가 적용된 메소드를 

 

@Transactional 태그가 적용되지 않은 메소드에서 호출하여 사용한다해도 트랜잭션은 적용되지 않는다

 

트랜잭션은 항상 부모 메소드,클래스에 사용하도록 하자

 

@Service
public class TestService {
       ...
       ...
 
 
@Transactional
public int insertLib(TestVO testVO){
    ...
    int result = testService.updateBook(testVO);   --첫번째 접근
 
    if(result > 0){
        testService.insertPoint(testVO); -- 두번째 접근
    }
    
    testDao.insertLib(testVO); -- 세번째 접근
 
    return 1;
}
 
}
cs

 

해당 태그를 적용해주면, 해당 메소드 도중 에러가 발생하면 기존에 처리했던 메소드를 모두 롤백해버린다

 

예 )

updateBook 실행, 완료 -> insertPoint 실행, 완료 -> insertLib  실패(에러 발생)

DB에 반영했던 updateBook,insertPoint 모두 롤백 처리

 

마지막으로 Exception을 구분해서 트랜잭션 처리를 할 수 있는데, 태그 옆에 

 

@Transactional(rollbackFor = {RuntimeException.class})

이런 식으로 작성해서 사용하면 된다

728x90