본문 바로가기
Spring/Spring

[Spring] 스프링을 이용한 MVC패턴 구현(4) - @Service 구성

by J4J 2021. 2. 16.
300x250
반응형

개요

 

들어가기에 앞서
Transaction이란?
Transaction 특징
Commit / Rollback
@Service란?
프로젝트 설정
설정 테스트
데이터 롤백 테스트
RootContext 설정 파일 변경 (xml → Java)
파일 구성

 

 

 

안녕하세요. J4J입니다.

 

이번 포스팅은 스프링을 이용한 MVC패턴 구현 중 네 번째인 @Service구성에 대해 적어보는 시간을 가져보려고 합니다.

 

 

들어가기에 앞서

 

 

 

이번 포스팅은 위의 스프링 MVC구조에서 빨간 박스에 포함되는 내용에 대해 적어보고자 합니다.

 

 

Transaction이란?

 

@Service에 대해 이해하기 위해서는 트랜잭션의 개념부터 알고 있어야 합니다.

 

트랜잭션이란 데이터베이스에서 데이터 처리를 위한 하나의 논리적인 작업 단위를 의미합니다.

 

예를 들어 A통장에 있는 돈을 B통장에 계좌이체한다고 가정해보겠습니다.

 

만원을 계좌이체 할 경우 A통장의 잔액에서 만원을 출금하는 작업을 해야 되고 B통장의 잔액에서 만원을 입금하는 작업을 진행해야 됩니다.

 

여기서 계좌이체를 한다라는 것을 하나의 작업 단위로 본다면 A통장에서 출금이 되고 B통장에 돈이 입금되는 게 정상적으로 이루어질 경우 트랜잭션 처리 하나가 이루어졌다고 말할 수 있습니다.

 

 

Transaction 특징

 

트랜잭션의 특징은 4가지가 있습니다.

 

1. 원자성(Atomicity): 하나의 트랜잭션이 처리될 때 모든 작업이 정상적으로 동작되어야 데이터베이스 반영

 

예를 들어 계좌 이체를 할 때 A통장에서는 만원을 출금하는 것이 정상적으로 동작되었지만 B통장에 돈이 입금되는게 에러 날 경우 정상적으로 동작한 A통장의 출금도 이전의 상태로 돌아가야 합니다.

 

 

 

2. 일관성(Consistency): 트랜잭션 처리 전과 후가 일관된 데이터 상태를 유지

 

예를 들어 출금 전과 출금 후의 A통장과 B통장의 잔액의 합이 동일해야 합니다.

 

 

 

3. 독립성(Isolation): 한 트랜잭션 처리가 작업되는 동안 다른 트랜잭션은 처리 불가

 

예를 들어 A통장과 B통장의 계좌이체가 완료가 되지 않은 상태에서 C통장과 D통장의 계좌이체가 진행되지 못하는 것을 의미합니다.

 

 

 

4. 지속성(Durability): 트랜잭션 처리가 정상적으로 이루어지면 결과가 영구히 반영

 

예를 들어 A통장과 B통장의 계좌이체가 정상적으로 이루어지면 시스템이 다운된 후 복구되어도 계좌이체 이전의 상태로 돌아가지 않고 이체된 상태로 유지되는 것을 의미합니다.

 

 

Commit / Rollback

 

데이터베이스에서 Commit과 Rollback은 다음과 같은 역할을 수행합니다.

 

1. Commit: 트랜잭션 처리가 이루어진 데이터를 데이터베이스에 반영

 

2. Rollback: 트랜잭션 처리가 이루어진 데이터를 데이터베이스에 반영하지 않고 트랜잭션 처리 이전의 상태로 복구

 

 

@Service란?

 

@Service는 스프링의 비즈니스 로직 처리에서 트랜잭션 처리를 위한 구간입니다.

 

@Repository에 속하는 여러 메서드들을 하나의 작업 단위로 묶어 처리되는 데이터 작업 중 하나라도 실패하게 된다면 Rollback 시키고 모두 정상적으로 처리되면 Commit 시켜줍니다.

 

@Service구간이 정상적으로 구현되어 있다면 사용자가 시스템을 사용하는 도중 데이터 처리와 관련된 에러가 발생하더라도 일부 데이터만 생기는 등의 문제가 발생되지 않습니다.

 

 

반응형

 

 

프로젝트 설정

 

※ 스프링과 관련된 코드는 모두 STS-3.9.12.RELEASE 버전을 기준으로 작성되었습니다.

 

[ 1. 트랜잭션 처리를 위한 dependency를 추가 ]

 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.spring</groupId>
	<artifactId>mvc</artifactId>
	<name>mvc</name>
	<packaging>war</packaging>
	<version>1.0.0-BUILD-SNAPSHOT</version>
	<properties>
		<java-version>1.8</java-version>
		<org.springframework-version>5.2.11.RELEASE</org.springframework-version>
		<org.aspectj-version>1.9.6</org.aspectj-version>
		<org.slf4j-version>1.7.30</org.slf4j-version>
	</properties>
	<dependencies>
		<!-- Spring -->
		
        	...
		
		<!-- MySQL, MyBatis, @Repository -->
		
        	...
		
		<!-- @Service -->
		<dependency> <!-- 트랜잭션 처리를 위한 dependency -->
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
				
			...
        
	</dependencies>
    	<build>
    
        	...
        
    	</build>
</project>

 

 

[ 2. root-context.xml의 Namespaces에서 tx 체크 ]

 

 

 

[ 3. root-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:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

	<context:component-scan
		base-package="com.spring.mvc.repository, com.spring.mvc.service" /> <!-- repository, service관련 클래스 등록 패키지 -->
        
    	...    
	
	<tx:annotation-driven transaction-manager="transactionManager"/> <!-- 트랜잭션 어노테이션 적용을 위한 설정 -->
</beans>

 

 

[ 4. Service 인터페이스 및 클래스 생성 (com.spring.mvc.service.CartService / com.spring.mvc.service.CartServiceImpl) ]

 

package com.spring.mvc.service;

import java.util.List;

import com.spring.mvc.dto.Cart;

public interface CartService {
	
	public int insertCarts(Cart cart); // cart데이터를 저장하는 용도
	
	public List<Cart> selectCart(); // cart데이터 조회 용도
}

 

package com.spring.mvc.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.spring.mvc.dto.Cart;
import com.spring.mvc.repository.CartRepository;

@Service // 해당 클래스를 빈으로 등록
public class CartServiceImpl implements CartService {
	
	@Autowired
	CartRepository cartRepositoryImpl;
	
	@Override
	@Transactional // 하나의 작업단위로 묶어 트랜잭션 처리를 위한 어노테이션
	public int insertCarts(Cart cart) {
		Cart myCart = new Cart(); // 임의로 만든 카트 데이터
		myCart.setName("쌀");
		myCart.setPrice(1200);
		myCart.setCount(6);
		
		int res = cartRepositoryImpl.insertCart(cart);
		res = res + cartRepositoryImpl.insertCart(myCart);
		
		return res;
	}
	
	@Override
	public List<Cart> selectCart() {
		return cartRepositoryImpl.selectCart();
	}
}

 

 

@Transactional이 있을 경우에만 트랜잭션 처리가 되기 때문에 다수의 데이터가 삽입되는 insertCarts에는 어노테이션을 추가하고 단순 조회를 위한 selectCart에는 어노테이션을 추가하지 않았습니다.

 

 

설정 테스트

 

※ 스프링과 관련된 코드는 모두 STS-3.9.12.RELEASE 버전을 기준으로 작성되었습니다.

 

위의 설정이 정상적으로 되었는지 단위 테스트를 진행해보겠습니다.

 ※ 단위 테스트에 대해 모르신다면? 2021/02/04 - [IT/Spring] - [Spring] 단위 테스트(JUnit Test)

 

[ 1. 이전 포스팅에서 테스트를 위해 생성했던 클래스에 Service 테스트를 위한 메서드 추가 (com.spring.mvc.MVC) ]

 

package com.spring.mvc;

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import com.spring.mvc.dto.Cart;
import com.spring.mvc.service.CartService;

@RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/root-context.xml"}) // xml 방식
//@ContextConfiguration(classes = RootContext.class) // java 방식
public class MVC {
	
	private static Logger logger = LoggerFactory.getLogger(MVC.class);
	
	...
	
	@Autowired
	CartService cartServiceImpl;
	
	@Test
	public void serviceTest() {
		Cart cart = new Cart();
		cart.setName("김치");
		cart.setPrice(1500);
		cart.setCount(4);
		
		logger.debug(cartServiceImpl.insertCarts(cart) + ""); // Cart 데이터 + 임의로 만든 데이터
		logger.debug(cartServiceImpl.selectCart().toString()); // Cart 데이터 조회
	}
}

 

 

[ 2. 테스트를 진행할 경우 아래와 같이 데이터가 삽입되고 조회되는 것이 확인 가능 ]

 

INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@6ebc05a6, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@6e6c3152, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@50b494a6, org.springframework.test.context.support.DirtiesContextTestExecutionListener@3cef309d, org.springframework.test.context.transaction.TransactionalTestExecutionListener@32709393, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@3d99d22e, org.springframework.test.context.event.EventPublishingTestExecutionListener@49fc609f]
DEBUG: org.springframework.core.env.StandardEnvironment - Activating profiles []
DEBUG: org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\User\Desktop\tistory_spring\mvc\target\classes\com\spring\mvc\repository\CartRepositoryImpl.class]
DEBUG: org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\User\Desktop\tistory_spring\mvc\target\classes\com\spring\mvc\service\CartServiceImpl.class]
DEBUG: org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 10 bean definitions from URL [file:src/main/webapp/WEB-INF/spring/root-context.xml]
DEBUG: org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@7e2d773b
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cartRepositoryImpl'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSession'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSessionFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dataSource'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cartServiceImpl'
DEBUG: com.spring.mvc.cartmapper.insertCart - ==>  Preparing: insert into cart(name, price, count) values (?, ?, ?)
DEBUG: com.spring.mvc.cartmapper.insertCart - ==> Parameters: 김치(String), 1500(Integer), 4(Integer)
DEBUG: com.spring.mvc.cartmapper.insertCart - <==    Updates: 1
DEBUG: com.spring.mvc.cartmapper.insertCart - ==>  Preparing: insert into cart(name, price, count) values (?, ?, ?)
DEBUG: com.spring.mvc.cartmapper.insertCart - ==> Parameters: 쌀(String), 1200(Integer), 6(Integer)
DEBUG: com.spring.mvc.cartmapper.insertCart - <==    Updates: 1
DEBUG: com.spring.mvc.MVC - 2
DEBUG: com.spring.mvc.cartmapper.selectCart - ==>  Preparing: select name, price, count from cart
DEBUG: com.spring.mvc.cartmapper.selectCart - ==> Parameters: 
DEBUG: com.spring.mvc.cartmapper.selectCart - <==      Total: 2
DEBUG: com.spring.mvc.MVC - [Cart [name=김치, price=1500, count=4], Cart [name=쌀, price=1200, count=6]]
DEBUG: org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@7e2d773b, started on Tue Feb 16 21:47:32 KST 2021

 

 

[ 3. MySQL에서도 데이터 적재 확인 ]

 

 

 

[ 4. 추후 설정을 위해 insertCarts메서드의 임의 데이터 제거 (com.spring.mvc.service.CartServiceImpl) ]

 

package com.spring.mvc.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.spring.mvc.dto.Cart;
import com.spring.mvc.repository.CartRepository;

@Service // 해당 클래스를 빈으로 등록
public class CartServiceImpl implements CartService {

	...
    
	@Override
	@Transactional // 하나의 작업단위로 묶어 트랜잭션 처리를 위한 어노테이션
	public int insertCarts(Cart cart) {
		return cartRepositoryImpl.insertCart(cart);
	}
    
    	...
}

 

 

데이터 롤백 테스트

 

※ 스프링과 관련된 코드는 모두 STS-3.9.12.RELEASE 버전을 기준으로 작성되었습니다.

 

위의 테스트는 설정된 코드가 정상적으로 수행되었기 때문에 에러가 발생되었을 때 데이터가 롤백되는 것을 확인할 수 없습니다.

 

에러가 발생되는 케이스를 의도적으로 만들어 트랜잭션 처리가 정상적으로 이루어지는 확인 해보겠습니다.

 

[ 1. mapper에 실패할 수밖에 없는 쿼리 추가 (src/main/resources/mapper/cartmapper.xml) ]

 

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.spring.mvc.cartmapper"> <!-- Repository와 연동을 위해 사용될 namespace -->
	
    ...
	
	<update id="updateCart">
		update cart
		set price = '테스트' -- price는 타입이 int라 문자열이 들어갈 수 없음
		where name = '김치'
	</update>
</mapper>

 

 

728x90

 

 

[ 2. Repository 인터페이스와 클래스에 데이터 접근을 위한 메서드 추가 (com.spring.mvc.repository.CartRepository / com.spring.mvc.repository.CartRepositoryImpl) ]

 

package com.spring.mvc.repository;

import java.util.List;

import com.spring.mvc.dto.Cart;

public interface CartRepository {
	
	...
	
	public int updateCart(); // 트랜잭션 테스트 용도
}

 

package com.spring.mvc.repository;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.spring.mvc.dto.Cart;

@Repository // 해당 클래스를 bean으로 등록
public class CartRepositoryImpl implements CartRepository {
	
	private String namespace = "com.spring.mvc.cartmapper."; // mapper파일의 namespace + "."
	
	@Autowired
	SqlSession session;
	
	...
	
	@Override
	public int updateCart() {
		return session.update(namespace + "updateCart");
	}
}

 

 

[ 3. Service 인터페이스와 클래스에 정상 로직과 비정상 로직이 포함된 메서드 추가, 우선 @Transaction을 제외 (com.spring.mvc.service.CartService / com.spring.mvc.service.CartServiceImpl) ]

 

package com.spring.mvc.service;

import java.util.List;

import com.spring.mvc.dto.Cart;

public interface CartService {
	
	...
	
	public int testCarts(Cart cart); // 트랜잭션 테스트 용도
}

 

package com.spring.mvc.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.spring.mvc.dto.Cart;
import com.spring.mvc.repository.CartRepository;

@Service // 해당 클래스를 빈으로 등록
public class CartServiceImpl implements CartService {
	
	@Autowired
	CartRepository cartRepositoryImpl;
	
	...
	
	@Override
	public int testCarts(Cart cart) {
		int res = 0;
		
		res = res + cartRepositoryImpl.insertCart(cart); // 정상 로직
		res = res + cartRepositoryImpl.updateCart(); // 비정상 로직
		
		return res;
	}
}

 

 

[ 4. 테스트를 위한 메서드 추가 (com.spring.mvc.MVC) ]

 

package com.spring.mvc;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import com.spring.mvc.dto.Cart;
import com.spring.mvc.service.CartService;

@RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/root-context.xml"}) // xml 방식
//@ContextConfiguration(classes = RootContext.class) // java 방식
public class MVC {
	
	private static Logger logger = LoggerFactory.getLogger(MVC.class);
	
	...
	
	@Autowired
	CartService cartServiceImpl;
	
	@Test
	public void transactionTest() {
		Cart cart = new Cart();
		cart.setName("트랜잭션 테스트");
		cart.setPrice(1000);
		cart.setCount(1);
		
		logger.debug(cartServiceImpl.testCarts(cart) + ""); // 트랜잭션 테스트
	}
}

 

 

[ 5. 테스트를 할 경우 insertCart는 정상 동작하지만 updateCart는 비정상 동작을 하여 에러가 발생 ]

 

INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@6ebc05a6, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@6e6c3152, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@50b494a6, org.springframework.test.context.support.DirtiesContextTestExecutionListener@3cef309d, org.springframework.test.context.transaction.TransactionalTestExecutionListener@32709393, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@3d99d22e, org.springframework.test.context.event.EventPublishingTestExecutionListener@49fc609f]
DEBUG: org.springframework.core.env.StandardEnvironment - Activating profiles []
DEBUG: org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\User\Desktop\tistory_spring\mvc\target\classes\com\spring\mvc\repository\CartRepositoryImpl.class]
DEBUG: org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\User\Desktop\tistory_spring\mvc\target\classes\com\spring\mvc\service\CartServiceImpl.class]
DEBUG: org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 10 bean definitions from URL [file:src/main/webapp/WEB-INF/spring/root-context.xml]
DEBUG: org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@7e2d773b
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cartRepositoryImpl'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSession'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSessionFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dataSource'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cartServiceImpl'
DEBUG: com.spring.mvc.cartmapper.insertCart - ==>  Preparing: insert into cart(name, price, count) values (?, ?, ?)
DEBUG: com.spring.mvc.cartmapper.insertCart - ==> Parameters: 트랜잭션 테스트(String), 1000(Integer), 1(Integer)
DEBUG: com.spring.mvc.cartmapper.insertCart - <==    Updates: 1
DEBUG: com.spring.mvc.cartmapper.updateCart - ==>  Preparing: update cart set price = '테스트' -- price는 타입이 int라 문자열이 들어갈 수 없음 where name = '김치'
DEBUG: com.spring.mvc.cartmapper.updateCart - ==> Parameters: 
DEBUG: org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 11 bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'DB2'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'Derby'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'H2'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'HDB'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'HSQL'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'Informix'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'MS-SQL'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'MySQL'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'Oracle'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'PostgreSQL'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'Sybase'
DEBUG: org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@7e2d773b, started on Tue Feb 16 22:10:22 KST 2021

 

 

[ 6. Service단에서 트랜잭션을 위한 어노테이션이 추가되지 않았기 때문에 정상 실행된 insertCart에 의해 데이터가 반영된 것을 확인 ]

 

 

 

[ 7. Service 클래스에 @Transactional 추가 (com.spring.mvc.service.CartServiceImpl) ]

 

package com.spring.mvc.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.spring.mvc.dto.Cart;
import com.spring.mvc.repository.CartRepository;

@Service // 해당 클래스를 빈으로 등록
public class CartServiceImpl implements CartService {
	
	@Autowired
	CartRepository cartRepositoryImpl;
	
    	...
	
	@Override
	@Transactional // 트랜잭션 처리를 위한 어노테이션
	public int testCarts(Cart cart) {
		int res = 0;
		
		res = res + cartRepositoryImpl.insertCart(cart); // 정상 로직
		res = res + cartRepositoryImpl.updateCart(); // 비정상 로직
		
		return res;
	}
}

 

 

[ 8. 테스트 다시 실행할 경우 에러가 발생하여 어떤 쿼리도 동작되지 않은 것을 확인 ]

 

INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@6ebc05a6, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@6e6c3152, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@50b494a6, org.springframework.test.context.support.DirtiesContextTestExecutionListener@3cef309d, org.springframework.test.context.transaction.TransactionalTestExecutionListener@32709393, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@3d99d22e, org.springframework.test.context.event.EventPublishingTestExecutionListener@49fc609f]
DEBUG: org.springframework.core.env.StandardEnvironment - Activating profiles []
DEBUG: org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\User\Desktop\tistory_spring\mvc\target\classes\com\spring\mvc\repository\CartRepositoryImpl.class]
DEBUG: org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\User\Desktop\tistory_spring\mvc\target\classes\com\spring\mvc\service\CartServiceImpl.class]
DEBUG: org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 15 bean definitions from URL [file:src/main/webapp/WEB-INF/spring/root-context.xml]
DEBUG: org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@7e2d773b
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.transaction.config.internalTransactionalEventListenerFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.aop.config.internalAutoProxyCreator'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cartRepositoryImpl'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSession'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSessionFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dataSource'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cartServiceImpl'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.transaction.interceptor.TransactionInterceptor#0'
DEBUG: org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@7e2d773b, started on Tue Feb 16 22:19:31 KST 2021

 

 

[ 9. 데이터도 추가적으로 적재되지 않은 것을 확인 ]

 

 

 

 

 

RootContext 설정 파일 변경 (xml → Java)

 

※ 스프링과 관련된 코드는 모두 STS-3.9.12.RELEASE 버전을 기준으로 작성되었습니다.

 

이전 포스팅에서도 xml에서 java파일로 변경을 했었는데 변경했던 상태에 이어서 service구간 적용을 위한 설정을 추가해보겠습니다.

 

[ 1. RootContext클래스에 설정 추가 (com.spring.mvc.config.RootContext) ]

 

package com.spring.mvc.config;

import org.apache.commons.dbcp2.BasicDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration // 설정파일로 등록
@ComponentScan(basePackages = {"com.spring.mvc.repository", "com.spring.mvc.service"}) // repository, service관련 클래스 등록 패키지
@EnableTransactionManagement // 트랜잭션 어노테이션 적용을 위한 설정
public class RootContext {
	
	...
	
	@Bean
	public PlatformTransactionManager transactionManager() { // 트랜잭션 어노테이션 적용을 위한 설정
		DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
		transactionManager.setDataSource(dataSource());
		return transactionManager;
	}
}

 

 

[ 2. 단위 테스트 Java방식으로 설정 변경 ]

 

package com.spring.mvc;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import com.spring.mvc.config.RootContext;
import com.spring.mvc.dto.Cart;
import com.spring.mvc.service.CartService;

@RunWith(SpringRunner.class)
//@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/root-context.xml"}) // xml 방식
@ContextConfiguration(classes = RootContext.class) // java 방식
public class MVC {
	
	private static Logger logger = LoggerFactory.getLogger(MVC.class);
	
	...
	
	@Autowired
	CartService cartServiceImpl;
	
	@Test
	public void transactionTest() {
		Cart cart = new Cart();
		cart.setName("트랜잭션 테스트");
		cart.setPrice(1000);
		cart.setCount(1);
		
		logger.debug(cartServiceImpl.testCarts(cart) + ""); // 트랜잭션 테스트
	}
}

 

 

[ 3. 테스트할 경우 xml방식과 다르게 쿼리가 표현되지만 데이터베이스에는 반영되지 않음 ]

 

INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@551aa95a, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@35d176f7, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@1dfe2924, org.springframework.test.context.support.DirtiesContextTestExecutionListener@6ebc05a6, org.springframework.test.context.transaction.TransactionalTestExecutionListener@6e6c3152, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@50b494a6, org.springframework.test.context.event.EventPublishingTestExecutionListener@3cef309d]
DEBUG: org.springframework.core.env.StandardEnvironment - Activating profiles []
DEBUG: org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@3dfc5fb8
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
DEBUG: org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\User\Desktop\tistory_spring\mvc\target\classes\com\spring\mvc\repository\CartRepositoryImpl.class]
DEBUG: org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\User\Desktop\tistory_spring\mvc\target\classes\com\spring\mvc\service\CartServiceImpl.class]
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.transaction.config.internalTransactionalEventListenerFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.aop.config.internalAutoProxyCreator'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'rootContext'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'transactionAttributeSource'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'transactionInterceptor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'transactionInterceptor' via factory method to bean named 'transactionAttributeSource'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'org.springframework.transaction.config.internalTransactionAdvisor' via factory method to bean named 'transactionAttributeSource'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'org.springframework.transaction.config.internalTransactionAdvisor' via factory method to bean named 'transactionInterceptor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cartRepositoryImpl'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSession'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSessionFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dataSource'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'sqlSession' via factory method to bean named '&sqlSessionFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cartServiceImpl'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'transactionManager'
DEBUG: com.spring.mvc.cartmapper.insertCart - ==>  Preparing: insert into cart(name, price, count) values (?, ?, ?)
DEBUG: com.spring.mvc.cartmapper.insertCart - ==> Parameters: 트랜잭션 테스트(String), 1000(Integer), 1(Integer)
DEBUG: com.spring.mvc.cartmapper.insertCart - <==    Updates: 1
DEBUG: com.spring.mvc.cartmapper.updateCart - ==>  Preparing: update cart set price = '테스트' -- price는 타입이 int라 문자열이 들어갈 수 없음 where name = '김치'
DEBUG: com.spring.mvc.cartmapper.updateCart - ==> Parameters: 
DEBUG: org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 11 bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'DB2'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'Derby'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'H2'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'HDB'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'HSQL'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'Informix'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'MS-SQL'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'MySQL'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'Oracle'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'PostgreSQL'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'Sybase'
DEBUG: org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@3dfc5fb8, started on Tue Feb 16 22:37:08 KST 2021

 

 

 

파일 구성

 

 

 

참조

 

트랜잭션(Transaction)이란?

 

[DataBase] 트랜잭션이란? (Transaction)

 

 

정리

 

트랜잭션은 데이터 처리를 위한 논리적인 작업 단위를 의미
트랜잭션에는 원자성, 일관성, 독립성, 지속성의 특징이 존재
@Service는 트랜잭션 처리를 위한 구간으로 메서드들을 하나의 작업 단위로 묶어서 처리

 

 

 

이상으로 스프링을 이용한 MVC패턴 구현 중 네 번째인 @Service구성에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

728x90
반응형

댓글