20220916_FRI
순서
- porm.xml파일에서 dependencies 태그 안에서 아무곳이나 오른쪽 버튼 클릭시, add starter클릭 > validation 검색 후 추가하기. next클릭시, 어느 곳에 추가할 지 위치 선택가능 finish 클릭하면 추가된다.
- 아래 태그가 추가된다.
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
- VO에서 어노테이션 추가한다.
@NotBlank(message = "id는 필수입력입니다.")
:null,""," " 모두 허용하지않는다.
- cf)
@Notnull
사용하지않은 이유는?
: 빈값과 공백값도 허용하기때문에 허용하지않도록 하기위해서- 3.컨트롤러에서
@valid
어노테이션과BindingResult bindingResult
,Model model
매개변수 추가한다.
BindingResult
: 검증 대상 객체(memberVO)와 검증 결과의 대한 정보를 담고 있는 객체. 검증객체 바로 다음에 선언되어야 한다.
:실제로 input태그에 입력한 데이터가 memberVO에 들어오는 걸 바인딩이라고하는데, 그 결과를 말한다.@Valid
: post로 전달된 데이터(memberVO)가 검증 규칙을 따르는지 판단하는 어노테이션 그래서, 검증할 객체가 valid 다음 바로 와야한다. 순서가 중요하다!!!
: 정규식 비밀번호 영어숫자 코드(암기할 필요x)
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,16}$"
링크 > https://tjddnjs625.tistory.com/28
실습내용
- 로그인 화면에도 validation
- 회원 가입 기능구현
- 회원가입 성공 후, 게시글 목록 페이지로 이동
- 로그인 기능구현(session기능 사용o + ajax 사용 x)
- 로그인 실패 시, 다시 로그인 페이지로 이동
(로그인 페이지로 되돌아 왔을때, id 및 pw는 input 태그에 그대로 남아있도록 기능 구현)- 로그인 성공 시, 게시글 목록페이지로 이동
import java.util.Iterator;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import groovy.transform.ToString;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@ToString
public class MemberVO {
//참고//
//@NotNull //javax로시작하는 어노테이션 사용 : null허용x / "", " "빈값,공백 허용가능
@NotBlank(message = "id는 필수입력입니다.") //null,""," " 모두 허용x
private String memberId;
//정규식 코드 사용해야한다.(복사붙여넣기)
// 8-16자리, 영어대소문자,특수문자포함(#은 포함시키지않음! 주의!)
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,16}$"
, message = "비밀번호 형식에 맞지 않습니다")
private String memberPw;
@NotBlank(message = "이름은 필수입력입니다.")
@Size(max = 5,message = "제한된 길이를 초과했습니다.")// max : 영어숫자 상관없이 최대문자길이 뜻함
private String memberName;
@Size(min = 9, max = 12)
private String memberTell;//01012345678
private String isAdmin;
private String memberStatus;
private String[] memberTells;//010,1234,5678
public String getMemberTell() {
/* return memberTells[0] + memberTells[1] + memberTells[2]; */
if (memberTells == null) {
return null;
}
else {// 연락처가 null이 아니라면, 배열에서 하나씩 값을 빼와서 문자열을 누적시켜서 result값으로 데이터 던진다
String result = "";
for(String tell : memberTells) {//010,1234,5678
result += tell;//01012345678
}
return result; //01012345678
}
}
}
<?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="memberMapper">
<!-- 패키지명 클래스명 -->
<resultMap type="kh.study.board.member.vo.MemberVO" id="selectMember">
<id column="MEMBER_ID" property="memberId"/>
<result column="MEMBER_PW" property="memberPw"/>
<result column="MEMBER_NAME" property="memberName"/>
<result column="MEMBER_TELL" property="memberTell"/>
<result column="IS_ADMIN" property="isAdmin"/>
<result column="MEMBER_STATUS" property="memberStatus"/>
</resultMap>
<!-- 회원가입 -->
<insert id="join">
INSERT INTO BOARD_MEMBER (
MEMBER_ID, MEMBER_PW, MEMBER_NAME, MEMBER_TELL,MEMBER_STATUS
) VALUES(
#{memberId},#{memberPw},#{memberName},#{memberTell},'ACTIVE'
)
</insert>
<!-- 로그인 -->
<select id="login" resultMap="selectMember">
SELECT MEMBER_ID , MEMBER_PW
FROM BOARD_MEMBER
WHERE MEMBER_ID = #{memberId}
and MEMBER_PW = #{memberPw}
</select>
</mapper>
package kh.study.board.member.service;
import kh.study.board.member.vo.MemberVO;
public interface MemberService {
void join(MemberVO memberVO);
MemberVO login(MemberVO memberVO);
}
package kh.study.board.member.service;
@Service("memberService")
public class MemberServiceImpl implements MemberService{
@Autowired//어노테이션으로 객체생성
private SqlSessionTemplate sqlSession;
//회원가입
@Override
public void join(MemberVO memberVO) {
sqlSession.insert("memberMapper.join", memberVO);
}
//로그인
@Override
public MemberVO login(MemberVO memberVO) {
return sqlSession.selectOne("memberMapper.login", memberVO);
}
}
@Controller
@RequestMapping("/board")
public class BoardController {
//목록페이지
@GetMapping("/list")
public String boardList() {
return "content/board/board_list";
}
@Controller
@RequestMapping("/member")
public class MemberController {
@Resource(name = "memberService")
private MemberService memberService;
// ---------------------- 커맨드객체 정의 -------------------------------------------//
// 커맨드객체는 html로 데이터를 전달하는 코드를 작성하지 않아도 자동으로 넘어간다.
// 이때 데이터가 넘어가는 이름은 클래스명에서 앞글자만 소문자로 변경된 이름으로 넘어간다.
//-----------------------------------------------------------------------------------//
// 회원가입
// a태그로 top파일에서 넘어올 때 get매핑
@GetMapping("/join") // a태그는 무조건 get 방식!!!(암기)
public String join(MemberVO memberVO) {
// 자동으로 커맨드 객체는 넘어간다.(memberVO) //커맨드객체에 아이디값 임의로 넣어주면, html로 넘어갈때 데이터 넘어감
System.out.println(memberVO);
return "content/member/join";
}
// 회원가입
// form태그로 top파일에서 넘어올 때 get매핑
// @Valid : post로 전달된 데이터가 검증 규칙을 따르는지 판단하는 어노테이션
// 해당 어노테이션 다음에는 반드시 검증할 객체가 valid 다음 바로 와야한다
// BindingResult :검증 대상 객체와 검증 결과의 대한 정보를 담고 있는 객체.
// 검증객체 바로 다음에 선언되어야 한다.
// 실제로 input태그에 입력한 데이터가 memberVO에 들어오는 걸 바인딩이라고하는데, 그 결과를 말함.
// model도 매개변수로 넣어준이유?
// : model 같이 매개변수 사용하면 데이터를 굳이 담지않아도(눈에보이지않아도) 바인딩결과의 오류여부정보와 검증한 객체memberVO 값을
// 자동으로 리턴값 join.html에 보내준다.
@PostMapping("/join")
public String joinProcess(@Valid MemberVO memberVO, BindingResult bindingResult, Model model) {
System.out.println("!!!!!!" + memberVO.getMemberTell()); // "010,1111,2222"
System.out.println("!!!!!!" + memberVO.getMemberPw()); // "010,1111,2222"
/*
* String[] tells = memberVO.getMemberTells(); for(String e : tells) {//리스트는
* 반복문으로 데이터꺼내기 System.out.println(e); }
*/
// 1) validation 체크 (데이터 유효성 검증)
if (bindingResult.hasErrors()) {// 바인딩하는데 오류가 생겼니?
System.out.println("error발생");
return "content/member/join";// 에러발생하면 join페이지로 다시 이동
}
// 2)회원가입 쿼리 실행
memberService.join(memberVO);
// 3)페이지이동
return "redirect:/board/list";
}
// 로그아웃
@GetMapping("/logout")
public String logout(HttpSession session) {
session.removeAttribute("loginInfo");
return "redirect:board/list";
}
// 로그인
// a태그로 top파일에서 넘어올 때
@GetMapping("/login") // a태그는 무조건 get 방식!!!(암기)
public String login(MemberVO memberVO) {
System.out.println("로그인 a태그로 클릭해서 넘어왔다!!!!!!!!!!!!!!!!!!");
return "content/member/login";
}
// 로그인
// form태그로 top파일에서 넘어올 때
@PostMapping("/login")//a태그는 무조건 get 방식!!!(암기)
public String loginProcess(HttpSession session, MemberVO memberVO
, Model model) {
//!!로그인은 유효성검사 필요없다
//로그인 쿼리 실행
MemberVO loginInfo = memberService.login(memberVO);
System.out.println("넘어왔다");
if (loginInfo != null) {
session.setAttribute("loginInfo", loginInfo);
}
else {
System.out.println("로그인실패!!!!!!!!!!!!!!!!!!!!!!!!!!");
return "redirect:/member/login";
}
return "redirect:/board/list";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div th:fragment="topFragment">
<!-- 로그인 클릭시 로그인html파일 이동방법 -->
<!-- 1번 <a href="/board/login">login</a> -->
<!-- 2번 <a th:href="@{/board/login}"> login</a><!-- 타임리프 경로이동시!!! 골뱅이+중괄호 -->
<div class="row">
<div class="col text-end" style="color:#224B0C;">
<th:block th:if="${session.loginInfo == null}">
<a th:href="@{/member/login}">login</a><!-- a태그 -> get방식!!! -->
<a th:href="@{/member/join}">join</a><!-- a태그 -> get방식!!! -->
<!-- <span th:onclick="location.href='/board/join';"></span> -->
</th:block>
<th:block th:unless="${session.loginInfo == null}">
<a th:href="@{/member/login}">
[[${session.loginInfo.memberName}]]님 반갑습니다✔✔ </a><!-- a태그 -> get방식!!! -->
<a th:href="@{/member/logout}">LOGOUT</a><!-- a태그 -> get방식!!! -->
</th:block>
</div>
</div>
<div class="row">
<div class="col text-center" style="background-color: #EEF2E6;">
<span style="font-weight: bold; font-size: 50px; color: #3D8361;">Board</span>
</div>
</div>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}">
<!-- th 타임리프사용하려면 경로이동은!!! 골뱅이+{}사용하기!!! -->
<!-- th:object="${memberVO}" 는 데이터받기위해 사용 : 컨트롤러에서 보낸 객체 받아오기!!!
커맨드객체 memberVO는 자동으로 데이터 가져와서 사용가능하다 -->
<!-- 단, th:field는 th:object와 반드시 함께 사용하는 애트리뷰트 -->
<!-- formx태그에 th:object로 넘어오는 커맨드객체를 지정할 경우
해당 폼 태그 내에서 th:field 속성으로 자동으로 id,name,value 값을 세팅할 수 있다. -->
<!-- 개발자모드 콘솔을 보면 th:feild속성값만으로 name,id,value값 모두 확인가능하다!!! -->
<!-- th:field 받지않고 원래 사용하던 방법 -->
<!-- id :<input type="text" name="memberId" id="memberId"> -->
<!-- th:field 받아와서 사용하는 방법 -->
<!-- *{memberId}라는 값으로 들어오는 데이터 받아와서 사용하겠다. -->
<div layout:fragment="content">
<!-- row justify-content-center : 가운데 정렬 -->
<div class="row justify-content-center">
<div class="col-6">
<form th:action="@{/member/join}" class="row g-3" method="post" th:object="${memberVO}">
<div class="col-12">
<label for="memberId" class="form-label"> ID </label>
<input type="text" class="form-control" th:field="*{memberId}">
<!-- 이 html파일에 위의 object의 해당하는 memberVO라는 객체를 던져줬기때문에 에러발생있는지 알수있는 것이다. -->
<!-- field 는 변수를 의미한다.-->
<!-- * 와 {} 표기 : object의 객체(memberVO에 담긴 데이터)로 인식한다. -->
<div class="hasError" th:if="${#fields.hasErrors('memberId')}" th:errors="*{memberId}"></div>
</div>
<div class="col-12">
<label for="memberPw" class="form-label">PASSWORD</label>
<input type="password" class="f orm-control" th:field="*{memberPw}">
<div class="hasError" th:if="${#fields.hasErrors('memberPw')}" th:errors="*{memberPw}"></div>
</div>
<div class="col-12">
<label for="memberName" class="form-label">NAME</label>
<input type="text" class="form-control" th:field="*{memberName}">
<div class="hasError" th:if="${#fields.hasErrors('memberName')}" th:errors="*{memberName}"></div>
</div>
<div class="col-4">
<!-- 연락처는 타임리프 사용하면 에러난다!!! -->
<label for="memberTells" class="form-label">TELL</label>
<select name="memberTells" class="form-select" >
<option value="010" selected>010</option>
<option value="">011</option>
</select>
</div>
<div class="col-4">
<label for="" class="form-label"> </label>
<input type="text" class="form-control" name="memberTells">
</div>
<div class="col-4">
<label for="" class="form-label"> </label>
<input type="text" class="form-control" name="memberTells">
</div>
<!-- div태그 밖으로 빼놓고 데이터를 memberTell 로 넣어준다 (memberTells 아님!! 주의) -->
<div class="hasError" th:if="${#fields.hasErrors('memberTell')}" th:errors="*{memberTell}"></div>
<div class="col-12 d-grid gap-2">
<button type="submit" class="btn btn-primary">JOIN</button>
</div>
</form>
</div>
</div>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}">
<div layout:fragment="content">
<div class="row justify-content-center">
<div class="col-6">
<form th:action="@{/member/login}" class="row g-3" method="post" th:object="${memberVO}">
<div class="col-12">
<label for="memberId" class="form-label" >ID</label>
<input type="text" class="form-label" name="memberId" th:text="${memberVO.memberId}" th:value="${memberVO.memberId}" >
<!-- value값에 ${memberVO.memberId} 가능 -->
</div>
<div class="col-12">
<label for="memberPw" class="form-label" >PW</label>
<input type="password" class="form-label" name="memberPw" >
</div>
<div class="col-12 d-grid gap-2">
<button type="submit" class="btn btn=primary">로그인</button>
<button type="button" class="btn btn=primary" onclick="location.href='/member/join';" >회원가입</button>
</div>
</form>
</div>
</div>
</div>
</html>