📌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"> Email : </label>
<input type="text" class="form-control" id="email" value="${loginUser.email}" name="email"> <br>
<label for="age"> Age : </label>
<input type="number" class="form-control" id="age" value="${loginUser.age}" name="age"> <br>
<label for="phone"> Phone : </label>
<input type="tel" class="form-control" id="phone" value="${loginUser.phone}" name="phone"> <br>
<label for="address"> 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">×</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>
- 페이징바 처리
'클라우드 융합 Full-stack 웹 개발자 양성과정 > Spring' 카테고리의 다른 글
Spring - ModelAndView로 바꾸기, 게시판글쓰기, 삭제, 수정 (0) | 2022.12.27 |
---|---|
Spring - 로그인, 로그아웃, 회원가입 (0) | 2022.12.20 |
Spring - 개발환경구성 (0) | 2022.12.20 |
MyBatis - 검색, filter기능 (0) | 2022.12.19 |
MyBatis - 환경구성, 로그인, 회원가입기능, 게시글리스트 조회, 게시글 상세조회 (0) | 2022.12.17 |