Kim VamPa

[Spring][쇼핑몰 프로젝트][27] 업로드 이미지 정보 등록 - 5 (트랜잭션 적용) 본문

스프링 프레임워크/쇼핑몰 프로젝트

[Spring][쇼핑몰 프로젝트][27] 업로드 이미지 정보 등록 - 5 (트랜잭션 적용)

Kim VamPa 2021. 6. 15. 10:02
728x90
반응형
프로젝트 Github : https://github.com/sjinjin7/Blog_Project
프로젝트 포스팅 색인(index) : https://kimvampa.tistory.com/188

** 죄송합니다. 개인 사정으로 좀 쉰 후 다시 포스팅 하겠습니다. 

06/21

 

목표

트랜잭션(Transaction) 적용

 직전 포스팅까지 작성하여 완성한 Service 단계의 bookEnroll() 메서드는 내부적으로 각각의 쿼리를 실행하는 Mapper 단계의 2개의 메서드를 호출합니다. 그런데 현재 작성된 코드에서 2개의 Mapper메서드 중 하나의 메서드가 에러가 발생을 한다면, 에러가 발생하지 않은 메서드가 수행하는 쿼리문은 수행되고 에러가 발생한 메서드의 쿼리문은 실행이 되지 않을 것입니다. 

 이와 같은 결과가 별 것 아니라고 생각을 할 수 있지만 만약 위의 상황이 결제시스템이라고 가정을 해본다면 이러한 상황은 매우 심각한 문제일 수 있습니다. bookEnroll() 메서드에서 호출하는 2개의 메서드가 각각 구매자에게선 돈을 차감하고, 판매자에게선 돈을 더하는 쿼리를 수행한다고 가정해보겠습니다. 이러한 상황에서 판매자에게서 돈을 더하는 쿼리를 호출하는 메서드에 에러가 남으로 인해서 구매자의 돈은 차감되는 쿼리는 실행이 되고 판매자의 돈을 더해주는 쿼리는 실행이 되지 않는다면 매우 큰 문제가 될 것입니다. 이로 인해서 개발자는 이러한 가능성이 생길 수 있는 수많은 예외 상황을 대비하기 위해 많은 시간과 노력을 투입시켜야 할 것입니다.

 이러한 문제가 발생할 수 있는 상황에서 사용 할 수 있는 스프링에서의 기능이 "트랜잭션(Transactional)"입니다.  트랜잭션 기능을 사용하게 된다면 내부적으로 2개 혹은 그 이상의 작업 단위들로 묶인 메서드가 하나의 단위(하나의 작업이라도 실패 시 모든 작업 취소, 모두 성공 시 모든 작업 처리)처럼 동작할 수 있도록 해 주고, 그 결과를 보장해줍니다. 따라서 개발자 입장에선 여러 예외상황을 대비하기 위한 수많은 코드를 추가해주지 않음에도 안정된 코드를 작성할 수 있습니다.

 

 이번 포스팅에선 트랜잭션을 사용하기 위한 기본적인 설정 방법에 대해 알아보고, 트랜잭션 사용 방법 중 어노테이션을 사용하여 bookEnroll() 메서드에 트랜잭션을 적용하는 것이 목표입니다. (트랜잭션은 세부적으로 많은 설정을 할 수 있고 어노테이션 이외에도 다른 방식으로도 사용을 할 수 있 도 있습니다. 모든 것을 설명하게되면 글이 너무 길어져 기본적인 설정 방법과 어노테이션을 통한 사용방법만 소개하고자 합니다.)

 

 

순서

1. 적용하지 않은 경우 문제 상황

2. 트랜잭션 설정

3. 트랜잭션 어노테이션 적용

4. 테스트

 

 

1. 적용하지 않은 경우 문제 상황

 고의적으로 bookEnroll() 메서드 내부에 동작하는 Mapper 메서드중 하나에 에러를 발생시키면 어떠한 결과 나오는지 직접 확인을 해보겠습니다. 테스트를 위해 직전 포스팅에서 작성한 Junit 테스트 코드를 활용하겠습니다.

 

 테스트 위해서 vam_image 테이블에 있는 모든 데이터를 지워 주었습니다. (Oracle 경우 delete 명령 후 commit 명령도 실행해야 합니다.)

DELETE FROM vam_image;

 

 아래는 AdminServiceTests.java에 작성한 기존 테스트 코드 입니다.

 

	/* 상품 등록 & 상품 이미지 등록 테스트 */
	@Test
	public void bookEnrollTEsts() {

		BookVO book = new BookVO();
		// 상품 정보
		book.setBookName("mapper 테스트");
		book.setAuthorId(8);
		book.setPubleYear("2021-03-18");
		book.setPublisher("출판사");
		book.setCateCode("202001");
		book.setBookPrice(20000);
		book.setBookStock(300);
		book.setBookDiscount(0.23);
		book.setBookIntro("책 소개 ");
		book.setBookContents("책 목차 ");

		// 이미지 정보
		List<AttachImageVO> imageList = new ArrayList<AttachImageVO>(); 
		
		AttachImageVO image1 = new AttachImageVO();
		AttachImageVO image2 = new AttachImageVO();
		
		image1.setFileName("test Image 1");
		image1.setUploadPath("test image 1");
		image1.setUuid("test1111");
		
		image2.setFileName("test Image 2");
		image2.setUploadPath("test image 2");
		image2.setUuid("test2222");
		
		imageList.add(image1);
		imageList.add(image2);
		
		
		book.setImageList(imageList);
		
		// bookEnroll() 메서드 호출
		service.bookEnroll(book);
		
		System.out.println("등록된 VO : " + book);
		
		
	}

 

 코드의 흐름은 상품 정보를 BookVO클래스 객체에 담고, 2개의 이미지 정보를 BookVO의 imageList의 첫 번째 요소, 두 번째요소에 각각 담은 후 BookVO를 Service 단계의 bookEnroll()메서드에 넘겨주어 실행 하도록 작성되어져 있습니다.

 

 저는 두번째 이미지 정보에 잘못된 데이터를 전달하여 고의적으로 에러를 발생시켜보겠습니다. (vam_image 테이블의 fileName 컬럼(Column)이 크기가 100인데 그 이상의 데이터를 넣었습니다.)

		// 기존 코드
        image2.setFileName("test Image 2");
		image2.setUploadPath("test image 2");
		image2.setUuid("test2222");
        
        // 수정 코드
        image2.setFileName("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
		image2.setUploadPath("test image 2");
		image2.setUuid("test2222");

 

그림 1-1

 

 Junit 테스트를 해보면 의도대로 너무 많은 데이터를 전달함으로 인해 에러가 난 걸 볼 수 있습니다.

 

그림 1-2

 

 DB의 결과를 보게 되면 상품의 정보와 첫 번째 이미지 정보는 등록되어 잇는 것을 볼 수 있습니다. 따라서 에러가 난 메서드의 쿼리문만 실행이 되지 않고 나머지 메서드는 정상적으로 동작한 것을 볼 수 있습니다.

 

그림 1-3

 

그림 1-4

 

 

 

 

2. 트랜잭션 설정

 

 가장 먼저 pom.xml에 트랜잭션 관련 dependency를 추가해주어야 합니다. 

 

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-tx</artifactId>
		    <version>${org.springframework-version}</version>
		</dependency>

 

 초기 프로젝트 설정 때 이미 추가해주었기 때문에 저는 추가해줄 필요가 없었지만 만약 추가하지 않으셨다면 추가해주셔야 합니다.

 

 트랜잭션 기능을 사용해주기 위해선 DataSourceTransactionManager 라는 클래스를 Bean 객체로 등록해주어서 스프링에서 이 기능을 인식하고 사용할 수 있도록 해주어야 합니다. 따라서 root-context.xml 파일에 아래와 코드를 추가해서 해당 클래스를 Bean 객체로 추가해줍니다.

 

	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="datasource"></property>
	</bean>

 

그림 2-1

 우리는 트랜잭션을 사용하는 방법 중 어노테이션 기반으로 사용을 할 것입니다. 따라서 먼저 root-context.xml 파일 창 하단에 namespace 탭에서 tx 항목을 체크해줍니다.

 

그림 2-2

 

 다시 source 탭을 눌러서 테스트 창을 연 후 아래의 태그를 추가해줍니다. (어노테이션 기반 트랜잭션을 사용하기 위해 추가해주는 태그입니다.)

 

<tx:annotation-driven />

 

그림 2-3

 이로써 트랜잭션 어노테이션을 사용하기 위한 준비가 되었습니다.

 

 

3. 트랜잭션 어노테이션 적용

 기본적인 설정이 완료되었기 때문에 이제 대상 메서드에 트랜잭션을 적용해보겠습니다. 사용방법은 간단합니다. 적용해야 할 메서드 선언부에 @Transactional 어노테이션만 추가해주며 됩니다.

 

@Transactional

 

 AdminServiceImpl.java 에 있는 bookEnrll() 메서드 선언부에 @Transactional 어노테이션을 추가해 줍니다.

 

그림 3-1

 

 

 

4. 테스트

 포스팅 목차 1에서 행했던 테스트와 어떠한 점이 달라졌는지 테스트해보겠습니다. 테스트를 위해 기존의 vam_image 테이블의 데이터를 지워 줍니다.

 

delete from vam_image;

 

 

목차 1에서 사용했던 테스트 코드를 그대로 Junit 테스트해봅니다. (달라진 점은 단지 bookEnroll() 메서드에 @Transactional 어노테이션을 추가해준 것뿐입니다.)

 

그림 4-1

 

 앞의 테스트와 동일하게 기존 설정한 크기의 데이터보다 큰 크기의 데이터가 컬럼값으로 들어와서 에러가 발생을 하였습니다. 이제 앞의 테스트처럼 에러가 발생한 이외의 쿼리문들이 실행이 되었는지 확인해야 합니다. 앞선 테스트로 인해서 이번에 등록될 bookId는 '3074'인데 해당 행이 등록되었는지 확인해보겠습니다.

 

그림 4-2

 

 아래의 DB의 결과를 보시면 vam_book, vam_image 테이블 모두 아무것도 등록되어 있는 것을 볼 수 있습니다. 트랜잭션 설정으로 인해서 bookEnroll() 메서드에서 처리해야 할 작업 중 하나의 작업에서 예외상황이 발생을 하였고 이로 인해 기존 진행했던 작업도 모두 취소되어서입니다.

 

그림 4-3

 

그림 4-4

 

REFERENCE

  • 코드로배우는 스프링 웹 프로젝트(남가람북스)
  •  

 

 

 

DATE

  • 2020.06.15

 

 

728x90
반응형
Comments