Spring)Transaction - AOP

메타플랫폼대표
|2024. 8. 6. 15:23
반응형

[ AOP의 정의 ]

 

OOP - 객체지향 프로그래밍

AOP - 관점지향 프로그래밍 : OOP가 가지고 있는 문제들을 보완하는 기능을 가진다. OOP를 돕는 서포팅 기능

 

Transaction은 all or nothing (실패되면 전부가 실패되고, 성공하면 전부 성공한다.)

insert가 TV 100대가 되면 update도 재고 수량 + 입고 수량이 되어야 한다.

 

하지만, update가 실패하면, insert 입고 수량 100대는 분명 들어왔는데, update 수량은 최신 갱신이 되지 않는다.

 

 

 

 

 

aop가 Exception을 감시하고 있다가 pointcut이 했던 작업을 모두 rollback 시킨다.

올바르이 정보가 들어오려면 전부 Exception이 되면 안되기 때문이다.

txManager가 Transaction을 전부 관리하고 캔슬시킨다.

 

 

 

 

 


 

 

 

AOP 분해하기

1. context 1-1 transaction.xml 구성

 

여기서 AOP를 사용할 때 Dao와 Service 객체를 나눠놓는 이유는, Dao는 데이터베이스와의 직접적인 상호작용을 담당

 

Service 객체는 비즈니스 로직을 처리하기 위해서 사용한다. (예를 들면, update와 insert 둘이 동시에 진행이 되어야 하는 Transaction을 사용해야 할 때 Service 객체를 따로 두는 것이다)

 

 

더보기

위의 예시를 적용시킨 코드 Exception 예외를 일부러줘서, 해당 Method를 강제로 취소하게 만든다

	@Override
	public int insert_out(ProductVo vo) throws Exception {
		// TODO Auto-generated method stub
		int res = 0;
		
		// 1. 출고등록하기
		res = product_out_dao.insert(vo);

		// 2. 재고정보 얻어오기
		ProductVo remainVo = product_remain_dao.selectOne(vo.getName());
		// 현재 insert의 정보는 Service에서 추가한것이기에 트랜잭션 구역에 머물고 있는 상태이다.
		if(remainVo==null) {
			// 재고목록에 상품이 없을경우(insert된 Method를 Exception을 줘서 rollback 시킨다)
			throw new Exception();
		}else {
			
		}
		
		return 0;
	}

 

 

 

2. Dao 데이터베이스와 연관되는 자료들을 처리한다.

sqlSession을 setter로 받고 있기 때문에, context3에서 property를 이용해서 setter 생성을 마무리 해야한다.

 

아래는 setter 방식으로 sqlSession을 받고 있다.

 

 

 

 

3. Service 객체를 이용해서 데이터를 Transaction 처리한다. Service 객체를 사용하는 이유

(여러 Dao에서 만들었던 Method를 한군데로 모아서 Total로 처리하기 위해서)

한 줄 정리 : 한 번에 처리하기 위해서

 

 

 

 

 

4. Exception 처리 방법

 

 

 

Exception을 throw하고 catch 하는 과정

 

 

 

 

 

 

 

 

 


 

 

 

 

 

기본적인 세팅을 완료시킨다. (maven 설정하기)

 

 

 

 

package controller;

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

@Controller
public class ProductController {

	
	@RequestMapping("/product/list.do")
	public String list(Model model) {
		
		return "product/product_list";
	}
	
}

 

 

 

 

 

 

 

 

 

 

 

[ Web Spring 초반 실행시 실행구조 ]

 

 

 

 

Exception 예외가 발생 시, 통보 -> 통보 -> 통보 순으로 DS에게 최종적으로 전달해서 rollback

 

 

 

 

exception이 발생하면, MyExcepHndlr를 call 해라

 

 

 

--입고
create sequence seq_product_in_idx

create table product_in
(
   idx   int,			  --일련번호
   name  varchar2(255),   --상품명 
   cnt   int,			  --입고수량	
   regdate date           --입고일자
)

alter table product_in
	add constraint pk_product_in_idx primary key(idx);

--출고
create sequence seq_product_out_idx

create table product_out
(
   idx   int,			  --일련번호
   name  varchar2(255),   --상품명 
   cnt   int,			  --출고수량	
   regdate date           --출고일자
)

alter table product_out
	add constraint pk_product_out_idx primary key(idx);

--재고
create sequence seq_product_remain_idx

create table product_remain
(
   idx   int,			  --일련번호
   name  varchar2(255),   --상품명 
   cnt   int,			  --재고수량	
   regdate date           --재고일자
)


alter table product_remain
	add constraint pk_product_remain_idx primary key(idx);

-- 재고상품명이 유일해야함
alter table product_remain
	add constraint unique_product_remain_name unique(name);

 

 

 

 

vo 작성하기

package vo;

public class ProductVo {

	int idx;
	String name;
	int cnt;
	String regdate;
	
	public int getIdx() {
		return idx;
	}
	public void setIdx(int idx) {
		this.idx = idx;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getCnt() {
		return cnt;
	}
	public void setCnt(int cnt) {
		this.cnt = cnt;
	}
	public String getRegdate() {
		return regdate;
	}
	public void setRegdate(String regdate) {
		this.regdate = regdate;
	}
	
}

 

 

 

3개의 Dao에서 공용으로 사용할 Interface를 작성한다.

package dao;

import java.util.List;

import vo.ProductVo;

public interface ProductDao {

	List<ProductVo> selectList();
	default ProductVo selectOne(String name) { return null; }
	int insert(ProductVo vo);
	int update(ProductVo vo);
	int delete(int idx);
	
}

 

 

 

바꿀때 Ctrl+f 해서 해당 항목을 변경해줄 수 있다.

 

 

 

 

DaoImpl 작성

package dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;

import vo.ProductVo;

public class Product_In_DaoImpl implements ProductDao {

	SqlSession sqlSession;
	
	public void setSqlSession(SqlSession sqlSession) {
		this.sqlSession = sqlSession;
	}

	@Override
	public List<ProductVo> selectList() {
		// TODO Auto-generated method stub
		return sqlSession.selectList("product_in.product_in_list");
	}

	@Override
	public int insert(ProductVo vo) {
		// TODO Auto-generated method stub
		return sqlSession.insert("product_in.product_in_insert",vo);
	}

	@Override
	public int update(ProductVo vo) {
		// TODO Auto-generated method stub
		return sqlSession.update("product_in.product_in_update",vo);
	}

	@Override
	public int delete(int idx) {
		// TODO Auto-generated method stub
		return sqlSession.delete("product_in.product_in_delete",idx);
	}

	@Override
	public ProductVo selectOne(int idx) {
		// TODO Auto-generated method stub
		return sqlSession.selectOne("product_in.product_in_one_idx",idx);
	}

}

 

 

 

 

mybatis 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="product_in">

	<!-- 전체조회 -->
	<select id="product_in_list" resultType="vo.ProductVo">
		select * from product_in order by idx
	</select>
		
	<!-- idx -> 객체1개 조회 -->
	<select id="product_in_one_idx" resultType="product" parameterType="int">
		select * from product_in where idx=#{ idx }
	</select>
	
	<!-- 추가작업 -->
	<insert id="product_in_insert" parameterType="product">
		insert into product_in values(seq_product_in_idx.nextVal,#{ name },#{ cnt },sysdate)
	</insert>
	
	<!-- 수정 -->
	<update id="product_in_update" parameterType="product">
		update product_in 
			set cnt=#{ cnt }, regdate=sysdate 
		where idx=#{ idx }
	</update>
	
	<!-- 삭제 -->
	<delete id="product_in_delete" parameterType="int">
		delete from product_in
			where idx=#{ idx }
	</delete>


</mapper>

 

 

 

context3

 

 

 

context4

 

 

 

ProductServiceImpl

package service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import dao.ProductDao;
import vo.ProductVo;

public class ProductServiceImpl implements ProductService {

	// 입고,출고,재고에 관련된 Interface
	ProductDao product_in_dao; 		// 입고
	ProductDao product_out_dao; 	// 출고
	ProductDao product_remain_dao; 	// 재고

	// Contructor Injection (순서 중요하다)
	public ProductServiceImpl(ProductDao product_in_dao, ProductDao product_out_dao, ProductDao product_remain_dao) {
		super();
		this.product_in_dao = product_in_dao;
		this.product_out_dao = product_out_dao;
		this.product_remain_dao = product_remain_dao;
	}

	@Override
	public Map<String, List<ProductVo>> selectTotalMap() {
		// TODO Auto-generated method stub
		List<ProductVo> in_list = product_in_dao.selectList(); 			// 입고목록
		List<ProductVo> out_list = product_out_dao.selectList(); 		// 출고목록
		List<ProductVo> remain_list = product_remain_dao.selectList();  // 재고목록
		
		Map<String, List<ProductVo>> map = new HashMap<String, List<ProductVo>>();
		map.put("in_list", in_list);
		map.put("out_list", out_list);
		map.put("remain_list", remain_list);
		
		return null;
	}

	@Override
	public int insert_in(ProductVo vo) throws Exception {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public int insert_out(ProductVo vo) throws Exception {
		// TODO Auto-generated method stub
		return 0;
	}

}

 

 

 

 

Controller 생성하기

package controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import service.ProductService;

@Controller
public class ProductController {

	@Autowired
	ProductService product_service;
	
	@RequestMapping("/product/list.do")
	public String list(Model model) {
		
		return "product/product_list";
	}
	
	
	
}

 

 

 

 

 

 

 

 

	@Override
	public int insert_out(ProductVo vo) throws Exception {
		// TODO Auto-generated method stub
		int res = 0;
		
		// 1. 출고등록하기
		res = product_out_dao.insert(vo);

		// 2. 재고정보 얻어오기
		ProductVo remainVo = product_remain_dao.selectOne(vo.getName());
		// 현재 insert의 정보는 Service에서 추가한것이기에 트랜잭션 구역에 머물고 있는 상태이다.
		if(remainVo==null) {
			// 재고목록에 상품이 없을경우(insert된 Method를 Exception을 줘서 rollback 시킨다)
			throw new Exception("remain_not");
		}else {
			// 재고수량 = 원래재고수량 - 출고수량
			int cnt = remainVo.getCnt() - vo.getCnt();
			
			if(cnt < 0) {
				// 재고수량이 부족한 경우
				throw new Exception("remain_lack");
			}
			
			// 재고수량 수정
			remainVo.setCnt(cnt);
			res = product_remain_dao.update(remainVo);
			
		}
		
		return 0;
	}

 

 

 

 

 

 

[ 입고 취소, 출고 취소 ]

 

- 예외 처리를 부모 Method로 떠넘긴뒤, 부모 Method에서 RedirectAttributes를 통해서 JSP에 파라미터로 넘긴뒤, 알람으로 넘겨주는 방법

더보기

Service

package service;

import java.util.List;
import java.util.Map;

import vo.ProductVo;

public interface ProductService {
	
	
	// 입고, 출고, 재고 목록 가져오는데 사용할 것이다.
	// ArrayList가 3개이기에 List가 값으로 담기는 것이다.
	Map<String, List<ProductVo>> selectTotalMap(); // 전체조회
	
	// 입고처리
	int insert_in(ProductVo vo) throws Exception;
	
	// 입고취소
	int delete_in(int idx) throws Exception;
	
	// 출고처리
	int insert_out(ProductVo vo) throws Exception;
	
	// 출고취소
	int delete_out(int idx) throws Exception;
	
	

}

 

 

ServiceImpl

package service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import dao.ProductDao;
import vo.ProductVo;

public class ProductServiceImpl implements ProductService {

	// 입고,출고,재고에 관련된 Interface
	ProductDao product_in_dao; 		// 입고
	ProductDao product_out_dao; 	// 출고
	ProductDao product_remain_dao; 	// 재고

	// Contructor Injection (순서 중요하다)
	public ProductServiceImpl(ProductDao product_in_dao, ProductDao product_out_dao, ProductDao product_remain_dao) {
		super();
		this.product_in_dao = product_in_dao;
		this.product_out_dao = product_out_dao;
		this.product_remain_dao = product_remain_dao;
	}

	@Override
	public Map<String, List<ProductVo>> selectTotalMap() {
		// TODO Auto-generated method stub
		List<ProductVo> in_list = product_in_dao.selectList(); 			// 입고목록
		List<ProductVo> out_list = product_out_dao.selectList(); 		// 출고목록
		List<ProductVo> remain_list = product_remain_dao.selectList();  // 재고목록
		
		Map<String, List<ProductVo>> map = new HashMap<String, List<ProductVo>>();
		map.put("in_list", in_list);
		map.put("out_list", out_list);
		map.put("remain_list", remain_list);
		
		return map;
	}

	@Override
	public int insert_in(ProductVo vo) throws Exception {
		// TODO Auto-generated method stub
		int res = 0;
		
		// 1. 입고등록하기
		res = product_in_dao.insert(vo);
		
		// 2. 재고등록(수정처리)
		ProductVo remainVo = product_remain_dao.selectOne(vo.getName());
		
		if(remainVo==null) {
			// 등록상품이 없으니 등록추가
			res = product_remain_dao.insert(vo);
		}else {
			// 상품기등록상태 : 수량수정
			// 재고수량 = 기존재고수량 + 추가수량
			int cnt = remainVo.getCnt() + vo.getCnt();
			remainVo.setCnt(cnt);
			
			res = product_remain_dao.update(remainVo);
		}
		
		return res;
	}

	@Override
	public int insert_out(ProductVo vo) throws Exception {
		// TODO Auto-generated method stub
		int res = 0;
		
		// 1. 출고등록하기
		res = product_out_dao.insert(vo);

		// 2. 재고정보 얻어오기
		ProductVo remainVo = product_remain_dao.selectOne(vo.getName());
		// 현재 insert의 정보는 Service에서 추가한것이기에 트랜잭션 구역에 머물고 있는 상태이다.
		if(remainVo==null) {
			// 재고목록에 상품이 없을경우(insert된 Method를 Exception을 줘서 rollback 시킨다)
			throw new Exception("remain_not");
		}else {
			// 재고수량 = 원래재고수량 - 출고수량
			int cnt = remainVo.getCnt() - vo.getCnt();
			
			if(cnt < 0) {
				// 재고수량이 부족한 경우
				throw new Exception("remain_lack");
			}
			
			// 재고수량 수정
			remainVo.setCnt(cnt);
			res = product_remain_dao.update(remainVo);
			
		}
		
		return res;
	}

	@Override
	public int delete_in(int idx) throws Exception {
		// TODO Auto-generated method stub
		// 0. 취소할 입고상품 정보 얻어오기
		ProductVo vo = product_in_dao.selectOne(idx);
		
		// 1. 입고상품삭제
		int res = 0;
		
		res = product_in_dao.delete(idx);
		
		// 2. 재고상품수정
		ProductVo remainVo = product_remain_dao.selectOne(vo.getName());
		
		if(remainVo==null) {
			throw new Exception("remain_not");
		} else {
			int cnt = remainVo.getCnt() - vo.getCnt();
			
			if(cnt < 0) {
				throw new Exception("remain_lack");
			}
			
			// 재고수량 설정
			remainVo.setCnt(cnt);
			res = product_remain_dao.update(remainVo);
		}
		
		return res;
	}

	@Override
	public int delete_out(int idx) throws Exception {
		// TODO Auto-generated method stub
		// 0. 취소할 출고상품 정보 얻어오기
		ProductVo vo = product_out_dao.selectOne(idx);
		
		// 1. 출고상품삭제
		int res = 0;
		
		res = product_out_dao.delete(idx);
		
		// 2. 재고상품수정
		ProductVo remainVo = product_remain_dao.selectOne(vo.getName());
		
		if(remainVo==null) {
			throw new Exception("remain_not");
		} else {
			int cnt = remainVo.getCnt() + vo.getCnt();
			
			if(cnt < 0) {
				throw new Exception("remain_lack");
			}
			
			// 재고수량 설정
			remainVo.setCnt(cnt);
			res = product_remain_dao.update(remainVo);
		}
		
		
		
		return res;
	}
	
	
	
	

}

 

 

controller

package controller;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import service.ProductService;
import vo.ProductVo;

@Controller
public class ProductController {

	@Autowired
	ProductService product_service;
	
	@RequestMapping("/product/list.do")
	public String list(Model model) {
		
		Map map = product_service.selectTotalMap();
		
		model.addAttribute("map",map);
		
		return "product/product_list";
	}
	
	
	// 입고처리
	// /product/insert_in.do?name=TV&cnt=100
	@RequestMapping("/product/insert_in.do")
	public String insert_in(ProductVo vo) {
		
		try {
			product_service.insert_in(vo);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			// e.printStackTrace();
		}
		
		return "redirect:list.do";
	}
	
	
	// 출고처리
	// /product/insert_out.do?name=TV&cnt=100
	@RequestMapping("/product/insert_out.do")
	public String insert_out(ProductVo vo,RedirectAttributes ra) {
		
		try {
			product_service.insert_out(vo);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			// e.printStackTrace();
			String message = e.getMessage();
			ra.addAttribute("error",message);
		}
		
		return "redirect:list.do";
	}
	
	
	// 입고취소
	// /product/delete_in.do?idx=5
	@RequestMapping("/product/delete_in.do")
	public String delete_in(int idx,RedirectAttributes ra) {
		
		try {
			product_service.delete_in(idx);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			// e.printStackTrace();
			String message = e.getMessage();
			ra.addAttribute("error",message);
		}
		
		return "redirect:list.do";
	}
	
	
	
	// 출고취소
	// /product/delete_out.do?idx=5
	@RequestMapping("/product/delete_out.do")
	public String delete_out(int idx,RedirectAttributes ra) {
		
		try {
			product_service.delete_out(idx);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			// e.printStackTrace();
			String message = e.getMessage();
			ra.addAttribute("error",message);
		}
		
		return "redirect:list.do";
	}
	
	
	
}



 

 

 

 

 

 

 

 

 

반응형