마이 페이지 - 내 정보 수정 기능 구현 (23.08.14)

·2023년 8월 14일
0

Spring

목록 보기
7/36
post-thumbnail

🌷 내 정보 수정


👀 코드로 살펴보기

🌼 VS Code

🌱 main.jsp

...
                    <%-- 로그인 되었을 때 --%>
                    <c:otherwise>
                        <article class="login-area">

                            <a href="#">
                                <img src="/resources/images/user.png" id="memberProfile">
                            </a>

                            <div class="my-info">
                                <div>
                                    <a href="/myPage/info" id="nickname">${sessionScope.loginMember.memberNickname}</a>

                                    <a href="/member/logout" id="logoutBtn">로그아웃</a>
                                </div>   

                                <p>${loginMember.memberEmail}</p>

                            </div>

                        
                        </article>
                    </c:otherwise>
...

🌱 myPage-info.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"%>

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>마이페이지</title>

    <link rel="stylesheet" href="/resources/css/myPage/myPage-style.css">

</head>
<body>
    <main>
        <jsp:include page="/WEB-INF/views/common/header.jsp" />

        <section class="myPage-content">

            <jsp:include page="/WEB-INF/views/myPage/sideMenu.jsp" />

            <section class="myPage-main">

                <h1 class="myPage-title">내 정보</h1>
                <span class="myPage-subject">원하는 회원 정보를 수정할 수 있습니다.</span>

                <%-- 현재 페이지 : http://localhost/myPage/info
                    제일 뒤에 info 지우고
                    action에 작성된 경로 추가
                --%>
                
                <%-- 상대 경로 --%>
                <form action="info" method="POST" name="myPageFrm">

                    <div class="myPage-row">
                        <label>닉네임</label>
                        <input type="text" name="memberNickname"  maxlength="10" 
                            value="${loginMember.memberNickname}">
                    </div>

                    <div class="myPage-row">
                        <label>전화번호</label>
                        <input type="text" name="memberTel"  maxlength="11" 
                            value="${loginMember.memberTel}">
                    </div>

                    <div class="myPage-row info-title">
                        <span>주소</span>
                    </div>

                    <%-- ${fn:split(loginMember.memberAddress, '^^^')[0]}
                    ${fn:split(loginMember.memberAddress, '^^^')[1]}
                    ${fn:split(loginMember.memberAddress, '^^^')[2]} --%>

                    <%--
                        ${fn:split(문자열, 구분자)}
                        문자열을 구분자로 나누어 배열 형태로 반환
                    --%>

                    <c:set var="addr" value="${fn:split(loginMember.memberAddress,'^^^')}"/>

                    <div class="myPage-row info-address">
                        <input type="text" name="memberAddress" placeholder="우편번호" value="${addr[0]}" id="sample6_postcode">
                        <button type="button" onclick="sample6_execDaumPostcode()">검색</button>
                    </div>

                    <div class="myPage-row info-address">
                        <input type="text" name="memberAddress"  placeholder="도로명/지번 주소" value="${addr[1]}" id="sample6_address">                
                    </div>

                    <div class="myPage-row info-address">
                        <input type="text" name="memberAddress"  placeholder="상세 주소" value="${addr[2]}" id="sample6_detailAddress">                
                    </div>

                    <button class="myPage-submit">수정하기</button>
                </form>

            </section>

        </section>

    </main>
    <jsp:include page="/WEB-INF/views/common/footer.jsp" />

    <script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
    <script>
        function sample6_execDaumPostcode() {
            new daum.Postcode({
                oncomplete: function(data) {
                    // 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.

                    // 각 주소의 노출 규칙에 따라 주소를 조합한다.
                    // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
                    var addr = ''; // 주소 변수

                    //사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
                    if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
                        addr = data.roadAddress;
                    } else { // 사용자가 지번 주소를 선택했을 경우(J)
                        addr = data.jibunAddress;
                    }

                    // 우편번호와 주소 정보를 해당 필드에 넣는다.
                    document.getElementById('sample6_postcode').value = data.zonecode;
                    document.getElementById("sample6_address").value = addr;
                    // 커서를 상세주소 필드로 이동한다.
                    document.getElementById("sample6_detailAddress").focus();
                }
            }).open();
        }
    </script>
    
</body>
</html>

🌱 sideMenu.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!-- 왼쪽 사이드 메뉴 -->
<section class="left-side">
    사이드메뉴

    <ul class="list-group">
        <li> <a href="/myPage/profile">프로필</a> </li>
        
        <li> <a href="/myPage/info">내 정보</a> </li>
        
        <li> <a href="/myPage/changePw">비밀번호 변경</a> </li>
        
        <li> <a href="/myPage/secession">회원 탈퇴</a> </li>
        
    </ul>
    
    
</section>

🌱 myPage-style.css

/* 회원 페이지 전체를 감싸고 있는 요소 */
.myPage-content{
    display: flex;
    width: 1000px;
    min-height: 700px;
    margin: 50px auto;
    /* 
        width / height 관련 속성

        1) width / height
            - 지정된 크기로 고정

        2) min-width / min-height    
            - 내부 요소가 부모 크기보다 작아도 지정된 최소 크기를 유지
            - 단, 내부 요소가 부모크기를 초과화면 부모의 크기가 늘어남

        3) max-width / max-height    
            - 내부 요소가 부모 크기보다 커도 지정된 크기를 유지 
            - 단, 내부 요소가 부모 크기보다 작다면 부모의 크기가 줄어듬
    */

}

/* 사이드메뉴 */
.left-side{
    width: 25%;
    border-right: 2px solid #ddd;
}

.list-group{
    width: 100%;
    list-style: none;
    padding-right: 20px;
}

.list-group > li{
    height: 50px;
    font-size: 18px;
}

.list-group > li > a{
    color:black;
    text-decoration: none;

    display: flex;
    height: 100%;

    justify-content: center;
    align-items: center;

    border-bottom : 2px solid #ddd;
}

.list-group > li > a:hover{
    background-color: #ccc;
}

/* ********************************* */
/* 마이페이지 공통 */
.myPage-main{
    width: 75%;
    padding: 0 50px;
}

/* 마이페이지 제목 */
.myPage-title{
    margin-bottom: 10px;
    font-size: 30px;
}

/* 마이페이지 부제 */
.myPage-subject{
    display: block;
    margin-bottom: 30px;

    font-size: 14px;
    letter-spacing: -1px;
}

/* 마이페이지 행 단위 스타일 지정 */
.myPage-row{
    width: 500px;
    height: 50px;
    margin-top: 20px;

    display: flex;
    align-items: center;
    border-bottom : 2px solid #ddd;
}

.myPage-row > * {
    font-size: 18px;
    font-weight: bold;
}

/* 행 제목 */
.myPage-row > label{
    width: 30%;
    color: #455ba8;
}

.myPage-row > span{
    width: 70%;
    color: #455ba8;
}

/* 행 내부 input 태그 */
.myPage-row > input{
    width: 100%;
    height: 100%;
    border: none;
    outline: none;
    font-weight: normal;
}

/* 제출 버튼 */
.myPage-submit{
    width: 100%;
    padding: 10px;
    margin: 50px 0;
    
    border: none;
    font-size: 20px;
    font-weight: bold;

    background-color: #455ba8;
    color: white;
    cursor: pointer;
}

/* form태그 */
form[name='myPageFrm']{
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
}

/* 내부 input 요소 focus 시 */
.myPage-row:focus-within{ 
    border-bottom-color: #455ba8;
}


/* ******************************************* */
/* 내 정보 페이지 전용 스타일 */

.info-title{
    border: none;
}

.info-address{
    margin: 0;
}

.info-address > button{
    width: 30%;
    height: 70%;

    font-size: 14px;
    font-weight: normal;

    background-color: white;
    border : 1px solid gray;
    cursor: pointer;
}

/* *********비밀번호 변경 화면*********** */
.myPage-row > input[type='password']{
    width: 70%;
} 

/* **********회원 탈퇴 약관********** */
.secession-terms{
    width: 500px;
    height: 300px;
    border: 1px solid black;
    
    overflow: auto;
    /* 내용이 요소를 벗어나는 경우 방향에 맞춰서 자동으로 스크롤 추가 */

    font-family: sans-serif; /* 돋움체 */
    font-size: 14px;
}

/* ************* 프로필 화면 ************* */
.profile-image-area{
    width: 150px;
    height: 150px;
    border: 3px solid #ccc;
    border-radius: 50%;

    position: relative;

    overflow: hidden;
    display: flex;
    justify-content: center;
    align-content: center;
}

#profileImage{
    height: 100%;
}

/* 삭제버튼 */
form[name='myPage-frm']{position: relative;}

#deleteImage{
    position: absolute;
    top: 0px;
    right: 240px;
    cursor: pointer;
}

/* 이미지 버튼 영역 */
.profile-btn-area{
    width: 230px;
    margin: 20px 0;

    display: flex;
    justify-content: center;
    align-items: center;
}

.profile-btn-area > *{
    width: 110px;
    height: 33px;
    padding: 5px 10px;

    border: 1px solid black;
    background-color: white;
    font-size: 14px;
    cursor: pointer;
    text-align: center;
}

#imageInput{ display: none;}

.profile-btn-area > button{
    background-color: #455ba8;
    color : white;
    margin-left: 2px;
}

🌱 jsp.json

{
	// Place your snippets for jsp here. Each snippet is defined under a snippet name and has a prefix, body and 
	// description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
	// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the 
	// same ids are connected.
	// Example:
	// "Print to console": {
	// 	"prefix": "log",
	// 	"body": [
	// 		"console.log('$1');",
	// 		"$2"
	// 	],
	// 	"description": "Log output to console"
	// }

	"core:jstl": {
        "prefix" : "core",
        "body" : ["<%@ taglib prefix=\"c\" uri=\"http://java.sun.com/jsp/jstl/core\"%>"],
        "description": "JSTL core(중요 구문 제공) 추가"
    }

    ,"fn:jstl": {
    "prefix" : "fn",
    "body" : ["<%@ taglib prefix=\"fn\" uri=\"http://java.sun.com/jsp/jstl/functions\"%>"],
    "description": "JSTL function(문자열 관련 함수 제공) 추가"
    }

    ,"fmt:jstl": {
    "prefix" : "fmt",
    "body" : ["<%@ taglib prefix=\"fmt\" uri=\"http://java.sun.com/jsp/jstl/fmt\"%>"],
    "description": "JSTL formmat(숫자, 날짜 형식 제공) 추가"
    }

}

🌱 myPage-secession.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"  %>

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>My Page</title>

    <link rel="stylesheet" href="/resources/css/myPage/myPage-style.css">

</head>
<body>
    <main>
       <jsp:include page="/WEB-INF/views/common/header.jsp"/>

        
        <!-- 마이페이지 - 내 정보 -->
        <section class="myPage-content">
            
			<!-- 사이드메뉴 include -->
			<!-- jsp 액션 태그 -->
			<jsp:include page="/WEB-INF/views/myPage/sideMenu.jsp"/>


            <!-- 오른쪽 마이페이지 주요 내용 부분 -->
            <section class="myPage-main">

                <h1 class="myPage-title">회원 탈퇴</h1>
                <span class="myPage-subject">현재 비밀번호가 일치하는 경우 탈퇴할 수 있습니다.</span>

                <form action="secession" method="POST" name="myPageFrm">

                    <div class="myPage-row">
                        <label>비밀번호</label>
                        <input type="password" name="memberPw" id="memberPw" maxlength="30">              
                    </div>

                    
                    <div class="myPage-row info-title">
                        <label>회원 탈퇴 약관</label>
                    </div>

                    <pre class="secession-terms">
제1조
이 약관은 샘플 약관입니다.

① 약관 내용 1

② 약관 내용 2

③ 약관 내용 3

④ 약관 내용 4


제2조
이 약관은 샘플 약관입니다.

① 약관 내용 1

② 약관 내용 2

③ 약관 내용 3

④ 약관 내용 4

                    </pre>

                    <div>
                        <input type="checkbox" name="agree" id="agree">
                        <label for="agree">위 약관에 동의합니다.</label>
                    </div>


                    <button class="myPage-submit">탈퇴</button>

                </form>

            </section>

        </section>

    </main>

	<jsp:include page="/WEB-INF/views/common/footer.jsp"/>

</body>
</html>

🌱 header.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<link rel="stylesheet" href="/resources/css/main-style.css">

<!-- font awesome 라이브러리 추가 + key 등록 -->
<script src="https://kit.fontawesome.com/f7459b8054.js" crossorigin="anonymous"></script>


<header>
    <section>
        <!-- 클릭 시 메인 페이지로 이동하는 로고 -->
        <a href="/">
            <img src="/resources/images/logo.jpg" alt="로고" id="homeLogo">
        </a>
    </section>


    <section>
        <!-- 검색어 입력할 수 있는 요소 배치 -->

        <article class="search-area">

            <!-- 
                action : 내부 input에 작성된 값을 제출할 경로/주소 
                method : 어떤 방식으로 제출할지 지정

                - GET : input태그 값을 주소에 담아서 제출(주소에 보임)
                - POST : input태그 값을 주소에 담지 않고 제출(주소에 안보임)
                        -> HTTP Body에 담아서 제출
            -->
            <form action="#" method="GET">

                <fieldset> <!-- form태그 내 영역 구분 -->

                    <!-- 
                        input의 name 속성 == 제출 시 key
                        input에 입력된 내용 == 제출 시 value

                        autocomplete="off" : 브라우저 제공 자동완성 off
                    -->
                    <input type="search" name="query" id="query"
                    placeholder="검색어를 입력해주세요."
                    autocomplete="off">

                    <!-- 검색 버튼 -->
                    <!-- button type="submit" 이 기본값 -->
                    <button id="searchBtn" class="fa-solid fa-magnifying-glass"></button>

                </fieldset>

            </form>

        </article>

    </section>


    <section></section>

    <%-- 우측 상단 드롭다운 메뉴 --%>
    <div class="header-top-menu">

        <c:choose>
        
            <c:when test="${empty loginMember}">
                <%-- 로그인 X --%>
                <a href="/">메인 페이지</a> | <a href="/member/login">로그인</a>
            </c:when>
        
            <c:otherwise>
                <%-- 로그인 O --%>
                <label for="headerMenuToggle">
                    ${loginMember.memberNickname} <i class="fa-solid fa-caret-down"></i>
                </label>

                <input type="checkbox" id="headerMenuToggle">

                <div class="header-menu">
                    <a href="/myPage/info">내정보</a>
                    <a href="/member/logout">로그아웃</a>
                </div>
            </c:otherwise>

        </c:choose>

    </div>

</header>

<nav>
    <ul>
        <li><a href="#">공지사항</a></li>
        <li><a href="#">자유 게시판</a></li>
        <li><a href="#">질문 게시판</a></li>
        <li><a href="#">FAQ</a></li>
        <li><a href="#">1:1문의</a></li>
    </ul>
</nav>

🌼 Eclipse

🌱 MyPageController.java

package edu.kh.project.myPage.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import edu.kh.project.member.model.dto.Member;
import edu.kh.project.myPage.model.service.MyPageService;

@SessionAttributes({"loginMember"})
// 1) Model에 세팅된 값의 key와 {}에 작성된 값이 일치하면 session scope로 이동
// 2) Session으로 올려둔 값은 해당 클래스에서 얻어와 사용을 가능하게 함
//		-> @SessionAttribute(key)로 사용

@RequestMapping("/myPage") // /myPage로 시작하는 요청을 모두 받음
@Controller // 요청/응답 제어 클래스 + Bean 등록
public class MyPageController {
	
	@Autowired // MyPageService의 자식 MyPageServiceImple 의존성 주입(DI)
	private MyPageService service;

	// 내 정보 페이지로 이동
	@GetMapping("/info")
	public String info() {
		// ViewResolver 설정 -> servlet-context.xml
		return "myPage/myPage-info";
	}
	
	// 프로필 페이지 이동
	@GetMapping("/profile")
	public String profile() {
		return "myPage/myPage-profile";
	}
	
	// 비밀번호 변경 페이지 이동
	@GetMapping("/changePw")
	public String changePw() {
		return "myPage/myPage-changePw";
	}
	
	// 탈퇴 페이지 이동
	@GetMapping("/secession")
	public String secession() {
		return "myPage/myPage-secession";
	}

	// 회원 정보 수정
	@PostMapping("/info")
	public String info(Member updateMember, String[] memberAddress
						, @SessionAttribute("loginMember") Member loginMember
						, RedirectAttributes ra) {
		
		
		// ----------- 매개 변수 설명 -----------
		// Member updateMember : 수정할 닉네임, 전화번호 담긴 커맨드 객체
		// String[] memberAddress : name='memberAddress'인 input 3개의 값(주소)
		
		// @SessionAttribute("loginMember") Member loginMember
		//	: Session에서 얻어온 "loginMember"에 해당하는 객체를 
		// : 매개변수 Member IoginMember에 저장
		
		// RedirectAttributes : 리다이렉트 시 값 전달용 객체(request)
		// ---------------------------------
		
		// 주소 하나로 합치기 (a^^^b^^^c)
		String addr = String.join("^^^", memberAddress);
		updateMember.setMemberAddress(addr);
		
		// 로그인한 회원의 반호를 updateMember에 추가
		updateMember.setMemberNo( loginMember.getMemberNo() );
		
		// DB에 회원 정보 수정(UPDATE) 서비스 호출
		int result = service.updateInfo(updateMember);

		String message = null;
		
		if(result > 0) { // 성공
			message = "회원 정보가 수정되었습니다.";
			
			// 세션에 로그인된 회원 정보도 수정(동기화)
			loginMember.setMemberNickname(updateMember.getMemberNickname());
			loginMember.setMemberTel(updateMember.getMemberTel());
			loginMember.setMemberAddress(updateMember.getMemberAddress());
			
		} else { // 실패
			message = "회원 정보 수정 실패 ㅠ";
		}
		
		ra.addFlashAttribute("message", message);
		
		return "redirect:info"; // 상대 경로(/myPage/info GET방식)
	}
}

🌱 MyPageService.java

package edu.kh.project.myPage.model.service;

import edu.kh.project.member.model.dto.Member;

public interface MyPageService {

	int updateInfo(Member updateMember);
	
}

🌱 MyPageServiceImpl.java

package edu.kh.project.myPage.model.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import edu.kh.project.member.model.dto.Member;
import edu.kh.project.myPage.model.dao.MyPageDAO;

@Service // 비즈니스 로직 처리 클래스 + Bean 등록 (IoC)
public class MyPageServiceImpl implements MyPageService {

	@Autowired // MyPageDAO 의존성 주입(DI)
	private MyPageDAO dao;

	// 스프링에서는 트랜잭션을 처리할 방법을 지원해 줌 (코드 기반, 선언적)
	
	// 1) <tx:advice> -> AOP를 이용한 방식(XML에 작성)

	// 2) @Transactional 어노테이션을 이용한 방식(클래스 또는 인터페이스에 작성)
	// - 인터페이스를 구현한 클래스로 선언된 빈은 인터페이스 메소드에 한해서 트랜잭션이 적용됨
	// * 트랜잭션 처리를 위해서는 트랜잭션 매니저가 bean으로 등록되어 있어야 함
	//   -> root-context.xml 작성

	// 정상 여부는 RuntimeException이 발생했는지 기준으로 결정되며, 
	// RuntimeException 외 다른 Exception(대표적으로 SQLException 등)에도 트랜잭션 롤백처리를 적용하고 싶으면 
	// @Transactional의 rollbackFor 속성을 활용하면 된다

	
	// 회원 정보 수정 서비스
	@Transactional(rollbackFor = {Exception.class})
	@Override
	public int updateInfo(Member updateMember) {
		return dao.updateInfo(updateMember);
	}
}

🌱 MyPageDAO.java

package edu.kh.project.myPage.model.dao;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import edu.kh.project.member.model.dto.Member;

@Repository // 저장소(DB)와 관련된 클래스 + Bean 등록(IoC, 스프링이 객체 관리)
public class MyPageDAO {
	
	// 등록된 Bean 중 타입이 SqlSessionTemplate으로 일치하는 Bean을 주입 (DI)
	// -> root-context.xml에 <bean> 작성됨
	@Autowired
	private SqlSessionTemplate sqlSession;

	/** 회원 정보 수정 DAO
	 * @param updateMember
	 * @return result
	 */
	public int updateInfo(Member updateMember) {
		
		// return sqlSession.update("namespace.id", 전달할 값);
		return sqlSession.update("myPageMapper.updateInfo", updateMember);
	}
}

🌱 myPage-mapper.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="myPageMapper">

	<!-- 회원 정보 수정 -->
	<update id="updateInfo" parameterType="Member">
		UPDATE MEMBER SET
		MEMBER_NICKNAME = #{memberNickname},
		MEMBER_TEL = #{memberTel},
		MEMBER_ADDR = #{memberAddress}
		WHERE MEMBER_NO = #{memberNo}
	</update>
	
</mapper>

💻 구현 화면

내 정보 중 닉네임, 전화번호, 주소 수정이 가능한 마이 페이지가 구현되었다. 😉

profile
풀스택 개발자 기록집 📁

0개의 댓글