[ Transactio 트랜잭션 ]
트랜잭션(transaction) : 논리적 작업 단위 (데이터 처리의 단위)
- commit : 변경 사항을 실제 DB에 저장
- rollback : 변경 사항을 취소, 원상태로 복귀
AOP @Transactional : 선언적 방식의 트랜잭션 관리
참고) 프로그래밍적으로는 connection에서 setAutoCommit(false)를 설정해서,
수동커밋 모드에서 정상수행 시 commit, 문제 발생 시 rollback
ex)
계좌 이체(논리적 작업단위)라는 트랜잭션에서는
출금 후 송금처리가 완료되지 않은 상태에서 문제가 발생할 경우,
→ rollback하여 출금을 취소하고
문제 없이 송금처리가 완료될 경우
→ commit하여 작업을 마무리한다.
* 참고
트랜잭션의 4가지 특성 (ACID)
- 원자성(Atomic) : 트랜잭션은 분해가 불가능한 최소 단위여야 함 (All or Nothing)
- 일관성(Consistency) : 트랜잭션이 성공적으로 완료되면 일관성있는 DB 상태를 보존
- 업무적으로 처리된 데이터(commit)는 변하지 않고 일관성있게 보존되어야 한다. - 격리성(Isolation) : 트랜잭션 수행 시 다른 트랜잭션의 작업이 끼어들지 못하도록 보장
- 지속성(Durability) : 성공적으로 수행된 트랜잭션은 영원히 반영
[ 요구사항 시나리오 ]
회원가입 업무는 회원 정보 등록과 동시에 반드시 포인트 적립이 수행되어야 한다.
두 가지 업무가 모두 정상적으로 처리될 경우에는 commit,
문제 발생 시에는 rollback 처리되도록
Spring 선언적 방식의 트랜잭션으로 처리한다 (annotaion style : @Transaction)
아래의 설정 정보는 아래 포스팅을 참고하세요!
- Maven pom 설정 - pom.xml
- DD (Deploytment Descriptor) 설정 - web.xml
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 둘 다 진행되지 않는다.
'Java Web Programming > 6. Spring | MyBatis' 카테고리의 다른 글
[SpringBoot] 스프링 부트 간단 예제! (0) | 2020.11.17 |
---|---|
[SpringBoot] 스프링부트 환경설정 (2) | 2020.11.17 |
[SpringMVC/MyBatis] 회원 게시판 어플리케이션 구현 (Paging 페이징) (0) | 2020.11.16 |
[SpringMVC/MyBatis] 마이바티스에도 Proxy(프록시)를 적용해보자! (0) | 2020.11.16 |
[SpringMVC/MyBatis] Ajax 적용해보기 (jQuery) + Session 세션 관리 (0) | 2020.11.13 |