상세 컨텐츠

본문 제목

Spring 블로그 만들기 - 8.트랜잭션 처리

개발/Spring 블로그 만들기

by 똘똘이박사 2019. 2. 22. 05:43

본문



이 포스팅의 샘플 게시판 개발 환경은 MAC OS, STS, OpenJDK11 입니다.


트랜잭션 처리에 대한 포스팅은 아래와 같은 순서로 진행합니다.

  1. 트랜잭션 이란

  2. 트랜잭션 설정 방법1 - 선언적 트랜잭션 설정

  3. 트랜잭션 설정 방법2 - @Transactional 어노테이션 사용

  4. @Transactional 사용을 위한 Service 수정

  5. 트랜잭션 테스트


블로그 만들기 - 8. 트랜잭션 처리


바로 이전 포스팅에서는 게시판 처리 시 발생할 수 있는 문제점에 대해 예외처리를 하는 였습니다.

하지만 아직 처리하지 못한 중요한 문제점이 남아 있습니다.

에러가 발생하여 에러 페이지로 이동한 후 다시 리스트 화면으로 돌아와 새로 고침을 해봅니다.

그럼 해당 게시물의 조회수가 올라가는 것을 볼 수 있습니다.

조회수를 올리고 의도적으로 에러를 내서 정상적인 조회가 되지 않았지만

조회수가 올가가는 문제점이 남아 있습니다.

(조회가 제대로 되지 않았기 때문에 조회수는 변경이 되어서는 안되겠죠?)

이 문제를 해결하기 위해 트랜잭션 처리에 대해 정리해 봅니다.


트랜잭션 이란?

트랜잭션은 어떤 일련의 작업을 의미 합니다.

어떤 일련의 작업들은 모두 에러 없이 끝나야 하며, 

만약 중간에 에러가 발생 한다면, 에러 발생 이전 시점까지 작업되었던 내용은 모두 원상복구 되어야 합니다.

이렇게 데이터에 대한 무결성을 유지하기 위한 처리 방법을 '트랜잭션 처리' 라고 합니다.

간단한 예를 들어 보도록 하겠습니다.

온라인 쇼핑몰에서 물건을 구입 후 장바구니에 담았습니다.

그리고 결재를 합니다.

결재를 했으니 장바구니에 '결재완료'에 대한 정보가 업데이트 되어야 하고

판매자 에게는 구매자의 정보와 함께 '결재완료' 내용을 보내 줘야 합니다.

그런데 중간에 문제가 생겼습니다.

'결재완료' 정보는 업데이트 되었는데 판매자 시스템에 문제가 생겨 구매자의 정보와 '결재완료' 내용이 업데이트 되지 않았습니다.

당연히 판매자는 '결재완료' 데이터가 없으므로 구매자에게 물건을 배송하지 않았습니다.

구매자 입장에서는 장바구니에 '결재완료' 가 떴지만, 물건은 몇 일이 지나도록 배송이 안되는 상황이 발생합니다.

위 프로세스를 보면, 판매자의 시스템에 문제가 생겨 발생한 경우 입니다.

시스템 문제로 구매정보가 업데이트 되지 않았으니, 결재정보 또한 원상복구 하여 결재 처리가 되지 않도록 해야 억울한 피해자가 발생하지 않습니다.

이처럼 어떤 한 작업 묶음 속에서 문제가 발생 했을 경우, 원상복구 시키는 것을 트랜잭션 처리라고 부릅니다.


스프링에서는 트랜잭션을 처리할 수 있는 방법을 제공하고 있습니다.

  • 선언적 트랜잭션

  • @Transactional 어노테이션을 통한 트랜잭션



트랜잭션 설정 방법 1- 선언적 트랜잭션 

먼저 살펴볼 '선언적 트랜잭션'은, XML에 트랜잭션에 대한 설정을 함으로써

트랜잭션을 적용할 범위와 대상을 선언하는 방식 입니다.


트랜잭션 설정을 위해 새로운 설정 파일을 만듭니다.

[src/main/resources > spring > tx-context.xml] 파일을 아래와 같이 생성합니다.



tx-context.xml 추가

<?xml version="1.0" encoding="UTF-8"?>

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

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

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

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

xsi:schemaLocation="

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

http://www.springframework.org/schema/beans/spring-beans.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">

<!-- Transaction 설정 -->

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

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

  </bean>

   

  <!-- Transaction을 위한 AOP 설정 -->

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

<aop:pointcut id="servicePublicMethod" expression="execution(public * com.freehoon.web.board..*(int))" />

<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePublicMethod" />

</aop:config>

<!-- 선언적 Transaction 설정  -->

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

<tx:attributes>

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

</tx:attributes>    

  </tx:advice> 


</beans>


위 구문을 보면

트랜잭션 설정을 위해 tx와 aop 라는 두가지 네임스페이스를 사용합니다.

그리고 트랜잭션 설정을 의해 스프링에서 제공하는 org.springframework.jdbc.datasource.DataSourceTransactionManager 클래스를 transactionManager 이라는 이름의 빈으로 등록을 합니다.

transactionManager 빈의 프로퍼티로 데이터베이스 설정에 사용했던 dataSource 빈을 참조 할 수 있도록 추가 하였습니다.


선언적 트랜잭션 처리는 AOP를 이용합니다.

따라서 AOP에대한 내용을 이해하고 있어야 합니다.

여기서는 트랜잭션을 처리하기 위해 필요한 부분만 살펴보도록 하겠습니다.


<!-- Transaction을 위한 AOP 설정 -->

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

<aop:pointcut id="servicePublicMethod" expression="execution(public * com.freehoon.web.board..*(int))" />

<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePublicMethod" />

</aop:config>


aop:pointcut 는 aop pointcut를 지정하기위한 속성입니다. aop의 pointcut는 공통기능을 적용 할 대상을 지정한다는 뜻입니다.

따라서 위의 구문을 보면 servicePublicMethod 라는 이름의 pointcut id를 만드는데, 

적용할 대상은 public * com.freehoo.web.board..*(int) 라는 것입니다. 

적용대상의 구조는 아래 그림과 같습니다.



즉, 샘플게시판의 예제의 com.freehoon.web.board 패키지 하위에 포함되어 있는 모든 패키지 중에서

반환형은 상관없이 모두 대상입니다. 그중에 파라미터 타입이 int 인 인자를 하나만 가지고 있는 클래스가 대상이 됩니다.

이 샘플게시판에서 보면 아래의 클래스들은 모두 대상이 됩니다.

  • deleteBoard(int)

  • getBoardContent(int)

  • updateViewCnt(int)

따라서, 샘플게시판에서 현재 의도적으로 문제를 만든 getBoardContent() 메소드가 포함됩니다.


aop:advice 는 공통기능을 가지고 있는 가지고 있는 구현체를 말합니다.

속성으로 사용된 advice-ref 속성으로 구현체의 빈을 지정하고, 이 구현체를 적용할 대상을 pointcut-ref에 지정합니다.


<!-- 선언적 Transaction 설정  -->

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

<tx:attributes>

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

</tx:attributes>    

 </tx:advice>


공통기능의 구현체를 지정합니다.

그 구현체는 위에서 지정했던 transactionManager를 설정합니다.

속성으로 적용할 메소드 이름을 적어 주고, 트랙잭션 발생시 어떠한 동작을 할 지 지정해 줍니다.

따라서 위 구문의 의미는 getBoardContent메소드에서 exception이 발생하면 롤백을 한다는 의미 입니다.


설정을 저장하고 게시판 리스트로 돌아갑니다.

목록을 클릭하면 이전 포스팅에서 적용했던 예외페이지로 이동이 될 것입니다.

뒤로 가기 버튼을 클릭한 후 새로고침 버튼을 눌러 봅니다.

이제 조회수가 증가하지 않고 그대로 유지되고 있는 것을 확인 할 수 있습니다.



트랜잭션 설정 방법 2- @Transactional 어노테이션 사용 

xml을 이용한 선언적 트랜잭션은 설정할 내용이 많이 있습니다.

하지만 어노테이션을 이용한 방법은 다른 스프링의 어노테이션 처럼 적은 코드로 동일한 효과를 볼 수 있도록 도와줍니다.


조금전에 생성했던 tx-context.xml파일을 아래와 같이 수정합니다.


tx-context.xml

<!-- Transaction 설정 -->

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

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

</bean>

 

<!-- Transaction을 위한 AOP 설정 -->

<!--  //주석처리

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

<aop:pointcut id="servicePublicMethod" expression="execution(public * com.freehoon.web.board..*(int))" />

<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePublicMethod" />

</aop:config>

 -->

<!-- 선언적 Transaction 설정  -->

<!--  //주석처리

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

<tx:attributes>

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

</tx:attributes>    

</tx:advice>

 -->

<tx:annotation-driven/>


기존의 <aop:config></aop:config> 와 <tx:advice></tx:advice>를 주석 처리 합니다.

그리고 <tx:annotation-driven/>을 새롭게 추가 합니다.

BoardServiceImpl.java에 @Transactional 어노테이션을 적용합니다.


BoardServiceImpl.java 수정

@Transactional

@Override

public BoardVO getBoardContent(int bid) throws Exception{

BoardVO boardVO = new BoardVO();


boardDAO.updateViewCnt(bid);

// boardVO = boardDAO.getBoardContent(bid);

try {

boardVO.setBid(bid);

boardVO.setCate_cd("1111111111111111111111111111111111111");

boardDAO.updateBoard(boardVO);

} catch (RuntimeException e) {

throw new NotFoundException();

}

return boardVO;

}


변경내용을 적용하고

선언적 트랜잭션과 동일한 방법으로 테스트 해봅니다.

게시판 리스트에서 목록을 클릭합니다.

예외처리페이지로 이동이 되었다면 '뒤로가기' 버튼을 클릭하여 다시 게시판 리스트 화면으로 돌아 옵니다.

'새로고침' 버튼을 클릭하여 조회수가 변경 되는지 확인해 봅니다.









※ 포스팅에 오타나 잘못된 부분, 추가적으로 더 알고 싶은 부분이 있으면 댓글 주세요~


반응형

관련글 더보기