course 2021/Spring

Spring08 - 12/13

코딩하는토끼 2021. 12. 26. 05:24

이번에는 회원만 이용할 수 있는 게시판 만들기

1. 테이블 만들기

mysql 

+ inserted 컬럼 추가!

2. Mapper (interface, xml) 만들기 (new - interface, new - file)

MemberMapper.java

package org.zerock.mapper.project1;

import java.util.List;

import org.zerock.domain.project1.MemberVO;

public interface MemberMapper {
	
	public int insert(MemberVO member);
	
	public MemberVO select(String id);
	
	public int update(MemberVO member);
	
	public int delete(String id);
	
	public List<MemberVO> list();

}

MemberMapper.xml

<?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="org.zerock.mapper.project1.MemberMapper">
  
    <insert id="insert">
      INSERT INTO 
      Member (id, password, email, address) 
      VALUES
      (#{id}, #{password}, #{email}, #{address})
    </insert>
    
    <select id="select"
		resultType="org.zerock.domain.project1.MemberVO">
		SELECT id, password, email, address, inserted
		FROM Member
		WHERE id = #{id}
	</select>
	
	<update id="update">
		UPDATE Member
		SET
		password = #{password}, 
		email = #{email},
		address = #{address}
		WHERE id = #{id}
	</update>
	
	<delete id="delete">
		DELETE FROM Member WHERE id = #{id}
	</delete>
	
	<select id="list"
		resultType="org.zerock.domain.project1.MemberVO">
		SELECT
		id, password, email, address, inserted
		FROM Member
		ORDER BY inserted DESC
	</select>

(mapper.xml 파일 상단 코드는 BoardMapper.xml 파일에서 복붙_동일하므로)

MemberVO

package org.zerock.domain.project1;

import java.time.LocalDateTime;

import lombok.Data;

@Data
public class MemberVO {
	
	private String id;
	private String password;
	private String email;
	private String address;
	private LocalDateTime inserted;

}

3. test 파일 만들기

new - JUnit Test Case (name: MemberMapperTest)

* BoardMapperTest 에서 annotation 복붙

MemberMapperTest

package org.zerock.mapper.project1;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.zerock.domain.project1.MemberVO;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class MemberMapperTest {

	@Autowired
	public MemberMapper mapper;
	
	@Test
	public void insertTest() {
		MemberVO vo = new MemberVO();
		vo.setId("newMember1");
		vo.setPassword("newpassword");
		vo.setAddress("seoul");
		vo.setEmail("member1@gmail.com");
		
		int cnt = mapper.insert(vo);
		
		assertEquals(1,cnt);
	}
}

 

MemberMapperTest 수정

	@Test
	public void insertTest() {
		MemberVO vo = new MemberVO();
		vo.setId("newMember1" + (new Date()).getTime());
		vo.setPassword("newpassword");
		vo.setAddress("seoul");
		vo.setEmail("member1@gmail.com");
		
		int cnt = mapper.insert(vo);
		
		assertEquals(1,cnt);
	}

MemberMapperTest 수정

	@Test
	public void mapperTest() {
		String id = "member" + (new Date()).getTime();
		String password = "newpassword" +(new Date()).getTime();
		String address = "jeju" + (new Date()).getTime();
		String email = id + "@gmail.com";
		
		MemberVO vo = new MemberVO();
		vo.setId(id);
		vo.setPassword(password);
		vo.setAddress(address);
		vo.setEmail(email);
		
		// insert test
		int cnt = mapper.insert(vo);
		assertEquals(1,cnt);
		
		// select test
		MemberVO s = mapper.select(id);
		
		assertEquals(id, s.getId());
		assertEquals(password, s.getPassword());
		assertEquals(address, s.getAddress());
		assertEquals(email, s.getEmail());
		
		// update test
		String newPassword = "newPassword";
		String newAddress = "newAddress";
		LocalDateTime inserted = s.getInserted();
		
		s.setPassword(newPassword);
		s.setAddress(newAddress);
		
		cnt = mapper.update(s);
		assertEquals(1,cnt);
		
		MemberVO t = mapper.select(id);
		assertEquals(id, t.getId());
		assertEquals(newPassword, t.getPassword());
		assertEquals(newAddress, t.getAddress());
		assertEquals(email, t.getEmail());
		assertEquals(inserted, t.getInserted());
		
		// list test (지우기 전)
		List<MemberVO> list1 = mapper.list();
		int size1 = list1.size();
		for(MemberVO item : list1) {
			assertNotNull(item.getId());
			assertNotNull(item.getAddress());
			assertNotNull(item.getPassword());
			assertNotNull(item.getEmail());
			assertNotNull(item.getInserted());
		}
		
		// delete test
		cnt = mapper.delete(id);
		assertEquals(1, cnt);
		
		assertNull(mapper.select(id));
		
		// list test (지운 후)
		List<MemberVO> list2 = mapper.list();
		int size2 = list2.size();
		
		assertEquals(size1 - 1, size2);
		
	}

4. Service 만들기 (new - class)

MemberService

package org.zerock.service.project1;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.stereotype.Service;
import org.zerock.domain.project1.MemberVO;
import org.zerock.mapper.project1.MemberMapper;

import lombok.Setter;

@Service
public class MemberService {
	
	@Setter(onMethod_=@Autowired)
	private MemberMapper mapper;
	
	public MemberVO read(String id) {
		return mapper.select(id);
	}
	
	public boolean register(MemberVO member) {
		return mapper.insert(member) == 1;
	}
	
	public boolean modify(MemberVO member) {
		return mapper.update(member) == 1;
	}
	
	public boolean remove(String id) {
		return mapper.delete(id) == 1;
	}
	
	public List<MemberVO> getList() {
		return mapper.list();
	}
	
}

5. Controller 만들기 (new - class) 

MemberController

package org.zerock.controller.project1;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/member")
public class MemberController {
	
	@GetMapping("/signup")
	public void signup() {
		
	}
	

}

 

6. jsp 파일 만들기

views > member 안에 new - other - jsp 파일 생성

signup.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="b" tagdir="/WEB-INF/tags/board"%>
<% request.setCharacterEncoding("utf-8"); %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="<%= request.getContextPath() %>/resource/css/icon/css/all.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">

<title>Insert title here</title>
</head>
<body>
  <b:navBar></b:navBar>
  
  <!-- .container>.row>.col>h1{회원 가입} -->
  <div class="container">
    <div class="row">
      <div class="col">
        <h1>회원 가입</h1>

        <!-- form>.form-group*4>label[for=input$]+input.form-control#input$[required]^button.btn.btn-outline-primary{가입} -->
        <form method="post">
          <div class="form-group">
            <label for="input1">아이디</label>
            <input type="text" class="form-control" id="input1" required name="id">
          </div>
          <div class="form-group">
            <label for="input2">패스워드</label>
            <input type="password" class="form-control" id="input2" required name="password">
          </div>
          <div class="form-group">
            <label for="input3">이메일</label>
            <input type="email" class="form-control" id="input3" required name="email">
          </div>
          <div class="form-group">
            <label for="input4">주소</label>
            <input type="text" class="form-control" id="input4" required name="address">
          </div>
          <button class="btn btn-outline-primary">가입</button>
        </form>
      </div>
    </div>
  </div>


	<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
</body>
</html>

navBar.tag 수정

 

요청 : /board/list

/member/signup

 

MemberController 추가

	@Setter(onMethod_=@Autowired)
	private MemberService service;


	@PostMapping("/signup")
	public String signup(MemberVO member, RedirectAttributes rttr) {
		boolean ok = service.register(member);
		
		if(ok) {
			rttr.addFlashAttribute("result", "회원가입이 완료되었습니다.");
			return "redirect:/board/list";
		} else {
			return "redirect:/member/signup";
		}
	}

양식을 작성하고 가입하면 /board/list 로 리디렉트되고, 화면에 모달이 뜸

mysql 에서 가입한 정보가 추가된 것을 확인할 수 있음

 

만약, id (PRIMARY KEY) 가 중복되게 입력된다면? → 예외처리해주기

MemberController 수정

	@PostMapping("/signup")
	public String signup(MemberVO member, RedirectAttributes rttr, Model model) {
		
		MemberVO m = service.read(member.getId());
		
		if(m==null) {
		
			boolean ok = service.register(member);
		
			if(ok) {
				rttr.addFlashAttribute("result", "회원가입이 완료되었습니다.");
				return "redirect:/board/list";
			} else {
				return "redirect:/member/signup";
			}
		}else {
			model.addAttribute("alertMessage", "중복된 아이디입니다.");
			return null;
		}	
	}

signup.jsp 추가

https://getbootstrap.com/docs/4.6/components/alerts/

        <c:if test="${not empty alertMessage}">
          <div class="alert alert-warning">${alertMessage }</div>
        </c:if>

이미 있는 아이디를 입력하면 다음과 같이 표시됨

이 화면에서 기존에 입력한 정보가 입력창에서 사라지지 않고 유지되도록 하기 위해서 다음과 같은 작업을 함

MemberController 수정

	@PostMapping("/signup")
	public String signup(@ModelAttribute("member") MemberVO member, RedirectAttributes rttr, Model model) {
		
		MemberVO m = service.read(member.getId());
		
		if(m==null) {
		
			boolean ok = service.register(member);
		
			if(ok) {
				rttr.addFlashAttribute("result", "회원가입이 완료되었습니다.");
				return "redirect:/board/list";
			} else {
				return "redirect:/member/signup";
			}
		}else {
			model.addAttribute("alertMessage", "중복된 아이디입니다.");
			return null;
		}	
	}

@ModelAttribute("member")

signup.jsp 수정

        <form method="post">
          <div class="form-group">
            <label for="input1">아이디</label>
            <input type="text" class="form-control" id="input1" required name="id" value="${member.id }">
          </div>
          <div class="form-group">
            <label for="input2">패스워드</label>
            <input type="password" class="form-control" id="input2" required name="password" value="${member.password }">
          </div>
          <div class="form-group">
            <label for="input3">이메일</label>
            <input type="email" class="form-control" id="input3" required name="email" value="${member.email }">
          </div>
          <div class="form-group">
            <label for="input4">주소</label>
            <input type="text" class="form-control" id="input4" required name="address" value="${member.address }">
          </div>
          <button class="btn btn-outline-primary">가입</button>
        </form>

value attribute 추가 작성


로그인

navBar.tag 추가

<c:url value="/member/login" var="loginUrl"></c:url>

    <li class="nav-item active">
      <a class="nav-link" href="${loginUrl }">로그인</a> 
    </li>

 

MemberController 추가

	@GetMapping("/login")
	public void login() {
		
	}

포워딩하는 역할 뿐임

 

login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="b" tagdir="/WEB-INF/tags/board"%>
<% request.setCharacterEncoding("utf-8"); %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="<%= request.getContextPath() %>/resource/css/icon/css/all.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">

<title>Insert title here</title>
</head>
<body>
  <b:navBar></b:navBar>
  
 <div class="container">
	<div class="row">
		<div class="col">
			<h1>로그인</h1>
            <!-- form>.form-group*2>label[for=input$]+input.form-control[name][required]^button.btn.btn-outline-primary{로그인} -->
            <form method="post">
            	<div class="form-group">
            		<label for="input1">아이디</label>
            		<input type="text" class="form-control" name="id" required="">
            	</div>
            	<div class="form-group">
            		<label for="input2">패스워드</label>
            		<input type="password" class="form-control" name="password" required="">
            	</div>
            	<button class="btn btn-outline-primary">로그인</button>
            </form>
		</div>
	</div>
</div>

	<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
</body>
</html>

 

MemberController 추가

	@PostMapping("/login")
	public String login(String id, String password, HttpSession session) {
		// service 사용하여 아이디로 Member vo 얻고
		MemberVO vo = service.read(id);
		
		if(vo == null) {
			return null;
		}
		
		// 얻어온 Member vo 의 패스워드와 입력한 패스워드가 같은 지 확인
		boolean correctPassword = password.equals(vo.getPassword());
		
		
		// Member vo 가 null 이거나 패스워드가 일치하지 않으면 로그인 실패
		if(!correctPassword) {
			return null;
		}
		
		// Member vo 가 null 이 아니고 패스워드가 일치하면 로그인 성공
		session.setAttribute("loggedInMember", vo);
		return "redirect:/board/list";
	}

로그인 실패 시 같은 경로로 포워딩

로그인 성공 시 list 로 리디렉트 

session.setAttribute("loggedInMember", vo) - 로그인 상태


로그아웃

navBar.tag 수정

<c:url value="/member/logout" var="logoutUrl"></c:url>


    <c:if test="${empty sessionScope.loggedInMember }">
      <li class="nav-item active">
        <a class="nav-link" href="${signupUrl }">회원가입</a> 
      </li>
      <li class="nav-item active">
        <a class="nav-link" href="${loginUrl }">로그인</a> 
      </li>
    </c:if>
    
    <c:if test="${not empty sessionScope.loggedInMember }">
      <li class="nav-item active">
        <a class="nav-link" href="${logoutUrl }">로그아웃</a> 
      </li>
    </c:if>

로그인되지 않은 상태 → 회원가입, 로그인

로그인 된 상태 → 로그아웃

 

로그인 전

로그인 후

MemberController 추가

	@RequestMapping("/logout")
	public String logout(HttpSession session) {
		// 세션 invalidate
		session.invalidate();
		// /board/list redirect
		return "redirect:/board/list";
		
	}

회원정보보기

navBar.tag 추가

<c:url value="/member/info" var="memberInfoUrl"></c:url>


      <li class="nav-item active">
        <a class="nav-link" href="${memberInfoUrl }">회원정보보기 </a> 
      </li>

로그인 상태일 때만 표시되어야 하므로 로그아웃과 같이 위치

MemberController 추가

	@GetMapping("/info")
	public String info(HttpSession session) {
		MemberVO vo = (MemberVO) session.getAttribute("loggedInMember");
		
		// 로그아웃 상태 
		if(vo == null) {
			return "redirect"/member/login";
		}
		
		// 로그인 상태 
		return null;
	}

info.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="b" tagdir="/WEB-INF/tags/board"%>
<% request.setCharacterEncoding("utf-8"); %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="<%= request.getContextPath() %>/resource/css/icon/css/all.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">

<title>Insert title here</title>
</head>
<body>
  <b:navBar></b:navBar>
  
  <!-- .container>.row>.col>h1{회원정보} -->
  <div class="container">
    <div class="row">
      <div class="col">
        <h1>회원정보</h1>
        <!-- form>.form-group*4>label[for=input$]+input.form-control[name][value] -->
        <form method="post" id="infoForm">
          <div class="form-group">
            <label for="input1">아이디</label>
            <input type="text" required id="input1" class="form-control" name="id" value="${sessionScope.loggedInMember.id }" readonly>
          </div>
          <div class="form-group">
            <label for="input2">패스워드</label>
            <input type="password" required id="input2" class="form-control" name="password" value="${sessionScope.loggedInMember.password }">
          </div>
          <div class="form-group">
            <label for="input3">이메일</label>
            <input type="email" required id="input3" class="form-control" name="email" value="${sessionScope.loggedInMember.email }">
          </div>
          <div class="form-group">
            <label for="input4">주소</label>
            <input type="text" required id="input4" class="form-control" name="address" value="${sessionScope.loggedInMember.address }">
          </div>
          <!-- button.btn.btn-outline-secondary{수정}+button.btn.btn-outline-danger{삭제} -->
          <button class="btn btn-outline-secondary">수정</button>
          <button class="btn btn-outline-danger">삭제</button>
        </form>
      </div>
    </div>
  </div>
  
  
	<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
</body>
</html>

MemberController 추가

	@PostMapping("/info")
	public String info(MemberVO member, HttpSession session, RedirectAttributes rttr) {
		MemberVO vo = (MemberVO) session.getAttribute("loggedInMember");
		
		// 로그아웃 상태 
		if (vo == null) {
			return "redirect:/member/login";
		}
		
		// 로그인 상태 
		boolean ok = service.modify(member);
		
		if (ok) {
			rttr.addFlashAttribute("result", "회원정보가 변경되었습니다.");
			session.setAttribute("loggedInMember", service.read(member.getId()));
		} else {
			rttr.addFlashAttribute("result", "회원정보가 변경되지 않았습니다.");
		}
		
		return "redirect:/board/list";
	}

 

회원정보 수정 시, 정보가 수정되고 list 화면으로 리디렉트 되며 모달이 표시됨

 

삭제 시, 아직 삭제되지 않음 - 해결 ↓

info.jsp 추가

<script>
$(document).ready(function() {
    // 수정버튼 또는 삭제버튼 클릭 시 form의 action 속성값 변경
    const infoForm = $("#infoForm");
    const modifyButton = $("#modifyButton");
    const removeButton = $("#removeButton");

    modifyButton.click(function(e) {
      e.preventDefault();
      infoForm.attr("action", "");
      infoForm.submit();
    });

    removeButton.click(function(e) {
      e.preventDefault();
      if (confirm("탈퇴하시겠습니까?")) {
        infoForm.attr("action", "remove");
        infoForm.submit();
      }
    });
});
</script>

+ 수정 버튼에 id="modifyButton", 삭제 버튼에 id="removeButton" 작성

MemberController 추가

	@PostMapping("/remove") 
	public String remove(String id, HttpSession session, RedirectAttributes rttr) {
		MemberVO vo = (MemberVO) session.getAttribute("loggedInMember");
		
		// 로그아웃 상태
		if (vo == null) {
			return "redirect:/member/login";
		}
		
		// 로그인 상태
		service.remove(id);
		
		session.invalidate();
		
		rttr.addFlashAttribute("result", "회원 탈퇴하였습니다.");
		
		return "redirect:/board/list";
		
	}

 

삭제 시 회원탈퇴

 

계정 정보가 테이블에서 삭제됨