클라우드 융합 Full-stack 웹 개발자 양성과정/Spring

Spring - 마이페이지(수정), 회원탈퇴, 게시글리스트조회

thesunset 2022. 12. 23. 17:59

📌taglib지시어 귀찮으니까 자동완성 해놓는 법 

window-preperences-jsp-editor-template-html5형식


📌추가설명

> 요청

① 클라이언트가 브라우저(크롬, 익스플로어)로 요청 전송

DispatcherServlet(Spring프레임워크에서 기본적으로 제공==web.xml)이 요청을 전달해줌 
 HanderMappingClass에게 찾아가서 url과 매칭되는 controller를 검색(HandlerMapping이 찾아서 알려줌/controller를 우리가 직접찾아야 됐다면 SpringBean에 등록되어있는 @RequestMapping이 대체해줌 ) 
 Handler Adpater에게 요청을 보냄(실질적으로 controller한테 일을 시키는 애)

 실행 Controller @Controller로 빈 등록 된 => Service에게 요청을 보냄  => DAO @Repository => DB

 


> 응답

돌아올 때 결과 리턴 클라이언트의 요청처리 완료 후 결과를 출력할 view데이터와 화면에 전달할 데이터를 담아서 리턴 핸들러 어댑터는 dispatcherServlet으로 돌아감(controller의 실행결과를 ModelAndView로 변환해서 리턴) 
dispactherSevlet은 ViewResolver에게 줌 (View를 검색해서 완성시켜달라함) 
 다시 dispatcherServlet에게 돌아감 => 다시 view한테 응답생성 요청을 함, jsp가 응답화면을 만듬 

 

 * handler가 controller의 매핑이다 라고 이해하기 

handler로 controller를 찾음 handler어댑터로 controller RequestaMapping으로 실행시켜줌

 

📌 포워딩인지, send-redirect인지 구별법.

dispatcherServlet에 rediretor가 붙었는지 안붙었는지, 안붙어있으면 무조건 포워딩(포워딩이면 viewResolver로 감-servlte-context dispatcherServlet에 다시가서 논리적경로(실체가없는)를 물리적경로(실제)로 갑니다)  


1. 마이페이지 

1) 로그인 후 화면에서 마이페이지 버튼 클릭시

 <a href="myPage.me">마이페이지</a>

2) 마이페이지 화면으로 포워딩* redirect가 없음

@RequestMapping("myPage.me")
public String myPage() {
    // /WEB-INF/views/member/myPage.jsp
    return "member/myPage";
}

<myPage.jsp>

3) myPage의 value값에 기본적으로 사용자의 정보가 들어있어야함 (EL구문으로 value값 넣어주기)

+ 회원정보 수정을 위해서 name값을 필드값과 동일하게 입력해줌, id값이 식별값이 므로 name값도 적어줘야함!(삭제하면 안됨)

<form action="update.me" method="post">
    <div class="form-group">
        <label for="userId">* ID : </label>
        <input type="text" class="form-control" id="userId" value="${sessionScope.loginUser.userId}" name="userId" readonly> <br>

        <label for="userName">* Name : </label>
        <input type="text" class="form-control" id="userName" value="${loginUser.userName}" name="userName" required> <br>

        <label for="email"> &nbsp; Email : </label>
        <input type="text" class="form-control" id="email" value="${loginUser.email}" name="email"> <br>

        <label for="age"> &nbsp; Age : </label>
        <input type="number" class="form-control" id="age" value="${loginUser.age}" name="age"> <br>

        <label for="phone"> &nbsp; Phone : </label>
        <input type="tel" class="form-control" id="phone" value="${loginUser.phone}" name="phone"> <br>

        <label for="address"> &nbsp; Address : </label>
        <input type="text" class="form-control" id="address" value="${loginUser.address}" name="address"> <br>
${sessionScope.loginUser.userId}

=> request를 거치지 않고 바로 sessionScope로 바로 조회해 더 빠르게 불러올 수 있음

이때, gender는 radio버튼이므로 사용자가 이전에 등록해두었던 정보에checked되어있어야 함

gender필드의 값이 빈 문자열(성별)일 경우 실제 DB에 담긴값은 null이겠지만 EL특성상 값이 없을 경우 빈문자열이 출력되므로 빈문자열과 비교

<script>
	$(function(){
        if('${loginUser.gender}'!=''){
            $('input[value="${loginUser.gender}"]').attr('checked', true);
        }
    })
</script>

수정을 위해 action속성으로 "update.me"

<form action="update.me" method="post">

<MembetController>

해야할 일1. 새로 입력받은 값으로 update하기 

해야할 일2. 새로 바뀐 값으로 session에 넣어주기

: DB로부터 수정된 회원정보를 다시 조회해서  session에 loginUser라는 키값으로 덮어씌워짐

@RequestMapping("update.me")
public String updateMember(Member m, Model model, HttpSession session ) {


    if(memberService.updateMember(m)>0) {
        session.setAttribute("loginUser", memberService.loginMember(m)); 	

        //session에 일회성 알럿문구 띄우기 
        session.setAttribute("alertMsg", "성공적으로 변경되었습니다.");

        //마이페이지로 url재요청
        return "redirect:myPage.me";
    }else {
        //request는 정보가 많이 담겨있음, 그래서 model을 쓰는 것이 좋음
        model.addAttribute("errorMsg", "회원정보수정실패");
        return "common/errorPage";
    }

}

<MemberServiceImpl>

@Override
public int updateMember(Member m) {
    //다중 트랜잭션이면 @transtion이라는 어노테이션을 붙여 트랜잭션처리를 해줘야하지만 단일 트랜잭션임
    return memberDao.updateMember(sqlSession, m);
}

<MemberDao>

public int updateMember(SqlSessionTemplate sqlSession, Member m) {
    return sqlSession.update("memberMapper.updateMember", m);
}

<member-mapper.xml>

<!-- 회원정보 수정용 쿼리문 -->
<update id="updateMember" parameterType="member">
    UPDATE 
            MEMBER 
    SET	
        USER_NAME = #{userName}
        ,EMAIL = #{email}
        ,AGE = #{age}
        ,PHONE = #{phone}
        ,ADDRESS = #{address}
        ,GENDER = #{gender}
        ,MODIFY_DATE = SYSDATE
    WHERE 
        USER_ID = #{userId}

</update>

다시 controller로 돌아와 화면 지정해주기

session에 넣은 alert창 띄워주기 

* alert창 예쁘게 만들기 

<!-- CSS -->
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/alertifyjs@1.13.1/build/css/alertify.min.css"/>
<!-- Default theme -->
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/alertifyjs@1.13.1/build/css/themes/default.min.css"/>
<!-- Semantic UI theme -->
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/alertifyjs@1.13.1/build/css/themes/semantic.min.css"/>

헤더에 넣구 

alertify.alert('알림', '${alertMsg}', function(){ alertify.success('Ok'); });

이거 넣어주기 !

 

+ alert창이 있을 때에만! 띄워줘야하므로 jstl, script구문이용해 조건문으로 처리

<c:if test="${not empty alertMsg}">
    <script>
    alertify.alert('알림', '${alertMsg}', function(){ alertify.success('Ok'); });
    </script>
    <c:remove var="alertMsg" scope="session"/>
</c:if>

2. 회원탈퇴

- 회원 탈퇴 버튼을 "클릭"하면 모달창이 열림

<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#deleteForm">회원탈퇴</button>

<!-- 회원탈퇴 버튼 클릭 시 보여질 Modal -->
<div class="modal fade" id="deleteForm">
    <div class="modal-dialog modal-sm">
        <div class="modal-content">

            <!-- Modal Header -->
            <div class="modal-header">
                <h4 class="modal-title">회원탈퇴</h4>
                <button type="button" class="close" data-dismiss="modal">&times;</button>
            </div>

            <form action="delete.me" method="post">
                <!-- Modal body -->
                <div class="modal-body">
                    <div align="center">
                        탈퇴 후 복구가 불가능합니다. <br>
                        정말로 탈퇴 하시겠습니까? <br>
                    </div>
                    <br>
                        <label for="userPwd" class="mr-sm-2">Password : </label>
                        <input type="text" class="form-control mb-2 mr-sm-2" placeholder="Enter Password" id="userPwd" name="userPwd"> <br>
                        <input type="hidden" name="userId" value="${userId}">
                </div>
                <!-- Modal footer -->
                <div class="modal-footer" align="center">
                    <button type="submit" class="btn btn-danger">탈퇴하기</button>
                </div>
            </form>
        </div>
    </div>
</div>

 

<MemberController>

고려할 점1. 사용자가 입력한 비밀번호와 DB의 비밀번호가 같아야 함

고려할 점2. 회원탈퇴 작업에 성공해야 함 

- userPwd : 회원탈퇴 요청 시 사용자가 입력한 비밀번호 평문

- session의 logingUser Member객체의 userPwd필드 : DB에 기록된 암호화된 비밀번호

지난시간에 했던 matches()메소드를 이용해서 맞는지 판별해주기 

=> 

후에 session에서 아이디값을 뽑아서 DB에 전달 조건을 걸어 status update에 성공하기!

@RequestMapping("delete.me")
public String deleteMember(String userPwd, Model model, HttpSession session) {
    //userPwd : 회원탈퇴 요청 시 사용자가 입력한 비밀번호 평문
    //session의 logingUser Member객체의 userPwd필드 : DB에 기록된 암호화된 비밀번호
    Member loginUser = (Member)session.getAttribute("loginUser");

    if(bcryptPasswordEncoder.matches(userPwd, loginUser.getUserPwd())) {
        //비밀번호가 사용자가 입력한 평문으로 만들어진 암호문일 경우 
        if(memberService.deleteMember(loginUser)>0) { 
            session.removeAttribute("loginUser");
            return "redirect:/";
        }else {
            model.addAttribute("errorMsg", "회원탈퇴실패");
            return "common/errorPage";
        }
    }else {
        model.addAttribute("errorMsg", "비밀번호 일치하지 않음");
        return "redirect:myPage.me";
    }
}

<MemberServiceImpl>

@Override
public int deleteMember(Member m) {
    return memberDao.deleteMember(sqlSession, m);
}

<MemberDao>

public int deleteMember(SqlSessionTemplate sqlSession, Member m) {
    return sqlSession.update("memberMapper.deleteMember", m);
}

<member-mapper.xml>

<update id="deleteMember" parameterType="member">
    UPDATE 
            MEMBER 
    SET	
        STATUS = 'N'
    WHERE 
        USER_ID = #{userId}
</update>

3. 게시판 리스트 조회(& 페이징처리)

0) 게시판 관련 기능 구현을 위한 셋팅

<Board> : Vo

@Data //Lombok
public class Board {
	private int boardNo;
	private String boardTitle;
	private String boardWriter;
	private String boardContent;
	private String originName;
	private String changeName;
	private int count;
	private Date createDate;
	private String status;
	
}

=> Lombok을 이용해서 생성자, getter, setter, toString만들어주기 == @Data

 

<BoardService> : interface

public interface BoardService {

	//게시글 총 개수 조회
	int selectListCount();
	
	//게시글 리스트 조회서비스
	ArrayList<Board> selectList(PageInfo pi);
	
	//게시글 작성하기 서비스
	int insertBoard(Board b);
	
	//게시글 조회수 증가(update)
	int increaseCount(int boardNo);
	//게시글 상세조회 서비스
	Board selectBoard(int boardNo);
	
	//게시글 삭제 서비스
	int deleteBoard(int boardNo);
	
	//게시글 수정 서비스 (실습)
	int updateBoard(Board b);
	
	//댓글 리스트 조회서비스(Ajax)
	ArrayList<Reply> replyList(int boardNo);
	
	//댓글 작성 서비스(Ajax)
	int insertReply(Reply r);  
	
	//Top-N분석
	ArrayList<Board> selectTopBoard();
	
	
}

<BoardServiceImpl> : 인터페이스 구현

//@Component ==bean으로 등록하겠다
@Service //Component보다 더 구체적
public class MemberServiceImpl implements MemberService {

의존성을 주입해주기 (File인젭션을 이용해서)@Autowired

@Autowired
private MemberDao memberDao;

//sqlSessionTemplate객체를 사용해서 bean등록을 했었음
@Autowired
private SqlSessionTemplate sqlSession; //기존의 mybatis의 sqlSession대체

<BoardDao>

@Repository
public class BoardDao {

<BoardController> :controller

@Controller
public class BoardController {

@controller, @Service, @Repository를 통해 Bean등록

@Autowired
private BoardService boardService;

의존성을 주입해주기 (File인젭션을 이용해서)@Autowired


1) header에서 게시판 a태그를 클릭할 경우 리스트를 조회

<li><a href="list.bo">게시판</a></li>

2) 저번까지 currentPage를 쿼리스트링을 가져왔으나 이번에는 

메뉴바의 게시판 클릭 시 => /list.bo
페이징바 클릭 시 => /list.bo?currentPage=요청하는 페이지의 번호

가 나오도록 할 예정 

*참고 :  RequestMapping을 세분화하면 GetMapping/PostMapping

@RequestMapping("list.bo")
public String selectList(@RequestParam(value="currentPage", defaultValue="1")int currentPage, Model model, ModelAndView mv){

    PageInfo pi = Pagination.getPageInfo( boardService.selectListCount(), currentPage, 10, 5);

    model.addAttribute("pi", pi);
    model.addAttribute("list",boardService.selectList(pi));

    //포워딩
    return "board/boardListView";
}

(1) 페이징바를 위한 list수 조회

(2) list의 데이터 조회

<BoardServiceImpl>

@Override
public int selectListCount() {
    return boardDao.selectListCount(sqlSession);
}

@Override
public ArrayList<Board> selectList(PageInfo pi) {
    return boardDao.selectList(sqlSession, pi);
}

<BoardDao>

public int selectListCount(SqlSessionTemplate sqlSession) {

    return sqlSession.selectOne("boardMapper.selectListCount");
}

public ArrayList<Board> selectList(SqlSessionTemplate sqlSession, PageInfo pi) {

    //나중엔 pageinfo에서 구해도 됨
    int offset = (pi.getCurrentPage()-1) * (pi.getBoardLimit());

    RowBounds rowBounds = new RowBounds(offset, pi.getBoardLimit());

    return (ArrayList)sqlSession.selectList("boardMapper.selectList", "", rowBounds);
}

* 팁 ! 나중에 프로젝트 때에는 offser과 rowBounds를 pageinfo에서 템플릿으로 받아놔도 편할 듯!

<board-mapper.xml>

 + 결과값을 받기위한 resultMap

<resultMap id="boardResultSet" type="board">
    <result column="BOARD_NO" property="boardNo"/>
    <result column="BOARD_TITLE" property="boardTitle"/>
    <result column="BOARD_WRITER" property="boardWriter"/>
    <result column="COUNT" property="count"/>
    <result column="CREATE_DATE" property="createDate"/>
    <result column="ORIGIN_NAME" property="originName"/>
</resultMap>

<!-- 페이징바를 위한 count -->
<select id="selectListCount" resultType="_int">
 SELECT 
        COUNT(*)
 FROM
        BOARD
 WHERE 
        STATUS = 'Y'
</select>

<!-- 게시글 리스트 쿼리문 -->
<select id="selectList" resultMap="boardResultSet">
SELECT
        BOARD_NO
        ,BOARD_TITLE
        ,BOARD_WRITER
        ,COUNT
        ,TO_CHAR(CREATE_DATE, 'YYYY-MM-DD') AS CREATE_DATE
        ,ORIGIN_NAME
FROM
    BOARD
WHERE 
    STATUS = 'Y'
ORDER 
    BY
        BOARD_NO DESC
</select>

SimpleValue타입이면 무조건 @RequestParam이 붙음 

그게 아니면, 앞에 ModelAttribute가 붙음(객체타입) 

 

<boardListView.jsp>

<tbody>
<c:forEach items="${list}" var="b">
    <tr>
        <td>${b.boardNo }</td>
        <td>${b.boardTitle }</td>
        <td>${b.boardWriter }</td>
        <td>${b.count }</td>
        <td>${b.createDate }</td>
        <c:if test="${not empty b.originName}">
             <td></td>
        </c:if>
        <td><td>
    </tr>
</c:forEach>
</tbody>

- 첨부파일이 있을 때에만 별을 보여줘야하는 조건문

<div id="pagingArea">
    <ul class="pagination">
        <c:choose>
            <c:when test="${pi.currentPage eq 1}">                    
                <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
            </c:when>
            <c:otherwise>
                <li class="page-item"><a class="page-link" href="list.bo?currentPage="${pi.currentPage-1}">Previous</a></li>
            </c:otherwise>
        </c:choose>
        <c:forEach begin="${pi.startPage}" end="${pi.endPage}" var="p">		     
            <li class="page-item"><a class="page-link" href="list.bo?currentPage=${p}">${p }</a></li>
        </c:forEach>

        <c:choose>
            <c:when test="${pi.maxPage eq 1}">                    
                <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
            </c:when>
            <c:otherwise>
                <li class="page-item"><a class="page-link" href="list.bo?currentPage="${pi.currentPage+1}">Next</a></li>
            </c:otherwise>
        </c:choose>

    </ul>
</div>

- 페이징바 처리