스프링 26강 - MyBatis 동적 SQL 정리, 입력값 검증(Validate)

voilà!·2022년 2월 13일
0

JSP 스프링

목록 보기
26/31

계층형 쿼리

  • 서브쿼리 구분 : 위치에 따라
    1) select : scalar 서브쿼리
    2) where : nested(중첩된) 서브쿼리
    3) from : inline 서브쿼리

MyBatis 동적 SQL 정리
MyBatis는 동적 SQL을 조립하는 구조를 지원해줌
SQL 조립 규칙을 매퍼 XML 파일에 정의할 수 있음

-<where> : where절 앞뒤에 내용을 더 추가하거나 삭제할 때 사용
-<if> : 조건 만족 시 SQL을 조립
-<choose> : 여러 선택 항목에서 조건 만족 시 SQL을 조립
-<foreach> : 컬렉션이나 배열에 대해 반복 처리
-<set> : UPDATE절의 SET절 앞 뒤에 내용을 더 추가하거나 삭제 시 사용

Hibernate-validator 설치

입력값 검증을 위한 라이브러리

1) https://mvnrepository.com/ 에서 hibernate-validator 검색 ->
Home » org.hibernate » hibernate-validator 클릭

2) 5.4.2.Final 버전 클릭 -> 중간 코드 복사
-> pom.xml에 붙여넣기

3) 프로젝트 우클릭 - Run As - 4번 클릭
-> build Success 나오면 설치 성공

입력값 검증(validate)

스프링 MVC 프레임워크에서 Bean(자바빈 클래스, VO) 기능을 이용하여 요청 파라미터 값이 바인딩(=메모리에 올라감) 된 도메인 클래스(=메모리에 올라간 VO객체)의 입력값 검증을 함

입력값 검증 절차

  • 메서드 매개변수에 도메인 클래스를 정의하고 @Validated를 지정함
  • 입력값 검증 대상의 도메인 클래스 직후에 BindingResult를 정의
  • BindingResult에는 요청 데이터의 바인딩 오류와 입력값 검증 오류 정보가 저장됨
  • forwarding을 통해 뷰(jsp)와 오류 데이터를 공유 -> redirect 방식으로는 jsp에 오류정보를 보낼 수 없다!

입력값 검증 결과(BindingResult)

  • hasErrors() : 오류가 발생한 경우 true를 반환
  • getGlobalErrors() : 객체 레벨의 오류
  • getFieldErrors() : 필드(멤버변수) 레벨의 오류

입력값 검증 규칙

  • @NotBlank : 문자열이 null이 아니고 trim한 길이가 0보다 크다는 것을 검사
  • @NotNull : 빈 값이 아닌지 검사
  • @Email : 이메일 주소 형식인지 검사
  • @Size : 글자수 검사
  • @Past : 과거 날짜인지 검사(오늘 기준으로 과거 날짜만 가능)
  • @Future : 미래 날짜인지 검사(오늘 기준으로 미래 날짜만 가능)

예시) detail.jsp에서 수정 시 입력값 검증하기

1.VO

buyerVO.java

package kr.or.ddit;

import java.util.List;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;

//자바빈 클래스
public class BuyerVO {
	//멤버변수
	private String buyerId;
	private String buyerName;
	private String buyerLgu;
	private String buyerBank;
	private String buyerBankno;
	private String buyerBankname;
	private String buyerZip;
	private String buyerAdd1;
	private String buyerAdd2;
	@NotEmpty
	private String buyerComtel;
	private String buyerFax;
	@NotEmpty
	@Email
	private String buyerMail;
	@NotEmpty
	private String buyerCharger;
	private String buyerTelext;
	//상품분류명
	private String lprodNm;
	
	
	//Buyer : ProdVO = 1: N 관계
	private List<ProdVO> ProdVO;
	
	//getter/setter 메소드
	public String getBuyerId() {
		return buyerId;
	}
	public void setBuyerId(String buyerId) {
		this.buyerId = buyerId;
	}
	public String getBuyerName() {
		return buyerName;
	}
	public void setBuyerName(String buyerName) {
		this.buyerName = buyerName;
	}
	public String getBuyerLgu() {
		return buyerLgu;
	}
	public void setBuyerLgu(String buyerLgu) {
		this.buyerLgu = buyerLgu;
	}
	public String getBuyerBank() {
		return buyerBank;
	}
	public void setBuyerBank(String buyerBank) {
		this.buyerBank = buyerBank;
	}
	public String getBuyerBankno() {
		return buyerBankno;
	}
	public void setBuyerBankno(String buyerBankno) {
		this.buyerBankno = buyerBankno;
	}
	public String getBuyerBankname() {
		return buyerBankname;
	}
	public void setBuyerBankname(String buyerBankname) {
		this.buyerBankname = buyerBankname;
	}
	public String getBuyerZip() {
		return buyerZip;
	}
	public void setBuyerZip(String buyerZip) {
		this.buyerZip = buyerZip;
	}
	public String getBuyerAdd1() {
		return buyerAdd1;
	}
	public void setBuyerAdd1(String buyerAdd1) {
		this.buyerAdd1 = buyerAdd1;
	}
	public String getBuyerAdd2() {
		return buyerAdd2;
	}
	public void setBuyerAdd2(String buyerAdd2) {
		this.buyerAdd2 = buyerAdd2;
	}
	public String getBuyerComtel() {
		return buyerComtel;
	}
	public void setBuyerComtel(String buyerComtel) {
		this.buyerComtel = buyerComtel;
	}
	public String getBuyerFax() {
		return buyerFax;
	}
	public void setBuyerFax(String buyerFax) {
		this.buyerFax = buyerFax;
	}
	public String getBuyerMail() {
		return buyerMail;
	}
	public void setBuyerMail(String buyerMail) {
		this.buyerMail = buyerMail;
	}
	public String getBuyerCharger() {
		return buyerCharger;
	}
	public void setBuyerCharger(String buyerCharger) {
		this.buyerCharger = buyerCharger;
	}
	public String getBuyerTelext() {
		return buyerTelext;
	}
	public void setBuyerTelext(String buyerTelext) {
		this.buyerTelext = buyerTelext;
	}
	
	public List<ProdVO> getProdVO() {
		return ProdVO;
	}
	public void setProdVO(List<ProdVO> prodVO) {
		ProdVO = prodVO;
	}
	public String getLprodNm() {
		return lprodNm;
	}
	public void setLprodNm(String lprodNm) {
		this.lprodNm = lprodNm;
	}
	@Override
	public String toString() {
		return "BuyerVO [buyerId=" + buyerId + ", buyerName=" + buyerName + ", buyerLgu=" + buyerLgu + ", buyerBank="
				+ buyerBank + ", buyerBankno=" + buyerBankno + ", buyerBankname=" + buyerBankname + ", buyerZip="
				+ buyerZip + ", buyerAdd1=" + buyerAdd1 + ", buyerAdd2=" + buyerAdd2 + ", buyerComtel=" + buyerComtel
				+ ", buyerFax=" + buyerFax + ", buyerMail=" + buyerMail + ", buyerCharger=" + buyerCharger
				+ ", buyerTelext=" + buyerTelext + ", lprodNm=" + lprodNm + ", ProdVO=" + ProdVO + "]";
	}
	
		
}

2.xml

lprod_SQL.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="lprod">
	<resultMap type="lprodVO" id="lprodMap">
		<id property="lprodGu" column="LPROD_GU" />
		<result property="rnum" column="RNUM" />
		<result property="lprodNm" column="LPROD_NM" />
		<collection property="buyerVO" resultMap="buyerMap">
		</collection>
	</resultMap>

	<resultMap type="buyerVO" id="buyerMap">
		<result property="buyerId" column="BUYER_ID"/>
		<result property="buyerName" column="BUYER_NAME"/>
		<result property="buyerLgu" column="BUYER_LGU"/>
	</resultMap>
	
<!-- 상품분류 별 거래처 목록 -->
<!-- 
parameter : 
	{keyWord=캐주,currentPage=1,size=10}
-->
	<select id="list" parameterType="hashMap" resultMap="lprodMap">
		SELECT ROWNUM RNUM, T.LPROD_GU, T.LPROD_NM
	                     , T.BUYER_ID, T.BUYER_NAME, T.BUYER_LGU
          FROM
          (
               SELECT ROW_NUMBER() OVER(ORDER BY LPROD_NM, BUYER_ID) RNUM
                   , L.LPROD_GU
                   , L.LPROD_NM
                   , B.BUYER_ID
                   , B.BUYER_NAME
                   , B.BUYER_LGU
              FROM   LPROD L INNER JOIN BUYER B
              ON(L.LPROD_GU = B.BUYER_LGU)
             WHERE 1 = 1
             <if test="keyWord!=null and keyWord!=''">
                AND (L.LPROD_GU LIKE '%'||#{keyWord}||'%' 
                OR	 L.LPROD_NM LIKE '%'||#{keyWord}||'%' 
                OR	 B.BUYER_ID LIKE '%'||#{keyWord}||'%' 
                OR	 B.BUYER_NAME LIKE '%'||#{keyWord}||'%')
              </if> 
          ) T
	      WHERE T.RNUM BETWEEN #{currentPage}*7-6 AND #{currentPage}*7
	</select>
	
	<!-- 상품분류 별 거래처 목록의 totalCount -->
	<select id="listCount" parameterType="hashMap" resultType="int">
		SELECT COUNT(*)
		FROM LPROD L INNER JOIN BUYER B
		ON(L.LPROD_GU = B.BUYER_LGU)
        WHERE 1 = 1
        <if test="keyWord!=null and keyWord!=''">
           AND (L.LPROD_GU LIKE '%'||#{keyWord}||'%' 
           OR	 L.LPROD_NM LIKE '%'||#{keyWord}||'%' 
           OR	 B.BUYER_ID LIKE '%'||#{keyWord}||'%' 
           OR	 B.BUYER_NAME LIKE '%'||#{keyWord}||'%')
        </if> 		
	</select>
	
	<!-- 거래처 상세 정보 -->
	<select id="detail" parameterType="String" resultType="buyerVO">
	SELECT (SELECT L.LPROD_NM FROM LPROD L WHERE L.LPROD_GU = B.BUYER_LGU) LPROD_NM
           , B.BUYER_ID
           , B.BUYER_NAME
           , B.BUYER_LGU
           , BUYER_BANK     
           , BUYER_BANKNO
           , BUYER_BANKNAME
           , BUYER_ZIP
           , BUYER_ADD1
           , BUYER_ADD2
           , BUYER_COMTEL
           , BUYER_FAX
           , BUYER_MAIL
           , BUYER_CHARGER
           , BUYER_TELEXT
      FROM   BUYER B
      WHERE B.BUYER_ID = #{buyerId}
	</select>
	
	
</mapper>

3. Dao

LprodDao.java

package kr.or.ddit;

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

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class LprodDao {
	
	@Autowired
	SqlSessionTemplate sqlSessionTemplate;
	
	public List<LprodVO> list(Map<String, Object> map){
		return this.sqlSessionTemplate.selectList("lprod.list", map);
	}
	//상품분류 별 거래처 목록 행의 수
	public int listCount(Map<String, Object> map) {
		return this.sqlSessionTemplate.selectOne("lprod.listCount", map);
	}
	//거래처 상세 정보
	public BuyerVO detail(String buyerId) {
		return this.sqlSessionTemplate.selectOne("lprod.detail", buyerId);
	}
	
	
	
}

4. Service

LprodService.java

package kr.or.ddit;

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

public interface LprodService {
	
	//메소드 시그니처 처리
	//상품분류 별 거래처 목록
	public List<LprodVO> list(Map<String, Object> map);
	//상품분류 별 거래처 목록 행의 수
	public int listCount(Map<String, Object> map);
	//거래처 상세 정보
	BuyerVO detail(String buyerId);
	
	
}

LprodServiceImpl.java

package kr.or.ddit;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class LprodServiceImpl implements LprodService {
	@Autowired
	LprodDao lprodDao;
	
	@Override
	public List<LprodVO> list(Map<String, Object> map){
		return this.lprodDao.list(map);
	}
	
	//상품분류 별 거래처 목록 행의 수
	@Override
	public int listCount(Map<String, Object> map) {
		return this.lprodDao.listCount(map);
	}
	
	//거래처 상세 정보
	@Override
	public BuyerVO detail(String buyerId) {
		return this.lprodDao.detail(buyerId);
	}
}

@RequestMapping 정리

  • 요청 경로는 필수!
  • 속성이 하나일 때는 속성명 생략 가능
  • ex) @RequestMapping(value="detail", method = RequestMethod.GET) -> @RequestMapping("/detail")
  • 클래스 레벨과 메서드 레벨로 지정 가능
  • 클래스 레벨로 요청 경로를 지정하면 메서드 레벨에서 지정한 경로의 기본 경로로 취급

5. Controller

LprodController.java

	///lprod/detail?buyerId=P10101 요렇게 넘어온다
	//리턴 타입 : String => 뷰 파일의 경로와 파일 이름을 나타내기 위해 사용
	@RequestMapping(value = "detail", method = RequestMethod.GET)
	public String detail(Model model, @RequestParam String buyerId) {
    logger.info("buyerId : " + buyerId);
    
    BuyerVO buyerVO = this.lprodService.detail(buyerId);
    
    //buyerVO이 null이면 오류를 보자
    if(buyerVO! = null) {
    	logger.info("buyerVO : " + buyerVO.toString());
    }
    
    model.addAttribute("buyerVO", buyerVO);
    
    //forward
    //반환값이 lprod/detail이므로 lprod폴더의 detail.jsp를 리턴
    return "/lprod/detail";
   
   }

	//detail.jsp에서 $("#buyerVO").submit();로 보내면 받는다!
	//수정 요청 매핑
    @RequestMapping(value = "/detail", method = RequestMethod.POST)
    public String detailPost(@Validated BuyerVO buyerVO, BindingResult result) {
    logger.info("buyerVO : " + buyerVO.toString());
    
    //validation 중에 문제가 발생된다면 true
    logger.info("result.hasErrors() : " + result.hasErrors());
    //validation 문제가 발생된다면
    //allErrors = globalErrors + fieldErrors
		if(result.hasErrors()) {
			List<ObjectError> allErrors = result.getAllErrors();
			List<ObjectError> globalErrors = result.getGlobalErrors();
			List<FieldError> fieldErrors = result.getFieldErrors();
			
			//validation 중에 어떤 오류가 나왔는지 확인..
			for(int i = 0; i<allErrors.size(); i++) {
				ObjectError objectError = allErrors.get(i);
				logger.info("objectError : " + objectError);
			}
			
			for(ObjectError objectError : globalErrors) {
				logger.info("objectError : " + objectError);
			}
			
			for(FieldError fieldError : fieldErrors) {
				logger.info("fieldError : " + fieldError.getDefaultMessage());
			}
			//문제가 발생되면 수정프로세스는 중단되고 되돌아감
			return "lprod/detail";
		}//end if
		
		//수정 비즈니스 로직 처리 후
		
		//상세 페이지로 되돌아감
		return "redirect:/lprod/detail?buyerId="+buyerVO.getBuyerId();
		
	}
	
}
    

6. JSP

detail.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<!DOCTYPE html>
<html>

<head>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>상품분류 별 거래처 목록</title>

    <!-- Custom fonts for this template-->
    <link href="/resources/sbadmin2/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
    <link
        href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
        rel="stylesheet">

    <!-- Custom styles for this template-->
    <link href="/resources/sbadmin2/css/sb-admin-2.min.css" rel="stylesheet">
    <script type="text/javascript">
    	function fn_submit() {
    		var frm = document.searchForm;
    		console.log("frm.size : " + frm.size.value);
    		console.log("frm.keyWord : " + frm.keyWord.value);
    		frm.submit();
    	}
    </script>

</head>

<body id="page-top">

    <!-- Page Wrapper -->
    <div id="wrapper">

        <!-- Sidebar -->
        <jsp:include page="../includes/aside.jsp"></jsp:include>
        <!-- End of Sidebar -->

        <!-- Content Wrapper -->
        <div id="content-wrapper" class="d-flex flex-column">

            <!-- Main Content -->
            <div id="content">

                <!-- Topbar -->
                <jsp:include page="../includes/header.jsp"></jsp:include>
                <!-- End of Topbar -->

<!-- Begin Page Content 본문 시작 -->
<div class="container-fluid">
<div class="card o-hidden border-0 shadow-lg my-5">
       <div class="card-body p-0">
           <!-- Nested Row within Card Body -->
      <div class="row">
          <div class="col-lg-5 d-none d-lg-block bg-register-image"></div>
          <div class="col-lg-7">
              <div class="p-5">
                  <div class="text-center">
                      <h1 class="h4 text-gray-900 mb-4">${buyerVO.buyerName}</h1>
                    </div>
                    <form:form modelAttribute="buyerVO" class="user" method="post" action="/lprod/detail">
                        <div class="form-group row">
                            <div class="col-sm-6 mb-3 mb-sm-0">
                                <form:input path="buyerId" class="form-control form-control-user" id="buyerId" placeholder="" />
                            </div>
                            <div class="col-sm-6">
                                <form:input path="lprodNm" class="form-control form-control-user" id="lprodNm" placeholder="" />
                            </div>
                        </div>
                        <div class="form-group">
                            <form:input path="buyerMail" class="form-control form-control-user" id="buyerMail" placeholder="" />
                        	<font color="red"><form:errors path="buyerMail" /></font>
                        </div>
                        <div class="form-group row">
                            <div class="col-sm-6 mb-3 mb-sm-0">
                                <form:input path="buyerComtel" class="form-control form-control-user" id="buyerComtel" />
                            	<font color="red"><form:errors path="buyerComtel" /></font>
                            </div>
                            <div class="col-sm-6">
                                <form:input path="buyerCharger" class="form-control form-control-user" id="buyerCharger" />
                            	<font color="red"><form:errors path="buyerCharger" /></font>
                            </div>
                        </div>
                        <a href="#" id="btnEdit" class="btn btn-primary btn-user btn-block">
                        	수정
                        </a>
                        <a href="#" id="btnConfirm" style="display:none;" class="btn btn-success btn-icon-split btn-block">
                            <span class="icon text-white-50">
                                <i class="fas fa-check"></i>
                            </span>
                            <span class="text">확인</span>
                        </a>
                        <a href="#" id="btnCancel" style="display:none;" class="btn btn-danger btn-icon-split btn-block">
                             <span class="icon text-white-50">
                                 <i class="fas fa-trash"></i>
                             </span>
                             <span class="text">취소</span>
                        </a>            
                        <hr>
                        <a href="/lprod/list" class="btn btn-google btn-user btn-block">
                            <i class="fab fa-google fa-fw"></i> 목록보기
                        </a>
                    </form:form>
                    <hr>
                    <div class="text-center">
                        <a class="small" href="forgot-password.html">Forgot Password?</a>
                    </div>
                    <div class="text-center">
                        <a class="small" href="login.html">Already have an account? Login!</a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
        <!-- /.container-fluid 본문 끝-->

</div>
            <!-- End of Main Content -->

            <!-- Footer -->
            <jsp:include page="../includes/footer.jsp"></jsp:include>
            <!-- End of Footer -->

        </div>
        <!-- End of Content Wrapper -->

    </div>
    <!-- End of Page Wrapper -->

    <!-- Scroll to Top Button-->
    <a class="scroll-to-top rounded" href="#page-top">
        <i class="fas fa-angle-up"></i>
    </a>

    <!-- Logout Modal-->
    <div class="modal fade" id="logoutModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
        aria-hidden="true">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="exampleModalLabel">수정 요청</h5>
                    <button class="close" type="button" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">×</span>
                    </button>
                </div>
                <div class="modal-body">Select "Logout" below if you are ready to end your current session.</div>
                <div class="modal-footer">
                    <button class="btn btn-secondary" type="button" data-dismiss="modal">취소</button>
                    <a class="btn btn-primary" id="modalConfirm" href="#">확인</a>
                </div>
            </div>
        </div>
    </div>

    <!-- Bootstrap core JavaScript-->
    <script src="/resources/sbadmin2/vendor/jquery/jquery.min.js"></script>
    <script src="/resources/sbadmin2/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>

    <!-- Core plugin JavaScript-->
    <script src="/resources/sbadmin2/vendor/jquery-easing/jquery.easing.min.js"></script>

    <!-- Custom scripts for all pages-->
    <script src="/resources/sbadmin2/js/sb-admin-2.min.js"></script>

    <!-- Page level plugins -->
    <script src="/resources/sbadmin2/vendor/chart.js/Chart.min.js"></script>

    <!-- Page level custom scripts -->
    <script src="/resources/sbadmin2/js/demo/chart-area-demo.js"></script>
    <script src="/resources/sbadmin2/js/demo/chart-pie-demo.js"></script>
    
    <script type="text/javascript">
	   	$(function(){
	   		$(".form-control-user").prop("readonly",true);
	   		
	   		//수정버튼 클릭 시 수정버튼 사라지고, 확인/취소버튼 보임
	   		$("#btnEdit").on("click",function(){
	   			$(".form-control-user").prop("readonly",false);
	   			//수정 대상이 아닌 요소 처리
	   			//단, disabled -> true(X, 요소가 비활성됨) => form으로 안넘어감..
	   			//readonly를 쓰자!
	   			$("#buyerId").prop("readonly",true);
	   			$("#lprodNm").prop("readonly",true);
	   			
	   			//수정버튼 사라지고
	   			$("#btnEdit").css("display", "none");
	   			
	   			//확인/취소버튼 보임
	   			$("#btnConfirm").css("display", "block");
	   			$("#btnCancel").css("display", "block");
	   		});
	   		//취소버튼 클릭  시 새로고침 처리
	   		$('#btnCancel').on("click",function(){
	   			location.reload();
	   		});
	   		//확인버튼 클릭 시 수정 요청
	   		$("#btnConfirm").on("click",function(){
	   			$(".modal-body").html("해당 글을 수정 하시겠습니까?");
	   			$("#logoutModal").modal("show");
	   		});
	   		//모달 확인 버튼 클릭 시 수정 프로세스 진행
	   		$("#modalConfirm").on("click",function(){
	   			$("#buyerVO").submit();
	   		});
	   	});
    </script>
    

</body>

</html>

0개의 댓글