개요
안녕하세요. 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>
[ 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
파일 구성
참조
[DataBase] 트랜잭션이란? (Transaction)
정리
이상으로 스프링을 이용한 MVC패턴 구현 중 네 번째인 @Service구성에 대해 간단하게 알아보는 시간이었습니다.
읽어주셔서 감사합니다.
'Spring > Spring' 카테고리의 다른 글
[Spring] 스프링을 이용한 MVC패턴 구현(6) - JSP를 이용한 화면 구성 (0) | 2021.02.22 |
---|---|
[Spring] 스프링을 이용한 MVC패턴 구현(5) - @Controller 구성 (0) | 2021.02.18 |
[Spring] 스프링을 이용한 MVC패턴 구현(3) - MySQL, MyBatis, @Repository 구성 (0) | 2021.02.13 |
[Spring] 스프링을 이용한 MVC패턴 구현(2) - 프로젝트 초기 설정 방법 (0) | 2021.02.11 |
[Spring] 스프링을 이용한 MVC패턴 구현(1) - Spring MVC (0) | 2021.02.09 |
댓글