본문 바로가기

Java/JavaSpring

Chap 14. MVC 4 : 날짜 값 변환, @PathVariable, 익셉션 처리

REGDATE 값이 두 파라미터로 전달받은 from 과  to 사이의 Member 목록을 구한다.

	private RowMapper<Member> memRowMapper = 
			new RowMapper<Member>() {
				@Override
				public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
					Member member = new Member(rs.getString("EMAIL"),
							rs.getString("PASSWORD"),
							rs.getString("NAME"),
							rs.getTimestamp("REGDATE").toLocalDateTime());
					member.setId(rs.getLong("ID"));
					return member;
				}
			};

 

시간을 나타내기 위해 LocalDateTime 타입으로 변환(Casting)해야 한다. @DateTimeFormat 애노테이션을 통해 자동으로 형변환 할 수 있다.

 

import org.springframework.format.annotation.DateTimeFormat;

public class ListCommand {

	@DateTimeFormat(pattern = "yyyyMMddHH")
	private LocalDateTime from;
	@DateTimeFormat(pattern = "yyyyMMddHH")
	private LocalDateTime to;

 

@DateTimeFormat 애노테이션을 통해 LocalDateTime 타입으로 변환한다. "2018030115" 문자열을 "2018년 3월 1일 15시" 값을 갖는 LocalDateTime 객체로 변환할 수 있다.

 

다음은 ListCommand 클래스를 커맨드 객체로 사용한 컨트롤러이다.

 

package controller;

import java.util.List;

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

import spring.Member;
import spring.MemberDao;

@Controller
public class MemberListController {

	private MemberDao memberDao;

	public void setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
	}

	@RequestMapping("/members")
	public String list(
			@ModelAttribute("cmd") ListCommand listCommand,
			Errors errors, Model model) {
		if (listCommand.getFrom() != null && listCommand.getTo() != null) {
			List<Member> members = memberDao.selectByRegdate(
					listCommand.getFrom(), listCommand.getTo());
			model.addAttribute("members", members);
		}
		return "member/memberList";
	}

}

 

태그 파일을 사용하여 LocalDateTime 값을 지정한 형식으로 출력한다.

 

<%@ tag body-content="empty" pageEncoding="utf-8" %>
<%@ tag import="java.time.format.DateTimeFormatter" %>
<%@ tag trimDirectiveWhitespaces="true" %>
<%@ attribute name="value" required="true" 
              type="java.time.temporal.TemporalAccessor" %>
<%@ attribute name="pattern" type="java.lang.String" %>
<%
	if (pattern == null) pattern = "yyyy-MM-dd";
%>
<%= DateTimeFormatter.ofPattern(pattern).format(value) %>

 

view는 다음과 같다.

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="tf" tagdir="/WEB-INF/tags" %>    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원조회</title>
</head>
<body>
    <form:form modelAttribute="cmd">
    <p>
        <label>from: <form:input path="from" /></label>
        ~
        <label>to:<form:input path="to" /></label>
        <input type="submit" value="조회">
    </p>
    </form:form>
    
    <c:if test="${! empty members}">
    <table>
        <tr>
            <th>아이디</th><th>이메일</th>
            <th>이름</th><th>가입일</th>
        </tr>
        <c:forEach var="mem" items="${members}">
        <tr>
            <td>${mem.id}</td>
            <td><a href="<c:url value="/members/${mem.id}"/>">
                ${mem.email}</a></td>
            <td>${mem.name}</td>
            <td><tf:formatDateTime value="${mem.registerDateTime }" 
                                   pattern="yyyy-MM-dd" /></td>
        </tr>
        </c:forEach>
    </table>
    </c:if>
</body>
</html>

 

해당 화면에서 from 이나 to 에서 지적된 형식에 맞지 않는 값을 입력하면 400에러가 발생한다. 400에러 대신 에러메시지를 출력하고 싶다면 Errors 타입 파라미터를 요청 매핑 어노테이션 적용 메서드에 추가한다.

 

package controller;

import java.util.List;

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

import spring.Member;
import spring.MemberDao;

@Controller
public class MemberListController {

	private MemberDao memberDao;

	public void setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
	}

	@RequestMapping("/members")
	public String list(
			@ModelAttribute("cmd") ListCommand listCommand,
			Errors errors, Model model) {
		if (errors.hasErrors()) {
			return "member/memberList";
		}
		if (listCommand.getFrom() != null && listCommand.getTo() != null) {
			List<Member> members = memberDao.selectByRegdate(
					listCommand.getFrom(), listCommand.getTo());
			model.addAttribute("members", members);
		}
		return "member/memberList";
	}

}

 

label.properties에 typeMismatch의 메시지를 추가한다. 

 - typeMismatch.java.time.LocalDateTime = 잘못된 형식

 

view 도 다음과 같이 수정한다.

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="tf" tagdir="/WEB-INF/tags" %>    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원조회</title>
</head>
<body>
    <form:form modelAttribute="cmd">
    <p>
        <label>from: <form:input path="from" /></label>
        <form:errors path="from" />
        ~
        <label>to:<form:input path="to" /></label>
        <form:errors path="to" />
        <input type="submit" value="조회">
    </p>
    </form:form>
    
    <c:if test="${! empty members}">
    <table>
        <tr>
            <th>아이디</th><th>이메일</th>
            <th>이름</th><th>가입일</th>
        </tr>
        <c:forEach var="mem" items="${members}">
        <tr>
            <td>${mem.id}</td>
            <td><a href="<c:url value="/members/${mem.id}"/>">
                ${mem.email}</a></td>
            <td>${mem.name}</td>
            <td><tf:formatDateTime value="${mem.registerDateTime }" 
                                   pattern="yyyy-MM-dd" /></td>
        </tr>
        </c:forEach>
    </table>
    </c:if>
</body>
</html>

 

변환 처리에 대한 이해

 

@DateTimeFormat 애노테이션을 통해 LocalDateTime 타입으로 변환해준다. 이때 해당 데이터 타입으로 변환시켜 주는 것이 WebDataBinder이다. WebDataBinder는 역할을 Conversion Service에게 위임한다. @EnableWebMvc 애노테이션 사용시 DefaultFormattingConversionService를 ConversionService로 사용한다. 커맨드로 사용할 클래스에 @DateTimeFormat 애노테이션만 붙이면 지정한 형식의 문자열을 시간 타입 값으로 받을 수 있다. 이렇게 받은 값을 jsp의 form을 표현한다.

 

@PathVariable을 이용한 경로 변수 처리

로그인하여 접속하는 개인정보를 가진 처리에서는 정보보안의 문제로 잘 쓰지 않는다.  

 

	@GetMapping("/members/{id}")
	public String detail(@PathVariable("id") Long memId, Model model) {
		Member member = memberDao.selectById(memId);
		model.addAttribute("member",member);
		return "member/memberDetail";
	}

 

'{경로변수}' 와 같이 중괄호로 둘러 쌓인 부분을 경로변수라고 부른다.경로값을 @PathVariable("id") 애노테이션이 적용된 memId 파라미터에 전달한다. memId 파라미터 타입을 WebDataBinder를 통해 String 타입 값을 Long으로 변환한다.

 

같은 컨트롤러에 @ExceptionHandler 애노테이션을 적용한 메서드가 존재하면 그 메서드가 익셉션을 처리한다. 

 

package controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.TypeMismatchException;
import org.springframework.web.bind.annotation.ExceptionHandler;

import spring.Member;
import spring.MemberDao;
import spring.MemberNotFoundException;

@Controller
public class MemberDetailController {
	public MemberDao memberDao;
	
	public void setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
	}
	
	@GetMapping("/members/{id}")
	public String detail(@PathVariable("id") Long memId, Model model) {
		Member member = memberDao.selectById(memId);
		if(member == null) {
        		//@ExceptionHandler의 파라미터 값에 따라 처리가 달라진다.
			throw new MemberNotFoundException();  
		}
		model.addAttribute("member",member);
		return "member/memberDetail";
	}
	
	@ExceptionHandler(TypeMismatchException.class)
	public String handleTypeMismatchException() {
		return "member/invalidId";
	}
	
	@ExceptionHandler(MemberNotFoundException.class)
	public String MemberNotFoundException() {
		return "member/noMember";
	}
}

 

 

@ControllerAdvice를 이용한 공통 익셉션 처리

컨트롤러 클래스에 @ExceptionHandler 애노테이션을 적용하면 해당 컨트롤러에서 발생한 익셉션만 처리한다. 여러 컨트롤러에서 동일하게 처리할 익셉션 발생시 @ControllerAdvice 애노테이션을 이용해서 중복을 없앤다.

 

@ControllerAdvice("spring")//spring이 범위가 된다.
public class CommonExceptionHandler{
	@ExceptionHandler(RuntimeException.class)
    public String handleRuntimeException(){
    	return "error/commonException";
    }
}

 

 

@ExceptionHandler 적용 매서드의 우선순위는 다음과 같다. 

 

속성 타입 설명
value
basePackages
String[] 공통 설정을 적용할 컨트롤러가 속하는 패키지
annotations Class<? extends Annotation>[] 특정 애노테이션이 적용된 컨트롤러 대상
assignableTypes Class<?>[] 특정 타입 또는 그 하위 타입인 컨트롤러 대상

 

@ExceptionHandler를 붙인 메서드는 다음 파라미터를 가질 수 있다.

- HttpServletRequest, HttpServletResponse, HttpSession

- Model

- 익셉션

 

리턴타입은 다음과 같다.

- ModelAndView (자동으로 설정됨)

- String(뷰이름)

- (@ResponseBody 애노테이션을 붙인 경우) 임의 객체

- ResponseEntity