본문 바로가기
Java Web Programming/6. Spring | MyBatis

[SpringMVC/MyBatis] Transaction(트랜잭션) 적용 (회원관리)

by 파프리카_ 2020. 11. 17.
728x90
반응형

[ Transactio 트랜잭션 ]

트랜잭션(transaction) : 논리적 작업 단위 (데이터 처리의 단위)

 

- commit : 변경 사항을 실제 DB에 저장

- rollback : 변경 사항을 취소, 원상태로 복귀

 

 

AOP @Transactional  : 선언적 방식의 트랜잭션 관리

참고) 프로그래밍적으로는 connection에서 setAutoCommit(false)를 설정해서,

수동커밋 모드에서 정상수행 시 commit, 문제 발생 시 rollback

 

ex)

계좌 이체(논리적 작업단위)라는 트랜잭션에서는

출금 후 송금처리가 완료되지 않은 상태에서 문제가 발생할 경우,

→ rollback하여 출금을 취소하고

문제 없이 송금처리가 완료될 경우

→ commit하여 작업을 마무리한다.


* 참고

트랜잭션의 4가지 특성 (ACID)

  1. 원자성(Atomic) : 트랜잭션은 분해가 불가능한 최소 단위여야 함 (All or Nothing)
  2. 일관성(Consistency) : 트랜잭션이 성공적으로 완료되면 일관성있는 DB 상태를 보존
    - 업무적으로 처리된 데이터(commit)는 변하지 않고 일관성있게 보존되어야 한다.
  3. 격리성(Isolation) : 트랜잭션 수행 시 다른 트랜잭션의 작업이 끼어들지 못하도록 보장
  4. 지속성(Durability) : 성공적으로 수행된 트랜잭션은 영원히 반영

 


 [ 요구사항 시나리오 ] 

 

회원가입 업무는 회원 정보 등록과 동시에 반드시 포인트 적립이 수행되어야 한다.

두 가지 업무가 모두 정상적으로 처리될 경우에는 commit,

문제 발생 시에는 rollback 처리되도록 

Spring 선언적 방식의 트랜잭션으로 처리한다 (annotaion style : @Transaction)


아래의 설정 정보는 아래 포스팅을 참고하세요!

  • Maven pom 설정 - pom.xml
  • DD (Deploytment Descriptor) 설정 - web.xml

> SpringMVC 환경설정 Template

 

[SpringMVC/MyBatis] SpringMVC Template Project 환경설정

[ 환경 설정 ] 1. Spring Legacy Project 프로젝트 생성 > 'Spring MVC Project' 선택 > 프로젝트 명 작성 (작성한 프로젝트 이름이 실제 브라우저상 보이는 서버프로그램 url 이 된다 *주로 프로젝트 명을 쓴다..

creamilk88.tistory.com


Spring configure 설정 

 

 > ①선언적 방식의 트랜잭션 설정, ②어노테이션 기반 트랜잭션 설정 추가

<!-- 선언적 방식의 트랜잭션 설정 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"/> 
</bean>

<!-- 어노테이션 기반 트랜잭션 설정 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

 

- Namespaces에서 'beans', 'context', 'mybatis-spring', 'tx' 체크

/WEB_INF/spring-model.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:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
	xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
		http://www.springframework.org/schema/beans http://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">
	
	<!-- 1. DBCP(ver.2) 설정 -->
	<bean id="dbcp" class="org.apache.commons.dbcp2.BasicDataSource">
		<property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
		<property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:xe"/>
		<property name="username" value="scott"/>
		<property name="password" value="tiger"/>
	</bean>
	
	<!--2. SqlSessionFactory 설정 -->
	<!-- @Mapper 어노테이션을 이용해 Proxy(대리인) 객체 (DAOImpl)를 생성하게 하는 설정 -->
	<mybatis-spring:scan base-package="org.kosta.myproject.model.mapper"/>
	<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- DBCP(datebase connection pool) 주입 -->
		<property name="dataSource" ref="dbcp"/>
		
		<!-- Mapper 어노테이션을 적용하여, 자동으로 DAO 구현체를 생성할 때는
			 아래 설정은 필요없다. 
		<property name="mapperLocations" value="classpath:/mybatis/config/*.xml"/>-->
		
		<!-- Package에 별칭주기 : vo까지 잡아주어 상세히 한다.-->
		<property name="typeAliasesPackage" value="org.kosta.myproject.model.vo"/>
		<!-- underScore 표기법을 Camel 표기법으로 mapping(변환)해주는 설정 -->
		<property name="configuration">
			<bean class="org.apache.ibatis.session.Configuration">
				<property name="mapUnderscoreToCamelCase" value="true"></property>
			</bean>
		</property>
	</bean>
 
	<!--3. SqlSessionTemplate설정 : 트랜잭션 제어를 지원-->
	<bean id="SqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg ref="sqlSessionFactoryBean"/>
	</bean>
	
	<!--4. IOC 설정 : <context:component-scan> :  IOC, DI, DL에 대한 설정-->
	<context:component-scan base-package="org.kosta"></context:component-scan>

</beans>

 

/WEB_INF/spring-web.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:mvc="http://www.springframework.org/schema/mvc"
	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 http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!--5. SpringMVC 설정-->
	<mvc:annotation-driven/>

	<!--6. ViewResolver 설정 : client에게 응답하는 view에 대한 설정 -->
	<bean id="ViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/views/"/>
		<property name="suffix" value=".jsp"/>
	</bean>
	
</beans>

/src/main/webapp/WEB-INF/views

 

View

 

/home.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>spring transaction</title>
</head>
<body>
Spring Transaction Test <br><br>
<form action="findMemberById.do">
아이디 <input type="text" name="id">
<input type="submit" value="회원검색">
</form> <hr>
<form action="findPointById.do">
아이디 <input type="text" name="id">
<input type="submit" value="포인트검색">
</form>
<hr><br><br>
<form method="post" action="register.do">
아이디 <input type="text" name="id" required="required"><br>
패스워드 <input type="text" name="password" required="required"><br>
이름 <input type="text" name="name" required="required"><br>
주소 <input type="text" name="address" required="required"><br>
포인트 <input type="number" name="point" required="required"><br>
포인트타입<input type="text" name="pointType" required="required"><br>
<input  type="submit" value="회원가입">
</form>
</body>
</html>

 

/find_result.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>검색결과</title>
</head>
<body>
검색결과 ${resultVO}
</body>
</html>

 

/register_result.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>가입완료</title>
</head>
<body>
가입완료
</body>
</html>

MyBatis Proxy 설정 !

 

/src/main/resource/org.kosta.myproject.model.mapper

 

/MemberMapper.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="org.kosta.springmvc12.model.mapper.MemberMapper"> 
	<select id="findMemberById" resultType="memberVO">
		select id,password,name,address from member_tx
		where id=#{value}
	</select>
	<insert id="register" parameterType="memberVO">
		insert into member_tx(id,password,name,address)
		values (#{id},#{password},#{name},#{address})
	</insert>
 </mapper> 		

 

/PointMapper.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="org.kosta.springmvc12.model.mapper.PointMapper"> 
	<select id="findPointById" resultType="pointVO">
		select id,point,point_type as pointType from point_tx
		where id=#{value}
	</select>
	<insert id="register" parameterType="pointVO">
		insert into point_tx(id,point,point_type)
		values(#{id},#{point},#{pointType})
	</insert>
 </mapper> 		

src/main/java/org.kosta.myproject.model.mapper

 

- 어노테이션 @Mapper를 명시하면,

스프링 컨테이너에 의해 Proxy구현체 (DAOImpl의 역할)가 생성된다.

 

/MemberMapper.java<<interface>>

package org.kosta.springmvc12.model.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.kosta.springmvc12.model.vo.MemberVO;
@Mapper
public interface MemberMapper {
	public MemberVO findMemberById(String id);
	void register(MemberVO memberVO);
}

 

/PointMapper.java<<interface>>

package org.kosta.springmvc12.model.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.kosta.springmvc12.model.vo.PointVO;
@Mapper
public interface PointMapper {
	public PointVO findPointById(String id);
	void register(PointVO pointVO);
}

SQL

 

1. member_tx 테이블 

create table member_tx(
	id varchar2(100) primary key,
	password varchar2(100) not null,
	name varchar2(100) not null,
	address varchar2(100) not null
)

insert into member_tx(id,password,name,address) values('java','1234','아이유','판교');

 

2. point_tx 테이블

create table point_tx(
	id varchar2(100) primary key,
	point number default 0,
	point_type varchar2(100) not null,
	constraint fk_tx_id foreign key(id) references member_tx(id)
)

insert into point_tx(id,point,point_type) values('java',1000,'cgv');


Model

 

/src/main/java/org.kosta.myproject.model.vo

 

/MemberVO.java

package org.kosta.springmvc12.model.vo;

public class MemberVO {
	private String id;
	private String password;
	private String name;
	private String address;
	public MemberVO() {
		super();
		// TODO Auto-generated constructor stub
	}
	public MemberVO(String id, String password, String name, String address) {
		super();
		this.id = id;
		this.password = password;
		this.name = name;
		this.address = address;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	@Override
	public String toString() {
		return "MemberVO [id=" + id + ", password=" + password + ", name=" + name + ", address=" + address + "]";
	}
	
}

 

/PointVO.java

package org.kosta.springmvc12.model.vo;

public class PointVO {
	private String id;
	private int point;
	private String pointType;
	public PointVO() {
		super();
		// TODO Auto-generated constructor stub
	}
	public PointVO(String id, int point, String pointType) {
		super();
		this.id = id;
		this.point = point;
		this.pointType = pointType;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public int getPoint() {
		return point;
	}
	public void setPoint(int point) {
		this.point = point;
	}
	public String getPointType() {
		return pointType;
	}
	public void setPointType(String pointType) {
		this.pointType = pointType;
	}
	@Override
	public String toString() {
		return "PointVO [id=" + id + ", point=" + point + ", pointType=" + pointType + "]";
	}
	
}

/src/main/java/org.kosta.myproject.model.service

 

 

/MemberService.java <<interface>>

package org.kosta.springmvc12.model.service;

import org.kosta.springmvc12.model.vo.MemberVO;
import org.kosta.springmvc12.model.vo.PointVO;

public interface MemberService {
	public MemberVO findMemberById(String id);

	public PointVO findPointById(String id);

	public void register(MemberVO memberVO, PointVO pointVO);
}

 

@Transactional : 오류가 하나라도 발생하면, 두 작업 모두 수행하지 않고, rollback 처리한다.

만약 PointMapper.xml에서 에러가 발생한다면, 
memberMapper.register()는 작동되고, 
pointMapper.register()는 작동이 되지 않을 것이다.
회원테이블에만 insert되고, 포인트 테이블에 insert가 안되면 안되므로 @Transactional 처리를 해준다! 

 

/MemberServiceImpl.java

package org.kosta.springmvc12.model.service;

import javax.annotation.Resource;

import org.kosta.springmvc12.model.mapper.MemberMapper;
import org.kosta.springmvc12.model.mapper.PointMapper;
import org.kosta.springmvc12.model.vo.MemberVO;
import org.kosta.springmvc12.model.vo.PointVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MemberServiceImpl implements MemberService{
	@Resource
	private MemberMapper memberMapper;
	@Resource
	private PointMapper pointMapper;
	
	@Override
	public MemberVO findMemberById(String id){
		return memberMapper.findMemberById(id);
	}
	@Override
	public PointVO findPointById(String id){
		return pointMapper.findPointById(id);
	}
	/**
		트랜잭션(transaction) : 논리적 작업 단위 (데이터 처리의 단위)
		- commit : 변경 사항을 실제 DB에 저장
		- rollback : 변경 사항을 취소, 원상태로 복귀
	
		ex)
		계좌 이체(논리적 작업단위)라는 트랜잭션에서는
		출금 후 송금처리가 완료되지 않은 상태에서 문제가 발생할 경우,
		→ rollback하여 출금을 취소하고
		문제 없이 송금처리가 완료될 경우
		→ commit하여 작업을 마무리한다.
		
		회원가입 업무는 회원 정보 등록과 동시에 반드시 포인트 적립이 수행되어야 한다.
		두 가지 업무가 모두 정상적으로 처리될 경우에는 commit,
		문제 발생 시에는 rollback 처리되도록 
		Spring 선언적 방식의 트랜잭션으로 처리한다.
		(annotaion style : @Transaction)

		AOP @Transactional  : 선언적 방식의 트랜잭션 관리
		참고) 프로그래밍적으로는 connection에서 setAutoCommit(false)를 설정해서,
			  수동커밋 모드에서 정상수행 시 commit, 문제 발생 시 rollback
	
	 */
	@Transactional
	@Override
	public void register(MemberVO memberVO, PointVO pointVO) {
		// 오류가 하나라도 발생하면, 두 작업 모두 수행하지 않고,
		// rollback 처리한다.
		// 만약 PointMapper.xml에서 에러가 발생한다면, 
		// memberMapper.register()는 작동되고, 
		// pointMapper.register()는 작동이 되지 않을 것이다.
		// 회원테이블에만 insert되고, 포인트 테이블에 insert가 안되면 안되므로
		// @Transactional 처리를 해준다! 
		memberMapper.register(memberVO);
		System.out.println("회원 테이블에 insert");
		pointMapper.register(pointVO);
		System.out.println("포인트 테이블에 insert");
	}
}

Controller

 

/src/main/java/org.kosta.myproject.controller

 

/HomeController.java

package org.kosta.springmvc12.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {
	@RequestMapping("home.do")
	public String home() {
		return "home";
	}
}

 

/MemberController.java

package org.kosta.springmvc12.controller;

import javax.annotation.Resource;

import org.kosta.springmvc12.model.service.MemberService;
import org.kosta.springmvc12.model.vo.MemberVO;
import org.kosta.springmvc12.model.vo.PointVO;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class MemberController {
	@Resource
	private MemberService memberService;
	@RequestMapping("findMemberById.do")
	public String findMemberById(String id,Model model) {
		model.addAttribute("resultVO", memberService.findMemberById(id));
		return "find_result";
	}
	@RequestMapping("findPointById.do")
	public String findPointById(String id,Model model) {
		model.addAttribute("resultVO", memberService.findPointById(id));
		return "find_result";
	}
	@RequestMapping(method=RequestMethod.POST,value="register.do")
	public String register(MemberVO memberVO,PointVO pointVO) {		
		memberService.register(memberVO, pointVO);
		return "redirect:registerResult.do";
	}
	@RequestMapping("registerResult.do")
	public String registerResult() {
		return "register_result";
	}
}

Test - 단위 테스트

 

/src/test/java/org.kosta.myproject

 

/BoardJUnitTest.java


[ Browser 결과 화면 ] 

 

1. PointMapper.xml에 오류가 있고,  MemberServiceImpl에서 @Transactional 처리를 해주지 않을 경우

회원 가입은 진행되고, 포인트 테이블에 insert는 진행되지 않는다.

 

2.  PointMapper.xml에 오류가 있고,  MemberServiceImpl에서 @Transactional 처리를 해줄 경우,

회원 가입과 포인트 테이블에 insert 둘 다 진행되지 않는다.

 

 

728x90
반응형