FreeBoard에 적용해봤다면, Member에도 적용해보려고 한다.
그런데 아직 배우지는 않았지만 사실 localhost:8080/study4/ member/* 에 해당하는 url은 특정권한을 가진 MANAGER만 접속할 수 있게 하려고 한다. memberList게시판같은 경우는 사실 다른 회원의 정보를 볼 수 있으므로
일반 회원은 접근 할 수 없다. 그래서 memberModify.wow, memberRegist.wow url에 validation을 적용하기 보단
실제로 일반 회원이 회원가입하는 과정과 자기 정보를 수정하는 페이지를 만들어보고 거기에 validation을 적용해보자.
https://drive.google.com/drive/folders/1Ipx2E34o3D0xER--DolOR3g7Xnv4VRsm에서 study4_2에 있는
step1,2,3 jsp 파일을 받아보자. 각각의 화면은 다음과 같다.
MemberVO에 이용약관 동의,개인정보동의,이벤트 동의에 대한 필드를 추가해주자.
각각 agreeYn, privacyYn, eventYn이라고 하겠다. (getter/setter는 알아서 만들어주십쇼)
step2에서는 step1에서 입력한 필수요소만 검사해야하고,
step3에서는 step2에서 입력한 ID,비밀번호,회원명,이메일만을 검사해야하고
regist에서는 step3에서 입력한 생일,우편번호,주소1,2, 핸드폰,직업,취미만 검사해야한다.
즉, 각각의 step에 대해서 따로 validation grouping을 해줘야 한다.
이를 위해 Step1 2 3 interface를 만들자. (예시는 1만했는데 Step2, Step3 만들어주세요. java파일이니까 대문자시작)
package com.study.common.valid;
public interface Step1 {
}
이제 validation을 적용해보자.
MemberVO.java
public class MemberVO {
@NotEmpty(groups = {Step2.class},message = "id는 필수입니다.")
private String memId; /* 회원 아이디 */
@NotEmpty(groups = {Step2.class}, message = "비밀번호는 필수입니다.")
private String memPass; /* 회원 비밀번호 */
@NotEmpty(groups= {Step2.class}, message = "이름은 필수입니다")
private String memName; /* 회원 이름 */
@NotEmpty(groups = {Step2.class},message = "이메일은 필수입니다.")
@Email(groups = {Step2.class}, message = "이메일형식에 맞춰주세요" )
private String memMail; /* 이메일 */
@NotEmpty(groups = {Step3.class}, message = "생일은 필수입니다.")
private String memBir; /* 회원 생일 */
@NotEmpty(groups = {Step3.class}, message = "우편번호는 필수입니다.")
private String memZip; /* 우편번호 */
@NotEmpty(groups = {Step3.class}, message = "주소는 필수입니다.")
private String memAdd1; /* 주소 */
@NotEmpty(groups = {Step3.class}, message = "상세주소는 필수입니다.")
private String memAdd2; /* 상세주소 */
@NotEmpty(groups = {Step3.class},message = "연락처는 필수입니다.")
@Pattern(groups = {Step3.class}
,regexp = "^\\d{3}-\\d{3,4}-\\d{4}$"
,message = "연락처형태가 다릅니다")
private String memHp; /* 연락처 */
@NotEmpty(groups = {Step3.class}, message = "직업은 필수입니다.")
private String memJob; /* 직업 코드 */
@NotEmpty(groups = {Step3.class}, message = "취미는 필수입니다.")
private String memHobby; /* 취미 코드 */
private int memMileage; /* 마일리지 */
private String memDelYn; /* 탈퇴여부 */
private String memJobNm;
private String memHobbyNm;
//join
@NotEmpty(groups = {Step1.class}, message ="이용약관 동의는 필수입니다." )
private String agreeYn;
@NotEmpty(groups = {Step1.class}, message ="개인정보이용 동의는 필수입니다." )
private String privacyYn;
private String eventYn;
// DB에 저장, Y이면 가끔 문자 보내주고 N이면 문자 안보내고.. 근데 난 안함
MemberJoinController를 따로 만들어보자. package는 com.study.member.web
MemberJoinController.java
package com.study.member.web;
import java.util.List;
import javax.inject.Inject;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import com.study.code.service.ICommCodeService;
import com.study.code.vo.CodeVO;
import com.study.common.valid.Step1;
import com.study.common.valid.Step2;
import com.study.common.valid.Step3;
import com.study.common.vo.ResultMessageVO;
import com.study.exception.BizDuplicateKeyException;
import com.study.exception.BizNotEffectedException;
import com.study.member.service.IMemberService;
import com.study.member.vo.MemberVO;
@Controller
public class MemberJoinController{
@Inject
IMemberService memberService;
@Inject
ICommCodeService codeService;
@ModelAttribute("jobList")
public List<CodeVO> jobList() {
return codeService.getCodeListByParent("JB00");
}
@ModelAttribute("hobbyList")
public List<CodeVO> hobbyList() {
return codeService.getCodeListByParent("HB00");
}
@RequestMapping("/join/step1.wow")
public String step1(@ModelAttribute("member") MemberVO member) {
return "join/step1";
}
@RequestMapping("/join/step2.wow")
public String step2(@ModelAttribute("member")
@Validated(value = {Step1.class}) MemberVO member
,BindingResult error) {
if(error.hasErrors()) {
return "join/step1";
}
return "join/step2";
}
@RequestMapping("/join/step3.wow")
public String step3(@ModelAttribute("member")
@Validated(value = {Step2.class}) MemberVO member
,BindingResult error) {
if(error.hasErrors()) {
return "join/step2";
}
return "join/step3";
}
@RequestMapping("/join/regist.wow")
public String regist(Model model,@ModelAttribute("member")
@Validated(value = {Step3.class}) MemberVO member
,BindingResult error){
if(error.hasErrors()) {
return "join/step3";
}
System.out.println(member);
//검사통과 => insert하면 됩니다.
try {
memberService.registMember(member);
ResultMessageVO resultMessageVO = new ResultMessageVO();
resultMessageVO.messageSetting(true, "회원 등록 성공 ", "회원을 등록했습니다.", "/member/memberList.wow", "목록으로");
model.addAttribute("resultMessageVO", resultMessageVO);
return "common/message";
} catch (BizNotEffectedException ene) {
ResultMessageVO resultMessageVO = new ResultMessageVO();
resultMessageVO.messageSetting(false, "회원 삭제 실패", "회원을 삭제하는데 실패했습니다.", "/member/memberList.wow", "목록으로");
model.addAttribute("resultMessageVO", resultMessageVO);
return "common/message";
} catch (BizDuplicateKeyException ede) {
ResultMessageVO resultMessageVO = new ResultMessageVO();
resultMessageVO.messageSetting(false, "회원 등록 실패", "회원아이디가 이미 존재합니다.", "/member/memberList.wow", "목록으로");
model.addAttribute("resultMessageVO", resultMessageVO);
return "common/message";
}
}
}
마지막으로 step1,2,3.jsp를 다음과 같이 <form:form> 태그를 적용해봅시다.
step1.jsp
<%@ 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="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<%@include file="/WEB-INF/inc/header.jsp"%>
<title>회원가입 1단계</title>
</head>
<body>
<%@include file="/WEB-INF/inc/top.jsp"%>
<div class="container">
<form:form modelAttribute="member" method="post" action="step2.wow">
<div class="row col-md-8 col-md-offset-2">
<div class="page-header">
<h3>회원가입 1단계</h3>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<label> 이용약관 동의 <span class="required">(필수)</span>
<form:checkbox path="agreeYn" value="Y"/>
<form:errors path="agreeYn"/>
</label>
</div>
<div class="panel-body">
<p>Study 사이트에 오신것을 환영합니다.
<p>Study 서비스 및 제품(이하 ‘서비스’)을 이용해 주셔서 감사합니다.
<p>본 약관은 다양한 서비스의 이용과 관련하여 본 사이트 서비스 회원(이하 ‘회원’) 또는 비회원과의 관계를
설명하며, 아울러 여러분의 네이버 서비스 이용에 도움이 될 수 있는 유익한 정보를 포함하고 있습니다.
<p>서비스를 이용하시거나 회원으로 가입하실 경우 여러분은 본 약관 및 관련 운영 정책을 확인하거나 동의하게
되므로, 잠시 시간을 내시어 주의 깊게 살펴봐 주시기 바랍니다.
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<label> 개인정보 수집 및 이용에 대한 동의 <span class="required">(필수)</span>
<form:checkbox path="privacyYn" value="Y"/>
<form:errors path="privacyYn"/>
</label>
</div>
<div class="panel-body">
<p>정보통신망법 규정에 따라 회원가입 신청하시는 분께 수집하는 개인정보에 대하여 알려드립니다.
<p>1. 개인정보의 수집 항목
<p>2. 개인정보의 수집 및 이용목적
<p>3. 개인정보의 보유 및 이용기간
<p>위 사항을 안내 드리오니 자세히 읽은 후 동의하여 주시기 바랍니다.
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<label> 이벤트 등 프로모션 알림 메일 수신 <span class="optional">(선택)</span>
<form:checkbox path="eventYn" value="Y"/>
<form:errors path="eventYn"/>
</label>
</div>
<div class="panel-body">
Study가 제공하는 이벤트/프로모션 알림 정보를 메일로 받습니다.
<p>Study가 제공하는 이벤트/프로모션 알림 정보를 메일로 받습니다.
<p>1. 본 사이트가 직접 운영하는 통합서비스들의 소식과 혜택, 쿠폰 등 유용한 정보를 메일로 제공합니다.
<p>2. 추후 새로운 서비스가 추가될 경우, 본 동의를 한 사용자에게 자동으로 해당 서비스의 메일을 수신할 수
있게 됩니다.
<p>3. 만약 사용자가 앞으로 이벤트/ 프로모션 알림 메일을 받고 싶지 않을 경우, [마이페이지 > 설정 >
내 개인정보 관리] 본 동의를 철회할 수 있습니다.
</div>
</div>
</div>
<div class="row panel panel-default">
<div class="panel-body">
<div class="pull-left">
<a href="${pageContext.request.contextPath}/join/cancel"
class="btn btn-sm btn-default"> <span
class="glyphicon glyphicon-remove" aria-hidden="true"></span>
취 소
</a>
</div>
<div class="pull-right">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-chevron-right"
aria-hidden="true"></span> 다 음
</button>
</div>
</div>
</div>
</form:form>
</div>
<!-- END : 메인 콘텐츠 컨테이너 -->
</body>
</html>
step2.jsp
<%@ 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="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<%@include file="/WEB-INF/inc/header.jsp"%>
<title>회원가입 2단계</title>
</head>
<body>
<%@include file="/WEB-INF/inc/top.jsp"%>
<div class="container">
<form:form modelAttribute="member" action="step3.wow" method="post">
<div class="row col-md-8 col-md-offset-2">
<div class="page-header">
<h3>회원가입 2단계</h3>
</div>
<table class="table" >
<colgroup>
<col width="20%" />
<col />
</colgroup>
<tr>
<th>ID</th>
<td>
<form:input path="memId" cssClass="form-control input-sm"/>
<form:errors path="memId"/>
</td>
</tr>
<tr>
<th>비밀번호</th>
<td>
<form:password path="memPass" cssClass="form-control input-sm"/>
<form:errors path="memPass"/>
</td>
</tr>
<tr class="form-group-sm">
<th>회원명</th>
<td>
<form:input path="memName" cssClass="form-control input-sm"/>
<form:errors path="memName"/>
</td>
</tr>
<tr class="form-group-sm">
<th>이메일</th>
<td>
<form:input path="memMail" cssClass="form-control input-sm"/>
<form:errors path="memMail"/>
</td>
</tr>
<tr>
<td colspan="2">
<div class="pull-left" >
<a href="cancel" class="btn btn-sm btn-default" >
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
취 소
</a>
</div>
<div class="pull-right">
<button type="submit" class="btn btn-sm btn-primary" >
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
다 음
</button>
</div>
</td>
</tr>
</table>
</div>
</form:form>
</div> <!-- END : 메인 콘텐츠 컨테이너 -->
</body>
</html>
step3.jsp
<%@ 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="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<%@include file="/WEB-INF/inc/header.jsp"%>
<title>회원가입 3단계</title>
</head>
<body>
<%@include file="/WEB-INF/inc/top.jsp"%>
<div class="container">
<form:form modelAttribute="member" method="post" action="regist.wow">
<div class="row col-md-8 col-md-offset-2">
<div class="page-header">
<h3>회원가입 3단계</h3>
</div>
<table class="table">
<tr class="form-group-sm">
<th>생일</th>
<td>
<input type="date" name="memBir"
class="form-control input-sm" value='${member.memBir }'>
<form:errors path="memBir" />
</td>
</tr>
<tr class="form-group-sm">
<th>우편번호</th>
<td>
<form:input path="memZip" cssClass="form-control input-sm"/>
<form:errors path="memZip"/>
</td>
</tr>
<tr class="form-group-sm">
<th>주소</th>
<td>
<form:input path="memAdd1" cssClass="form-control input-sm"/>
<form:errors path="memAdd1"/>
<form:input path="memAdd2" cssClass="form-control input-sm"/>
<form:errors path="memAdd2"/>
</td>
</tr>
<tr class="form-group-sm">
<th>핸드폰</th>
<td>
<form:input path="memHp" cssClass="form-control input-sm"/>
<form:errors path="memHp"/>
</td>
</tr>
<tr>
<th>직업</th>
<td>
<div class="form-group-sm">
<form:select path="memJob">
<form:option value="">--직업선택--</form:option>
<form:options items="${jobList}"
itemLabel="commNm" itemValue="commCd"/>
</form:select>
<form:errors path="memJob"/>
</div>
</td>
</tr>
<tr>
<th>취미</th>
<td class="form-group-sm">
<form:select path="memHobby">
<form:option value="">--직업선택--</form:option>
<form:options items="${hobbyList}"
itemLabel="commNm" itemValue="commCd"/>
</form:select>
<form:errors path="memHobby"/>
</td>
</tr>
<tr>
<td colspan="2">
<div class="pull-left">
<a href="${pageContext.request.contextPath}/join/cancel"
class="btn btn-sm btn-default"> <span
class="glyphicon glyphicon-remove" aria-hidden="true"></span>
취 소
</a>
</div>
<div class="pull-right">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-chevron-right"
aria-hidden="true"></span> 다 음
</button>
</div>
</td>
</tr>
</table>
</div>
</form:form>
</div>
</body>
</html>
이렇게 했을 때 step1,2,3를 무사히 통과해 regist메소드까지 와서 DB에 insert 잘 될 거 같다.
하지만 regist에서 MemberVO의 값을 보면 step1,2에서 사용자가 입력한 값은 없고,
step3에서 입력한 값들만 있다.
MemberJoinController의 regist메소드
@RequestMapping("/join/regist.wow")
public String regist(Model model,@ModelAttribute("member")
@Validated(value = {Step3.class}) MemberVO member
,BindingResult error){
if(error.hasErrors()) {
return "join/step3";
}
//검사통과 => insert하면 됩니다.
System.out.println(member);
try {
memberService.registMember(member);
콘솔을 보면 실제 MemberVO member의 값에는 step1,2에서 입력한 값이 Null이다
regist.wow 요청했을 때 memberService.registMember(member)에서 에러가난다.
DB에 넣을 때 Null이면 안되는 값에 Null이 들어가기 때문이다.
step1~regist까지 모든 메소드에서 @ModelAttribute("member")MemberVO member를 쓰지만
이 때 생성되는 MemberVO는 그 때 그 때 새로 생성한다. model은 한번의 하나에 요청을 처리하는데 사용하기 때문에
step1요청, step2요청 , step3 요청, regist 요청이 올때마다 MemberVO 객체를 새로 생성한다.
즉 우리는 MemberVO 값이 유지되도록 해야한다. 이 때 사용하는 것이 @SessionAtrributes이다
@SessionAttributes
@SessionAttributes는 class(컨트롤러) 에다가 적용한다.
MemberJoinController에 @SessionAttributes("member")를 붙이자.
@Controller
@SessionAttributes("member")
public class MemberJoinController {
참고로 @SessionAttributes 를 이용해 만든 세션은 HttpSession과 달리 해당 컨트롤러에서만 유지된다.
@SessionAttributes가 하는 역할은 다음과 같다.
- 컨트롤러 메소드가 생성하는 모델 정보 중에서 @SessionAttributes에 지정한 이름과
동일한 이름이 있다면 세션에 해당 모델을 저장한다. - 해당 클래스에서 사용 되는 @ModelAttribute는 세션의 같은 이름의 객체가 있는지 확인
있으면 객체를 새로 만들지 않고 세션에 저장된 객체 사용( 값이 유지됨)
없으면 에러. - 근데 맨처음 step1을 요청할 때는 당연히 없기 때문에 에러가 난다.
그래서 step1 요청메소드 실행전에 model이 session에 담기도록
@ModelAttribute 메소드를 만들어줘야 된다.
@ModelAttribute("member")
public MemberVO member() {
return new MemberVO();
}
여기까지 했다면 regist.wow까지 요청이 왔을 때 MemberVO member에는 step1,2,3에서 입력한 값이 다 저장된다.
마무리 작업으로는 Session에 값이 계속 유지되면 안된다. regist에서 session에 있는 값 초기화해야되고,
또 도중에 취소버튼 눌렀을 때 도 session에 있는 값 초기화해야된다.
SessionStatus를 이용해서 삭제해주면 된다. sessionStatus.setComplete()를 하면 session이 초기화된다.
(HttpSession은 초기화 안됩니다.)
regist메소드
@RequestMapping("/join/regist.wow")
public String regist(Model model,@ModelAttribute("member")
@Validated(value = {Step3.class}) MemberVO member
,BindingResult error, SessionStatus sessionStatus){
if(error.hasErrors()) {
return "join/step3";
}
//검사통과 => insert하면 됩니다.
System.out.println(member);
try {
memberService.registMember(member);
sessionStatus.setComplete();
cancel메소드
@RequestMapping("/join/cancel")
public String cancel(SessionStatus sessionStatus) {
sessionStatus.setComplete();
return "home";
}
-----------------------------------------------------------------------------------------------------------------------------------
여기까지 회원가입에 대해 해봤다. 이제 일반회원이 자신의 회원정보를 수정할 때 validation을 적용해보자.
딱히 건들곳이 없다.
MypageController의 modify메소드. @Validated 추가 후 Modify.class 그룹핑
@RequestMapping("/mypage/modify.wow")
public String modify(Model model, HttpSession session, HttpServletRequest req,
@ModelAttribute("member") @Validated(value = {Modify.class}) MemberVO member
,BindingResult error) {
if(error.hasErrors()) {
return "mypage/edit";
}
UserVO user = (UserVO) session.getAttribute("USER_INFO");
if(user==null) {
return "redirect:"+req.getContextPath()+"/login/login.wow";
}
try {
memberService.modifyMember(member);
MemberVO. 수정할 때 검사 필요한 곳에다 Modify.class 그룹핑
package com.study.member.vo;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import javax.xml.ws.BindingType;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.study.common.valid.Modify;
import com.study.common.valid.Step1;
import com.study.common.valid.Step2;
import com.study.common.valid.Step3;
public class MemberVO {
@NotEmpty(groups = {Modify.class,Step2.class},message = "id는 필수입니다.")
private String memId; /* 회원 아이디 */
@NotEmpty(groups = {Modify.class,Step2.class}, message = "비밀번호는 필수입니다.")
private String memPass; /* 회원 비밀번호 */
@NotEmpty(groups= {Modify.class,Step2.class}, message = "이름은 필수입니다")
private String memName; /* 회원 이름 */
@NotEmpty(groups = {Modify.class,Step2.class},message = "이메일은 필수입니다.")
@Email(groups = {Modify.class,Step2.class}, message = "이메일형식에 맞춰주세요" )
private String memMail; /* 이메일 */
@NotEmpty(groups = {Modify.class,Step3.class}, message = "생일은 필수입니다.")
private String memBir; /* 회원 생일 */
@NotEmpty(groups = {Modify.class,Step3.class}, message = "우편번호는 필수입니다.")
private String memZip; /* 우편번호 */
@NotEmpty(groups = {Modify.class,Step3.class}, message = "주소는 필수입니다.")
private String memAdd1; /* 주소 */
@NotEmpty(groups = {Modify.class,Step3.class}, message = "상세주소는 필수입니다.")
private String memAdd2; /* 상세주소 */
@NotEmpty(groups = {Modify.class,Step3.class},message = "연락처는 필수입니다.")
@Pattern(groups = {Modify.class,Step3.class}
,regexp = "^\\d{3}-\\d{3,4}-\\d{4}$"
,message = "연락처형태가 다릅니다")
private String memHp; /* 연락처 */
@NotEmpty(groups = {Modify.class,Step3.class}, message = "직업은 필수입니다.")
private String memJob; /* 직업 코드 */
@NotEmpty(groups = {Modify.class,Step3.class}, message = "취미는 필수입니다.")
private String memHobby; /* 취미 코드 */
private int memMileage; /* 마일리지 */
private String memDelYn; /* 탈퇴여부 */
private String memJobNm;
private String memHobbyNm;
//join
@NotEmpty(groups = {Step1.class}, message ="이용약관 동의는 필수입니다." )
private String agreeYn;
@NotEmpty(groups = {Step1.class}, message ="개인정보이용 동의는 필수입니다." )
private String privacyYn;
private String eventYn;
// DB에 저장, Y이면 가끔 문자 보내주고 N이면 문자 안보내고.. 근데 난 안함
}
끝~~