본문 바로가기

프로그래밍/Spring

Custom Annotation을 이용한 객체 검증 작업..(2)

저번 포스팅에 이어서 이번엔 ConstraintValidator 인터페이스를 구현하여 검증 역할을 하는 클래스인 ExistCheckValidator가 사용하는 비즈니스 서비스와 이를 Controller 및 jsp에서 어떻게 표현되는지를 다뤄보고자 한다. ExistCheckValidator 클래스는 검증 업무를 하는 서비스인 ValidateService 클래스 객체를 갖고 있다. ValidateService는 중복여부를 체크하는 함수인 existCheck 함수만 정의되어 있는 인터페이스이며 ExistCheckValidator 클래스에는 ValidateService 인터페이스를 구현한 클래스인 ValidateServiceImpl 클래스가 DI된다. 그런 내용을 알고 다음에 나오는 ValidateServiceImpl 클래스 소스를 보길 바란다

 

package com.terry.boardprj.service.impl;

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

import com.terry.boardprj.dao.BoardDAO;
import com.terry.boardprj.service.ValidateService;

@Service
public class ValidateServiceImpl implements ValidateService {

    @Autowired
    BoardDAO dao;
    
    @Override
    public boolean existCheck(String tableName, String columnName, String value) throws DataAccessException {
        // TODO Auto-generated method stub
        if(dao.existCheck(tableName, columnName, value) == 0){
            return false;
        }else{
            return true;
        }
    }

}

 

소스를 보면 알겠지만 딱히 특이한 점은 없는 소스이다. 그저 ExistCheckValidator의 isValid 함수에서 ValidateService의 existCheck 함수를 호출할때 넘겨주었던 테이블명, 컬럼명, 체크해야 할 값을 받아다가 DAO 역할을 하는 BoardDAO 클래스의 existCheck 함수에 넘겨서 DB 조회 결과를 받아 그에 맞춰 true나 false를 넘겨주는 것이다. BoardDAO 클래스의 existCheck 함수는 넘겨받은 테이블명과 컬럼명, 그리고 중복여부를 체크해야 할 값을 받아다가 쿼리를 실행시켜 그 결과를 return 해준다. 그래서 그 결과가 0이 return 되면 중복여부를 체크해야 할 값이 현재 등록되어 있지 않음을 나타내기 때문에 false를 return 하는 거고, 0이 아닌 경우는 이미 등록되어 있다는 의미이기 때문에 true를 return 해 준다. 이 예제는 Mybatis를 Spring과 연동하여 DB를 사용하고 있다. 그래서 BoardDAO 클래스의 부모클래스인 BaseDAO 클래스에서는 SqlSessionDaoSupport 클래스를 상속받고 있다. 이러한 내용을 알고 다음의 BoardDAO 클래스 소스를 보길 바란다(원래 BoardDAO 클래스에는 게시판 관련 작업을 하는 메소드들도 같이 있으나 이번 설명에서는 언급할 내용이 없어서 관련 메소드들은 삭제하고 보여주고 있다)

 

package com.terry.boardprj.dao;

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

import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;

import com.terry.boardprj.common.BaseDAO;
import com.terry.boardprj.vo.BoardVO;

@Repository
public class BoardDAO extends BaseDAO{

    public BoardDAO(){
        super();
    }
    
    public int existCheck(String tableName, String columnName, String value) throws DataAccessException {
        Map<String, String> param = new HashMap<String, String>();
        param.put("tableName", tableName);
        param.put("columnName", columnName);
        param.put("value", value);
        int result = getSqlSession().selectOne("board.existCheck", param);
        return result;
    }
}

 

BoardDAO 클래스의 existCheck 메소드를 보면 사용자가 입력한 테이블명, 컬럼명, 중복 여부를 체크해야 할 값을 받아다가 board.existCheck란 이름을 갖는 쿼리를 실행할때 관련 내용을 Map을 통해 넘겨주고 있다. board.existCheck 쿼리는 일반적인 COUNT 쿼리이다. 그러나 이전 포스팅에서 언급했던 중복여부 체크를위해 사용하는 쿼리의 패턴을 보고 이 쿼리를 보면 값이 어떤 형태로 들어가는지 알것이다. board.existCheck 쿼리는 다음과 같이 정의되어 있다.

 

<mapper namespace="board">
        <select id="existCheck" parametertype="map" resulttype="int">
                SELECT COUNT(${columnName}) FROM ${tableName} where ${columnName} = #{value}
        </select>
</mapper>

 

쿼리문을 보고 눈치빠른 분들은 짐작이 가시겠지만 설명을 하자면..위에서 테이블명, 컬럼명, 중복 여부를 체크해야 할 값들을 넘긴다고 했었다. 그러면 이 쿼리에서는 테이블명과 컬럼명을 갖고 있는 tableName과 columnName 변수가 쿼리문에 inline 형태의 문자열로 들어가지게 된다. 그리고 중복 여부를 체크해야 할 값인 value 변수는 쿼리문에 #{value}에 대입이 되어 쿼리가 실행이 된다. 이렇게 함으로써 일반적인 중복 여부 체크에 대한 일반적이고 범용적인 케이스를 다룰수 있게 되는 것이다. 이전 포스트에서 언급했던 중복 여부를 체크하는 쿼리 패턴을 상기해보면 무슨 뜻인지 이해가리라 생각된다. BoardDao의 existCheck 함수는 이 쿼리를 실행하여 나오는 count 결과를 return 하게 되며 이 값은 ValidateServiceImpl 클래스의 existCheck 함수에서 이용되어 그에 따라 true나 false를 return 하게 된다. 그래서 이렇게 return 된 boolean 값을 ExistCheckValidator 클래스의 isValid 함수가 판단하여 그에 따라 다시 boolean 값을 리턴하여 검증 로직 통과 여부를 알려주게 되는 것이다.

 

지금까지는 이렇게 검증 과정을 하는 클래스에 대한 정의들을 보여주었고 이제 이를 어떻게 사용하는지 알려주도록 하겠다. 예제로 어떤 글을 등록하는 웹페이지를 가지고 설명하겠다. 설명되는 소스를 보면 알겠지만 이 웹페이지는 DB에 실제 글을 등록하는 기능은 없다. 하지만 이 웹페이지에서 입력한 제목이 기존에 DB에 있는지를 조회해서 그 결과에 따른 검증작업을 하도록 되어 있다. 즉 같은 제목으로는 글이 등록될수 없게끔 해놓은 것이다. 그런 내용을 알고 다음부터 설명하게 되는 소스들을 보기 바란다.

 

Spring을 이용한 검증 작업을 할때는 jsp에서 Spring에서 사용하는 전용태그들을 사용하도록 되어 있다. 그래서 jsp에서는 Spring에서 제공하는 별도의 태그 라이브러리를 사용해야 한다(<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>) 예제 jsp의 url은 insertBoardValid.do로 정의할 것이다. 그런 사항을 먼저 알고서 다음부터 보여주는 jsp 소스와 Spring Controller 소스를 보길 바란다.(jsp 소스는 body 태그 안의 것만 넣었으며 Controller 소스도 지금 설명하고자 하는 내용과 상관이 없는 내용은 삭제하고 보여주도록 하겠다)

 

<body>
    <form:form commandName="boardModel" id="regfrm" name="regfrm" method="post">
        <table>
            <tr>
                <td>작성자</td>
                <td>
                    <form:input type="text" path="writer" value="" />
                    <form:errors path="writer" />
                </td>
            </tr>
            <tr>
                <td>제목</td>
                <td>
                    <form:input type="text" path="title" value="" />
                    <form:errors path="title" />
                </td>
            </tr>
            <tr>
                <td>내용</td>
                <td>
                    <form:textarea path="content"></form:textarea>
                    <form:errors path="content" />
                </td>
            </tr>
        </table>
        <input type="button" id="regbtn" value="등록" />
        <input type="button" id="resetbtn" value="재작성" />
    </form:form>
</body>

 

package com.terry.boardprj.controller;

import javax.validation.Valid;

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

import com.terry.boardprj.vo.BoardModel;

@Controller
public class BoardController {
    
    @RequestMapping(value = "insertBoardValid", method=RequestMethod.GET)
    public String insertBoardValid(Model model){
	model.addAttribute("boardModel", new BoardModel());
	return "insertBoardValid";
    }
    
    @RequestMapping(value = "insertBoardValid", method=RequestMethod.POST)
    public String insertBoardValidJob(@ModelAttribute @Valid BoardModel board, BindingResult result){
	if(result.hasErrors()){
	    return "insertBoardValid";
	}
	
	return "redirect:/boardList.do";
    }
    
}

 

Controller 소스를 보면 insertBoardValid란 URL로 2군데의 함수가 GET 방식, POST 방식으로 접근했을때를 나눠서 구현하고 있다. 즉 GET 방식으로 접근했을때는 insertBoardValid 함수가, POST 방식으로 접근했을때는 insertBoardValidJob 함수가 실행이 되는 것이다. 처음에 웹브라우저로 접근해서 화면을 보고자 할때는 GET 방식으로 접근을 할것이기 때문에 insertBoardValid 함수가 실행이 된다. 이 함수는 이전 포스트에서 언급했던 BoardModel 클래스 객체를 만든뒤 이를 boardModel이란 키를 주어 Model에 저장하고 화면을 보여주고 있다. 이 부분이 중요한데 jsp 소스에 보면 다음과 같은 부분이 있다.

 

<form:form commandName="boardModel" id="regfrm" name="regfrm" method="post">

 

여기서 commandName 속성과 Controller에서 Model에 저장할때 사용했던 key와 일치시켜주어야 한다. 그렇게 해야 <form:form> 태그 안에 있는 입력양식들이 BoardModel 클래스 객체의 멤버변수와 일치가 되기 때문이다. jsp 소스를 보면 다음과 같은 부분이 있다

 

 

<form:input type="text" path="title" value="" />

<form:errors path="title" />

 

path 속성에 title이라고 함으로써 BoardModel 클래스의 title 멤버변수와 매치시켜주는 것이다. 즉 text 태그에 입력된 내용은 자동으로 BoardModel 클래스의 title 멤버변수에 들어가게 되며 title 멤버변수와 관련된 검증 에러가 발생하였을 경우 관련 문구도 <form:errors> 태그를 통해 보여주도록 되는 것이다. 

 

등록버튼에 POST 방식으로 insertBoardValid에 submit 되도록 자바스크립트 등으로 코딩한뒤에 버튼을 클릭해보면 POST 방식으로 insertBoardValid URL에 접근했기 때문에 insertBoardValidJob이란 함수를 사용하게 된다. 이 함수를 보면 @Valid란 annotation이 있다. 이 annotation이 있어야 annotation을 이용한 검증 작업을 하게 되는 것이다. 이전 포스트에서 BoardModel의 title 멤버변수에 검증 작업을 수행하는 annotation인 @NotNull, @Min, 그리고 중복 여부를 체크하는 @ExistCheck annotation을 붙여두었다. POST 하는 시점엔 title 멤버변수엔 웹페이지에서 입력한 값이 들어 있을 것이기 때문에 이 값을 가지고 검증 작업을 하게 되는 것이고 그 결과는 BindingResult 클래스 객체로 insertBoardValidJob 함수 파라미터에 넣은 result에 들어가게 된다. insertBoardValidJob 함수 안에서는 검증 결과가 들어있는 BindingResult 클래스 객체의 hasError() 함수를 호출하여 에러가 발생하였는지 확인하고 에러가 발생한 경우 입력 URL을 그대로 return 함으로써 입력 페이지를 다시 보여주게 된다. 우리가 @ModelAttribute를 사용하였기 때문에 우리가 입력한 값은 Model에 들어있는 상태이며 이 Model을 같이 돌려주기 때문에 입력 페이지를 다시 보여주더라도 우리가 입력했던 값이 그대로 셋팅이 된 상태로 보여주게 된다. 거기에 덧붙여서 에러 메시지도 같이 보여주는 것이다. 그래서 밑에서 나오는 화면마냥 보이게 해줄려는것이다. 다음의 화면은 제목이 222로 되어 있는 글이 이미 DB에 있어서 등록 버튼을 클릭했을때 에러 메시지를 보여주는 것이다. 지금까지의 셋팅으로는 아직은 밑의 화면을 볼수는 없다

 

 

이상으로 검증 annotation을 이용한 검증 과정을 설명했다. 다음 포스트에서는 마지막 내용으로 에러 메시지를 출력하는 부분과 몇 가지 부가적인 사항을 다루면서 끝내고자 한다.