ch14. MVC 4: 날짜 값 변환/@PathVariable/익셉션 처리

728x90
참고 도서 : 스프링5 프로그래밍 입문 - 최범균 저

ch14. MVC 4: 날짜 값 변환/@PathVariable/익셉션 처리 

1. [프로젝트 준비]

-11, 12, 13장의 에제형식 그대로 사용

2. [날짜를 이용한 회원 검색 기능] : 회원 가입 일자 기준으로 검색 기능 구현

[회원 가입 일자 기준 ->회원 검색 기능 구현]

 MemberDao.java // + selectByRegdate() 메소드 추가

 ListCommand.java //
커맨드 객체


 MemberListController.java //
컨트롤러 클래스


 memberList.jsp //
뷰 코드

<MemberDao .java> // selectByRegdate() 메소드 추가하기

public class MemberDao { //회원 정보 관리 클래스 
	//필드 
	private JdbcTemplate jdbcTemplate; 
                  //Jdbc템플릿 = DB 연동 후 쿼리 .query() 메소드 제공하여 편리한 쿼리 사용 O 
		... 
	//추가한 메소드 selectByRegdate() 는 특정 날짜 기간 동안 가입한 회원 목록 List 반환 메소드 

	public List<Member> selectByRegdate(LocalDateTime from, LocalDateTime to) { 
                                             //from ~ to 날짜 사이에 존재하는 Member 목록을 구함 
		List<Member> results = jdbcTemplate.query(
				"select * from MEMBER where REGDATE between ? and ? " +
						"order by REGDATE desc",
				memRowMapper,
				from, to);
		return results;
	}
		... 
}

<ListCommand. java> //뷰 코드에 입력된 데이터 타입 변환해서 받아올 커맨드 객체

-> 검색 폼(뷰)에 입력된 날짜 문자열을 처리하기 위해서는 LocalDateTime 타입 변환 필요

public class ListCommand { //뷰 코드 데이터를 타입 변환하여 내부 필드에 받는 커맨드 객체 
	//필드 
	@DateTimeFormat(pattern = "yyyyMMddHH")
	private LocalDateTime from;
	@DateTimeFormat(pattern = "yyyyMMddHH")
	private LocalDateTime to;

	public LocalDateTime getFrom() {
		return from;
	}

	public void setFrom(LocalDateTime from) {
		this.from = from;
	}

	public LocalDateTime getTo() {
		return to;
	}
	public void setTo(LocalDateTime to) {
		this.to = to;
	}
}
# 스프링은 String -> 기본 데이터 타입(Long/int ) 으로의 타입 변환은 기본 처리 O

               String -> LocalDateTime 타입 변환 : 추가 설정 필요
                                                       -> 뷰코드 데이터를 받아올 커맨드 객체의 필드에 @DateTimeFormat 적용
▶▶ @DateTimeFormat : 커맨드 객체 데이터 타입 변환 처리 역할
-커맨드 객체 필드에 적용할 경우,
이 애노테이션의 pattern 속성값에 지정한 형식을 이용하여 뷰 코드에 입력된 문자열을 LocalDatetime 타입으로 변환한다.

-이렇게 되면 컨트롤러 클래스에서 뷰 코드에 입력된 검색 요청을 처리할 때

                   따로 데이터 타입 변환 거치지 않고 커맨드 객체에서 변환된 데이터를 그대로 처리하면 된다. 

<MemberListController .java> //컨트롤러 클래스 (-> 새 컨트롤러는 빈 등록 필요)

@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);
                                 //뷰에 여기서 구한 Member 목록을 members 속성으로 전달함
		}
		return "member/memberList";
	}
}
-> 뷰 코드 폼에 사용자가 입력한 날짜 문자열 데이터는 커맨드 객체에서 LocalDateTime 타입으로 변환하여 받은 뒤
컨트롤러 클래스에서 커맨드 객체를 파라미터로 받는 구조이므로 해당 데이터를 요청에 맞게 내부적으로 처리할 수 있다
.

-> 컨트롤러 클래스 속 list() 메소드는 커맨드 객체로 받은 from, to 이용하여 해당 기간에 가입한 Member 목록을 구하여 반환하는데, 이와 동시에 Model 타입 model을 통해서 뷰에도 members 속성으로 Member 목록을 함께 전달한다.

<memberList.jsp> //뷰 코드 작성

-뷰 코드는 컨트롤러 클래스의 list()가 전달해준 members 속성을 이용해서 회원 목록을 출력하도록 폼을 다시 구현

<!DOCTYPE html>
<html>
<head>
    <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> 
                                 //이 pattern 형식에 맞춰서 해당 날짜 데이터 값을 출력한다. 
        </tr>
        </c:forEach>
    </table>
    </c:if>
</body>
</html>

3. [변환 에러 처리]

-지정 형식에 맞지 않은 값을 폼에 입력한 뒤 실행하면 기본적으로 400 에러가 발생한다.

-사용자 입장에선 400에러를 보는 것 보다 <<< 폼에 알맞은 에러 메시지를 보고 싶다.

                    -에러 코드 : Errors 객체에 typeMismatch 에러 코드가 적용됨
                    -에러 코드에 알맞은 에러 메시지를 보여주고 싶을 때,
                                    컨트롤러의 요청 매핑 메소드의 파라미터에 Errors 타입 파라미터를 추가하면 된다

                                  -> * 
, Errors 파라미터는 반드시 커맨드 파라미터 바로 뒤에 위치해야 함 (원칙)

                                  -> 교재 p.335 설명. (원칙 어기면 Exception 발생)

[메시지 프로퍼티 파일] <-해당 에러 코드 메시지 내용 작성

<label.properties> 파일

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

[뷰 코드] <- <form:errors> 태그 사용하여 해당 에러 메시지 내용 출력하는 코드 작성 추가

<memberList.jsp> 파일


4. [변환 처리에 대한 이해]

Q. @DateTimeFormat 애노테이션을 붙였을 때,
폼에 입력된 문자열을 누가 LocalDateTime 타입으로 변환해주는 건가 ?

                          = WebDateBinder

<WebDateBinder 의 역할>

값 타입 변환 관여

스프링 MVC는 요청 매핑 적용 메소드와 DispatcherServlet 사이 연결을 위해 RequestMappingHandlerAdapter 객체를 사용함


이 핸들러 어댑터 객체는
다시, 요청 파라미터와 커맨드 객체 사이의 타입 변환 처리를 위해
WebDateBinder를 이용함


WebDateBinder는 커맨드 객체를 생성하고,
커맨드 객체의 프로퍼티와 같은 이름을 갖는 요청 파라미터를 이용해서 프로퍼티 값을 생성함


, WebDateBinder가 직접 타입 변환 X
ConversionService에 타입 변환 역할을 위임 O

--> 그래서 커맨드 클래스에 @DateTimeFormat 적용하면 지정 형식 문자열 타입으로 데이터를 받을 수 있게 되는 것

<form:input> 태그에도 사용됨

-이 태그 path 속성에 지정한 프로퍼티 값을 String 값으로 변환하여 <input> 태그의 value 속성값으로 생성한다.


5. [중복 코드 정리 및 메소드 추가]

-객체 생성 코드가 중복이 생길 경우,

중복 제거를 위해 임의 객체를 필드로 할당한 뒤 해당 필드를 사용하여 간결하게 코드 작성O


6. [@PathVariable 이용 가변 경로 변수 처리]

ex. 형식 URL
http://localhost:8080/sp5-chap14/members/10

-이 형식 URL 사용하면 각 회원마다 “members/{id}”에서 id 이 달라진다.

                                       * 경로 변수 : {중괄호}로 둘러쌓인 부분

-요청 경로에서 {id}에 해당하는 경로 값은
                         해당 요청 매핑 적용 메소드의 
@PathVariable(“id”) 가 적용된 memID 
파라미터에 전달한다.

-이 경우 String으로 받았던 경로 값은 memID 해당 타입(Long)에 알맞게 변환
@Controller
public class MemberDetailController { //컨트롤러 클래스 
	private 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) {
			throw new MemberNotFoundException();
		}
		model.addAttribute("member", member);
		return "member/memberDetail";
	}

-> 이제 주소 입력할 때, @PathVariable을 통해 전달받은 경로 변수값을 이용해서 회원 정보를 읽어와 뷰에 결과를 전달할 수 있게 된다.


7. [컨트롤러 익셉션 처리]

컨트롤러 안에서 @ExceptionHandler 적용 메소드 구현

- 해당 컨트롤러 안에서 발생한 익셉션을 직접 처리함

각각의 @ExceptionHandler 메소드는 각 에러 내용 보여줄 뷰 이름 리턴함

컨트롤러 클래스에 @ExceptionHandler 적용 클래스

-해당 컨트롤러에서 발생한 익셉션만 처리함

컨트롤러 클래스에 @ControllerAdvice 적용 클래스

-여러 컨트롤러에서 동일 타입의 익셉션을 공통으로 처리함

-지정한 범위의 컨트롤러에 공통 사용될 설정 지정 가능

ex. @ControllerAdvice(“spring”)
//spring 패키지 및 그 하위 패키지에 속한 컨트로로러 클래스들에 공통 적용됨
<우선 순위> : @ExceptionHandler 적용 클래스 > @ControllerAdvice 적용 클래스

@ExceptionHandler 클래스 안에 있는 @ExceptionHandler 메소드를 우선 처리한다.

728x90