국비교육(22-23)

57일차(2)/Spring(21) : Transaction, DataAccessException 활용 예제

서리/Seori 2022. 12. 28. 00:14

57일차(2)/Spring(21) : Transaction, DataAccessException 활용 예제

 

 

- table.sql에 새 테이블 생성한 내용추가

 

-- 상품테이블
CREATE TABLE shop(
   num NUMBER PRIMARY KEY, --상품번호
   name VARCHAR2(30), --상품이름
   price NUMBER, --상품가격
   remainCount NUMBER CHECK(remainCount >= 0) --재고갯수 
);

-- 고객 계좌 테이블
CREATE TABLE client_account(
   id VARCHAR2(30) PRIMARY KEY, -- 고객의 아이디
   money NUMBER CHECK(money >= 0), -- 고객의 잔고 
   point NUMBER
);

-- 주문 테이블
CREATE TABLE client_order(
   num NUMBER PRIMARY KEY, -- 주문번호
   id VARCHAR2(30), -- 주문 고객의 아이디
   code NUMBER, -- 주문한 상품의 번호 
   addr VARCHAR2(50) -- 배송 주소
);

-- 주문 테이블에 사용할 시퀀스 
CREATE SEQUENCE client_order_seq;


-- sample 데이터
INSERT INTO shop (num,name,price,remainCount)
VALUES(1, '사과', 1000, 5);

INSERT INTO shop (num,name,price,remainCount)
VALUES(2, '바나나', 2000, 5);

INSERT INTO shop (num,name,price,remainCount)
VALUES(3, '귤', 3000, 5);

 

- transaction 예제

 

- 하나의 비즈니스 로직을 처리하기 위해서 여러 개의 변경사항이 일어날 수 있다.

- 상품구입, 상품재고관리, 계좌 연동, 배송 처리 등

 

- 쇼핑몰이라고 하면, 하나의 구매만으로도 여러개의 테이블에 변경사항이 일어날 수 있다.

 

- 이러한 과정 속에서 exception이 일어날 수 있다.

- 이런 경우, 작업하다가 예외가 발생하면 이전에 작업했던 것들을 모두 rollback 해야 한다.

- DB를 관리할 때 이런 transaction을 관리할 일이 많다.

 

- transaction 관리 자체는 어렵지 않다. 단 테스트하는 환경을 만드는것이 번거롭다.

 

- quantum DB에서 테이블을 이렇게 만들어두고, 샘플 데이터도 넣어주었다.

 

- com.sy.spring04.shop.dto / dao / service / controller 4개의 패키지 생성

 

ShopDto

package com.sy.spring04.shop.dto;

public class ShopDto {
	private int num; //상품번호
	private String name; //상품명
	private int price; //가격
	private int remainCount; //재고개수
	private String id; //주문자 아이디
	
	public ShopDto() {}

	public ShopDto(int num, String name, int price, int remainCount, String id) {
		super();
		this.num = num;
		this.name = name;
		this.price = price;
		this.remainCount = remainCount;
		this.id = id;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		this.price = price;
	}

	public int getRemainCount() {
		return remainCount;
	}

	public void setRemainCount(int remainCount) {
		this.remainCount = remainCount;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}	
	
}

 

OrderDto

package com.sy.spring04.shop.dto;

public class OrderDto {
	private int num; //주문번호
	private String id; //주묹자의 아이디	
	private int code; //상품번호
	private String addr; //배송 주소
	   
	public OrderDto() {}
	   
	public OrderDto(int num, String id, int code, String addr) {
	      super();
	      this.num = num;
	      this.id = id;
	      this.code = code;
	      this.addr = addr;
	   }

	   public int getNum() {
	      return num;
	   }

	   public void setNum(int num) {
	      this.num = num;
	   }

	   public String getId() {
	      return id;
	   }

	   public void setId(String id) {
	      this.id = id;
	   }

	   public int getCode() {
	      return code;
	   }

	   public void setCode(int code) {
	      this.code = code;
	   }

	   public String getAddr() {
	      return addr;
	   }

	   public void setAddr(String addr) {
	      this.addr = addr;
	   }
}

 

ShopDao 인터페이스

package com.sy.spring04.shop.dao;

import java.util.List;

import com.sy.spring04.shop.dto.ShopDto;

public interface ShopDao {
	//상품목록을 리턴해주는 메소드
	public List<ShopDto> getList();
	//상품 재고를 감소시키는 메소드
	public void minusCount(int num);	
	//잔고 감소 시키는 메소드 
	public void minusMoney(ShopDto dto);
	//포인트를 증가 시키는 메소드
	public void plusPoint(ShopDto dto);
	//상품의 가격을 리턴해주는 메소드
	public int getPrice(int num);
	
}

 

OrderDao 인터페이스

package com.sy.spring04.shop.dao;

import com.sy.spring04.shop.dto.OrderDto;

public interface OrderDao {
	//배송정보를 추가하는 메소드
	public void addOrder(OrderDto dto);
}

 

ShopDaoImpl

package com.sy.spring04.shop.dao;

import java.util.List;

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

import com.sy.spring04.shop.dto.ShopDto;

@Repository
public class ShopDaoImpl implements ShopDao {

	@Autowired
	private SqlSession session;
	
	@Override
	public List<ShopDto> getList() {
	   
	   return session.selectList("shop.getList");
	}
	//재고의 갯수를 1 줄이기 
	@Override
	public void minusCount(int num) {
	   session.update("shop.minusCount", num);
	}
	//계좌 잔고 줄이기 
	@Override
	public void minusMoney(ShopDto dto) {
	   session.update("shop.minusMoney", dto);
	}
	//상품 구입 가격의 10% 를 포인트로 적립하는 메소드 
	@Override
	public void plusPoint(ShopDto dto) {
	   session.update("shop.plusPoint", dto);
	}
	//상품 번호에 해당하는 상품의 가격을 리턴하는 메소드 
	@Override
	public int getPrice(int num) {
	   // TODO Auto-generated method stub
	   return session.selectOne("shop.getPrice", num);
	}

}

- @Repository 와 @Autowired session 작성

 

 

OrderDaoImpl 클래스

package com.sy.spring04.shop.dao;

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

import com.sy.spring04.shop.dto.OrderDto;

@Repository
public class OrderDaoImpl implements OrderDao {

	@Autowired
	private SqlSession session;
	
	//상품 주문 정보를 저장하는 메소드 
	@Override
	public void addOrder(OrderDto dto) {
		session.insert("shop.addOrder", dto);		
	}
}

 

configuration.xml에 Dto정보 추가

 

ShopMapper.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="shop">
	<select id="getList" resultType="shopDto">
      SELECT num,name,price,remainCount
      FROM shop
      ORDER BY num ASC
   </select>
   <update id="minusCount" parameterType="int">
      UPDATE shop
      SET remainCount=remainCount-1
      WHERE num=#{num}
   </update>
   <update id="minusMoney" parameterType="shopDto">
      UPDATE client_account
      SET money=money-#{price}
      WHERE id=#{id}
   </update>
   <update id="plusPoint" parameterType="shopDto">
      UPDATE client_account
      SET point=point + #{price}*0.1
      WHERE id=#{id}
   </update>
   <select id="getPrice" parameterType="int" resultType="int">
      SELECT price
      FROM shop
      WHERE num=#{num}
   </select>
   <insert id="addOrder" parameterType="orderDto">
      INSERT INTO client_order
      (num, id, code, addr)
      VALUES(client_order_seq.NEXTVAL, #{id}, #{code}, #{addr})
   </insert>
</mapper>

 

- plusPoint : 산 가격의 10%를 포인트로 부여하는 것

- addOrder : 주문내역에 data를 추가하는 것

 

 

- 하나의 구입이 일어나면 아래의 과정이 일어난다고 하자.

1) 재고개수 줄이기,

2) 고객 계좌의 금액 감소,

3) 포인트 증가하기,

4) 주문정보 추가 (orderDao)

 


 

ShopService 인터페이스

package com.sy.spring04.shop.service;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.servlet.ModelAndView;

public interface ShopService {
	//상품 목록을 ModelAndView 객체에 담아주는 메소드
	public void getList(ModelAndView mView);
	//상품 주문 처리를 하는 메소드
	public void buy(HttpServletRequest request, ModelAndView mView);
}

 

ShopServiceImpl 클래스

package com.sy.spring04.shop.service;

import java.util.List;

import javax.servlet.http.HttpServletRequest;

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

import com.sy.spring04.exception.DeliveryException;
import com.sy.spring04.shop.dao.OrderDao;
import com.sy.spring04.shop.dao.ShopDao;
import com.sy.spring04.shop.dto.OrderDto;
import com.sy.spring04.shop.dto.ShopDto;

@Service
public class ShopServiceImpl implements ShopService {

	@Autowired private ShopDao shopDao;
	@Autowired private OrderDao orderDao;
	
	@Override
	public void getList(ModelAndView mView) {
		//상품 목록
		List<ShopDto> list=shopDao.getList();
		//ModelAndView객체에 list라는 키 값으로 담는다.
		mView.addObject("list", list);		
	}
	/*
	 * - Spring 트랜잭션 설정 방법
	 * 1. pom.xml에 spring-tx dependency 추가
	 * 2. servlet-context.xml에 transaction 설정 추가
	 * 3. 트랜잭션을 관리할 서비스의 메소드에 @Transactional 어노테이션 붙이기
	 * 
	 * - 프로그래머의 의도 하에서 트랜잭션에 영향을 주는 Exception을 발생시키는 방법
	 * 
	 * DataAccessException 클래스를 상속받은 클래스를 정의하고
	 * 예) class MyException extends DataAccessException{ }
	 * 
	 * 		throw new MyException("예외 메시지");
	 * 
	 * 예외를 발생시킬 조건이라면 위와 같이 예외를 발생시켜서
	 * 트랜잭션이 관리되도록 한다.
	 */
	@Transactional
	@Override
	public void buy(HttpServletRequest request, ModelAndView mView) {
		//구입자의 아이디
		String id=(String)request.getSession().getAttribute("id");
		//1. parameter로 전달되는 구입할 상품 번호
		int num=Integer.parseInt(request.getParameter("num"));
		//2. 상품의 가격을 얻어온다.
		int price=shopDao.getPrice(num);
		//3. 상품의 가격만큼 계좌 잔액을 줄인다.
		ShopDto dto=new ShopDto();
		dto.setId(id);
		dto.setPrice(price);
		shopDao.minusMoney(dto);
		//4. 가격의 10%를 포인트로 적립한다.
		shopDao.plusPoint(dto);
		//5. 재고의 개수를 1 줄인다.
		shopDao.minusCount(num);
		//6. 주문 테이블(배송)에 정보를 추가한다.
		OrderDto dto2=new OrderDto();
		dto2.setId(id); //누가
		dto2.setCode(num); //어떤 상품을
		//클라이언트가 입력한 배송 주소라고 가정
		String addr="제주도 역삼동";
		
		//가상의 테스트
		if(addr.contains("제주도")) {
			throw new DeliveryException("제주도는 배송 불가 지역입니다.");
		}
		
	    dto2.setAddr(addr);//어디로 배송할지
	    orderDao.addOrder(dto2);			
	}

}

 

- 이 service 클래스는 2개의 dao에 의존한다.

 

 

home.jsp 링크하나 추가

 

컨트롤러 추가

ShopController

package com.sy.spring04.shop.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.sy.spring04.shop.service.ShopService;

@Controller
public class ShopController {

	@Autowired private ShopService service;
	
	@RequestMapping("/shop/list")
	public ModelAndView list(ModelAndView mView) {
		service.getList(mView);
		mView.setViewName("shop/list");
		return mView;
	}
	
	@RequestMapping("/shop/buy")
	public ModelAndView buy(HttpServletRequest request, ModelAndView mView) {
		service.buy(request, mView);
		mView.setViewName("shop/buy");
		return mView;
	}
}

 

shop/list.jsp 뷰페이지

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/shop/list.jsp</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
</head>
<body>
<div class="container">
   <c:choose>
      <c:when test="${empty id }">
         <p>
            <a href="${pageContext.request.contextPath }/users/loginform">로그인</a>
            <a href="${pageContext.request.contextPath }/users/signup_form">회원가입</a>
         </p>
      </c:when>
      <c:otherwise>
         <p>
            <strong>${id }</strong> 님 로그인중...
         </p>
      </c:otherwise>
   </c:choose>
   <h1>상품 목록 입니다.</h1>
   <div class="row">
      <c:forEach var="tmp" items="${list }">
         <div class="col">
            <div class="card">
               <img class="card-img-top" src="${pageContext.request.contextPath }/resources/upload/apple.jpg"/>
               <div class="card-body">
                  <h3 class="card-title">${tmp.name }</h3>
                  <p class="card-text">
                     가격 : <strong>${tmp.price }</strong>원 <br/>
                     재고 : <strong>${tmp.remainCount }</strong>개
                  </p>
                  <a href="buy?num=${tmp.num }" class="card-link">구입하기</a>
               </div>
            </div>
         </div>      
      </c:forEach>
   </div>
</div>
</body>
</html>

 

- /resources/upload 폴더에 샘플 image 넣어주기

 

 

- 현재까지 만든 구매 페이지의 모습

 


 

- transaction을 관리하면서 구입을 해보기!

 

servlet-context.xml 설정 필요!

 

- 이 xml의 문서 구조는 상단에 정의된 것만 나온다.(지금은 aop,tx를 추가한 상태)

- 추가를 하고싶으면 하단의 Namespaces 탭을 클릭해보면 된다.

 

- Namespaces 탭에서 aop, tx를 체크해서 추가해주면

 다시 Source로 돌아와서 보면 문서가 확장되어 있다!

 

- 이 내용을 추가해주기

- 트랜잭션을 사용하려면 저 DataSourceTransactionManager가 있어야 한다.

 

- 위에 보면 dataSource 가 있는데, 그것이 저 ref 자리로 들어온다.

 

- 하단의 aop설정도 추가해주기!

 

- 이제 어노테이션 @transactional 만 붙이면 트랜잭션으로 사용할 수 있다.

- 트랜잭션이 관리되면서 구입이 이루어지도록 처리할 예정!

 


 

- id를 사용해서 클라이언트 계좌에 돈을 집어넣는다.

 

- 계좌테이블에 아래 샘플데이터 추가

-- sample 계좌 데이터
INSERT INTO client_account
(id, money, point)
VALUES('banana',10000, 0);

 

* 처리해야 할 업무

- 사과 1개 구입하기 를 누르면?

→ 잔고에서 1000원 줄이기

 포인트 100원 쌓기

 재고 1 감소

 배송테이블에 배송정보 추가

 

- 이 하나하나의 업무를 할 때 바로 commit하는 것이 아니다.(기존 자료는 auto commit이었다)

- 이 과정에서 하나가 잘못되면 바로 전부 다 rollback 해야 한다.

 - 임시반영했다가 한번에 모두 커밋하는 작업이 필요하다.

 

 

ShopController 작성

- 새 메소드추가

 

shopServiceImpl

@Override
public void buy(HttpServletRequest request, ModelAndView mView) {
    //구입자의 아이디
    String id=(String)request.getSession().getAttribute("id");
    //1. parameter로 전달되는 구입할 상품 번호
    int num=Integer.parseInt(request.getParameter("num"));
    //2. 상품의 가격을 얻어온다.
    int price=shopDao.getPrice(num);
    //3. 상품의 가격만큼 계좌 잔액을 줄인다.
    ShopDto dto=new ShopDto();
    dto.setId(id);
    dto.setPrice(price);
    shopDao.minusMoney(dto);
    //4. 가격의 10%를 포인트로 적립한다.
    shopDao.plusPoint(dto);
    //5. 재고의 개수를 1 줄인다.
    shopDao.minusCount(num);
    //6. 주문 테이블(배송)에 정보를 추가한다.
    OrderDto dto2=new OrderDto();
    dto2.setId(id); //누가
    dto2.setCode(num); //어떤 상품을
    //클라이언트가 입력한 배송 주소라고 가정
    String addr="강남구 역삼동";
    dto2.setAddr(addr);//어디로 배송할지
    orderDao.addOrder(dto2);			
}

 

 

- spring에서는 이 transaction 작업을 annotation으로 처리할 수 있다.

 

 

- 이 전체가 트랜잭션으로 관리해야하는 업무라면 주의해야 한다.

- 재고가 부족해서 여기서 예외가 발생한다면? 또는 52행에서 발송 불가능한 지역이라 예외가 발생한다면?

 주문도 되지 않았는데 계좌 잔액이 줄거나 포인트가 늘어나면 안되지 않을까?

- 그래서 하나라도 실패하면 전체를 롤백해야 하는 상황이 발생할 수 있다.

 

- transaction은 커밋을 보류하고 있지만, 기본적으로는 작업은 자동 커밋된다. (auto commit)

 

- orderDaoImpl 에서 확인해보기

- session에 commit() 이라는 메소드가 있다.

 

- rollback() 이라는 메소드도 있다.

 

- 하지만 이 전체를 하나하나 관리한다면 번거롭다.

- 그래서 commit과 rollback의 관리를 spring framework에 맡긴다.

 

 

buy.jsp 생성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/shop/buy.jsp</title>
</head>
<body>
	<div class="container">
		<h1>상품 구매 결과 페이지</h1>
		<p><strong>${id }</strong> 님 상품을 정상적으로 주문 했습니다.</p>
		<a href="list">상품 목록보기</a>
	</div>
</body>
</html>

 

 

 

- 구입하면 숫자가 하나씩 줄어들도록 했다.

- 이 재고값이 0이 되면 예외가 발생한다.

 

 

- check 제약조건을 만들어놓았기 때문에 -1은 들어갈 수 없다. 0까지만 값이 들어갈 수 있음.

 

 

- 하지만 계좌에 들어가보면 금액이 줄어있다. 재고가 없는데도 돈이 빠져나갔다!

- 계좌 잔액, 포인트가 6번 주문된 상태가 되어있다.

 

UPDATE client_account 
SET money=10000, point=0
WHERE id='banana';
--
UPDATE shop
SET remainCount=5;

- 위 sql문으로 값을 복구하고 다시 테스트 해보기!

 


 

[ Spring 트랜잭션 설정 방법 ]
1. pom.xml에 spring-tx dependency 추가
2. servlet-context.xml에 transaction 설정 추가
3. 트랜잭션을 관리할 서비스의 메소드에 @Transactional 어노테이션 붙이기

  
- 프로그래머의 의도 하에서 트랜잭션에 영향을 주는 Exception을 발생시키는 방법
  
- DataAccessException 클래스를 상속받은 클래스를 정의하고
ex) class MyException extends DataAccessException{ }
   throw new MyException("예외 메시지");
  
- 예외를 발생시킬 조건이라면 위와 같이 예외를 발생시켜서 트랜잭션이 관리되도록 한다.

 

- pom.xml에 이미 넣어놓은 트랜잭션 라이브러리!

 

- 위에서 servlet-context에서 설정한 것

- tx:annotation-driven 에서 저 txManager 를 필요로 한다.

 

@Transactional

- 이렇게 붙여두기만 하면 이 과정에서 예외가 발생하는 내용이 있다면 전체가 롤백 된다.

 

- 다시 테스트해보면, 재고가 0일때는 아무리 여러번 눌러도 이 이상으로 계좌잔액이 줄거나 포인트가 늘지 않는다!

- 재고도 줄지 않고, DB의 다른 값도 영향을 미치지 않는다.

 


 

ExceptionController 에 메소드 추가 

//DB관련 작업을 하다가 발생하는 모든 예외를 처리하는 컨트롤러
@ExceptionHandler(DataAccessException.class)
public ModelAndView dataAccess(DataAccessException dae) {
    ModelAndView mView=new ModelAndView();
    mView.addObject("exception", dae);
    mView.setViewName("error/info");
    return mView;
}

 

 

- 이 정보가 클라이언트에 출력되도록 한다.

- view page 를 만들면 이것도 컨트롤 가능!

 

 

- Spring 프레임워크는 DB 관련 작업중에 sql Exception이 발생하면

 해당 예외를 잡아서 DataAccessException을 발생시켜 준다.

- 따라서 Exception Controller에서 DataAccessException을 컨트롤할 준비가 되어있다면

 해당 컨트롤러에서 직접 응답할 수가 있다.

 

 

- DataAccessException 은 Transaction에 영향을 주는 Exception이다.

- Transaction의 예외 처리를 하고싶다면 이것을 상속받아서 만든 클래스로 Exception 을 만들어내면 된다.

 

- 이 예시가 바로 위의 내용을 의미한다.

- Exception 클래스를 하나 만든 다음에 상속받으면 된다.

- 이렇게 만들면 transaction에 영향을 미치는 Exception 이 된다.

 

 

DeliveryException 생성

package com.sy.spring04.exception;

import org.springframework.dao.DataAccessException;

/*
 * 트랜잭션에 영향을 주는 예외 클래스 만들기
 * - DataAccessException 클래스를 상속받아서 만든다.
 */

//배송 관련 예외를 발생시키고 싶을 때 사용할 클래스
public class DeliveryException extends DataAccessException{

	public DeliveryException(String msg) {
		super(msg);		
	}

}

 

 

- 상속하고, Add Constructor

 

 

- 부모가 디폴트 생성자가 없으므로 반드시 msg를 전달해 주어야 한다.

- 트랜잭션에 영향을 주는 예외클래스라는 것은 즉 이 예외가 발생하면 rollback시키라는 뜻!

 

 

service - 메소드에서 테스트

ex) 만약 제주도는 주문 불가하다면?

if(addr.contains("제주도")) {
	throw new DeliveryException("제주도는 배송 불가 지역입니다.");
}

- 이렇게 작성하고, 샘플 주소에 '제주도' 라는 텍스트를 넣어준다.

 

- 예외가 발생하면 이렇게 뜬다.

 

 

- DeliveryException 은 DataAccessException 타입이기도 하다.

- 그래서 이것이 실행된다!

 

 

- 만약에 다른처리를 하고 싶다면? 새로 view page를 만들면 된다.

 

Exception Controller 수정

- info 라는 키워드로 정보를 담아준다.

 

 

delivery.jsp 뷰 페이지

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/error/delivery.jsp</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
</head>
<body>
	<div class="container">
		<h3>배송관련 에러 입니다.</h3>
		<p class="alert alert-danger">${exception.message } </p>
		<p class="alert alert-info">
			${info }
			<a href="${pageContext.request.contextPath}/">인덱스로 돌아가기</a>
		</p>
	</div>
</body>
</html>

 

- 뷰페이지에 css cdn을 넣어서 info를 출력해준다.

 

- 이렇게 상황에 따라 예외를 발생시켜서, 

 적절한 정보를 담은 에러 페이지도 띄울 수 있다.