spring mybatis 셋팅이 끝났다면 이제 게시판을 만들 차례이다.
공지사항 게시판을 예로 들어 먼저 공지사항 리스트 페이지를 만들어 보자.
가장 먼저 게시판을 위한 controller, dao, mapper, vo를 생성하자.
<프로젝트 패키지 구성>
필자는 위와 같이 패키지와 클래스를 생성하였다.
Controller 소스를 구성하기 전에 기존에 pom.xml에 등록했던 lombok 라이브러리의 기능을 사용하기 위해 lombok.config라는 파일을 생성하자.
경로 : 프로젝트 하위(pom.xml과 동일한 경로)
<lombok.config 소스 코드>
lombok.log.fieldName=logger
이 설정은 @Slf4j 어노테이션 사용 시 static logger를 생성하지 않고 바로 log를 찍을 수 있게 해준다.(아래의 소스들을 보면 확인이 가능하다.)
<게시판 table 생성>
CREATE TABLE `push_notice` ( `notice_id` int(11) NOT NULL AUTO_INCREMENT, `notice_title` varchar(32) NOT NULL, `notice_coments` varchar(4000) NOT NULL, `mod_date` timestamp NULL DEFAULT NULL, `use_yn` varchar(2) NOT NULL DEFAULT 'Y', PRIMARY KEY (`notice_id`) )
필자는 위와같이 테이블을 생성하였다.
1. Controller 단 구성
<NoticeController.java 소스 코드>
package com.spring.test.controller; import java.util.List; import javax.annotation.Resource; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.spring.test.dao.NoticeDao; import com.spring.test.vo.NoticeVo; import com.spring.test.vo.PagingVo; import lombok.extern.slf4j.Slf4j; @Controller @Slf4j public class NoticeController { @Resource(name = "noticeDao") private NoticeDao noticeDao; @RequestMapping(value = "/noticeList") public String noticeList(@ModelAttribute("Notice") NoticeVo notice, @RequestParam(defaultValue="1") int curPage, Model model ) { logger.info("noticeList page"); int listCnt = noticeDao.getNoticeListCount(); PagingVo paging = new PagingVo(listCnt, curPage); notice.setStartIndex(paging.getStartIndex()); notice.setCntPerPage(paging.getPageSize()); List<NoticeVo> noticeList=noticeDao.getNoticeList(notice); model.addAttribute("noticeList", noticeList); model.addAttribute("listCnt", listCnt); model.addAttribute("paging", paging); return "notice/noticeList"; } @RequestMapping(value = "/noticeRegi") public String noticeRegi() { logger.info("notice Regi page"); return "notice/noticeDetail"; } @RequestMapping(value = "/noticeDetail/{notice_id}") public String noticeEdit(@PathVariable String notice_id, Model model) { logger.info("notice detail page [notice_id = {}]",notice_id); model.addAttribute("notice",noticeDao.getNoticeOne(notice_id)); return "notice/noticeDetail"; } @ResponseBody @RequestMapping(value = "/noticeInsert", method=RequestMethod.POST) public int noticeInsert(NoticeVo notice) { logger.info("notice insert"); return noticeDao.NoticeInsert(notice); } @ResponseBody @RequestMapping(value = "/noticeUpdate", method=RequestMethod.POST) public int noticeUpdate(NoticeVo notice) { logger.info("notice update {} ", notice.getNotice_id()); return noticeDao.NoticeUpdate(notice); } @ResponseBody @RequestMapping(value = "/noticeDelete", method=RequestMethod.POST) public int noticeDelete(NoticeVo notice) { logger.info("notice delete {} ", notice.getNotice_id()); return noticeDao.NoticeDelete(notice.getNotice_id()); } }
컨트롤러 구성은 위에서 부터 게시판 리스트 페이지, 등록페이지, 수정페이지, 게시판 정보 insert, 게시판 정보 update, 게시판 정보 delete 로 구성하였다.
하나의 메소드에서 여러개의 기능을 하도록 소스를 구성할 수 있으나, 보기 쉽게 할 수 있도록 메소드를 나뉘었다.
또한 noticeList()에서 페이징 처리를 할 수 있도록 소스를 구성하였다.
-> 여기서 @ResponseBody 어노테이션을 사용한 메소드는 view의 ajax로 부터의 요청에 대해 view 즉 jsp의 경로를 돌려주는게 아닌 문자열만 리턴하도록 처리된다.
-> Spring에서 제공하는 @Autowired 어노테이션 대신 java에서 제공하는 @Resource 어노테이션을 사용하여 가용성을 높였다.
2. model 단 구성
<PagingVo.java 소스코드>
package com.spring.test.vo; import lombok.Data; @Data public class PagingVo { /** 한 페이지당 게시글 수 **/ private int pageSize = 10; /** 한 블럭(range)당 페이지 수 **/ private int rangeSize = 10; /** 현재 페이지 **/ private int curPage = 1; /** 현재 블럭(range) **/ private int curRange = 1; /** 총 게시글 수 **/ private int listCnt; /** 총 페이지 수 **/ private int pageCnt; /** 총 블럭(range) 수 **/ private int rangeCnt; /** 시작 페이지 **/ private int startPage = 1; /** 끝 페이지 **/ private int endPage = 1; /** 시작 index **/ private int startIndex = 0; /** 이전 페이지 **/ private int prevPage; /** 다음 페이지 **/ private int nextPage; public PagingVo(int listCnt, int curPage){ /** * 페이징 처리 순서 * 1. 총 페이지수 * 2. 총 블럭(range)수 * 3. range setting */ // 총 게시물 수와 현재 페이지를 Controller로 부터 받아온다. /** 현재페이지 **/ setCurPage(curPage); /** 총 게시물 수 **/ setListCnt(listCnt); /** 1. 총 페이지 수 **/ setPageCnt(listCnt); /** 2. 총 블럭(range)수 **/ setRangeCnt(pageCnt); /** 3. 블럭(range) setting **/ rangeSetting(curPage); /** DB 질의를 위한 startIndex 설정 **/ setStartIndex(curPage); } public void setPageCnt(int listCnt) { this.pageCnt = (int) Math.ceil(listCnt*1.0/pageSize); } public void setRangeCnt(int pageCnt) { this.rangeCnt = (int) Math.ceil(pageCnt*1.0/rangeSize); } public void rangeSetting(int curPage){ setCurRange(curPage); this.startPage = (curRange - 1) * rangeSize + 1; this.endPage = startPage + rangeSize - 1; if(endPage > pageCnt){ this.endPage = pageCnt; } this.prevPage = curPage - 1; this.nextPage = curPage + 1; } public void setCurRange(int curPage) { this.curRange = (int)((curPage-1)/rangeSize) + 1; } public void setStartIndex(int curPage) { this.startIndex = (curPage-1) * pageSize; } }
<NoticeVo.java 소스코드>
package com.spring.test.vo; import lombok.Data; @Data public class NoticeVo { private String notice_id; private String notice_title; private String notice_coments; private String use_yn; private String mod_date; private int startIndex; private int cntPerPage; }
->lombok의 @Data 어노테이션을 사용하여 setter, getter 생성을 생략할 수 있다.
<NoticeMapper.java 소스코드>
package com.spring.test.dao; import java.util.List; import org.springframework.stereotype.Repository; import com.spring.test.vo.NoticeVo; @Repository(value = "noticeMapper") public interface NoticeMapper { public int noticeListCount(); public List<NoticeVo> noticeList(NoticeVo notice); public NoticeVo noticeOne(String notice_id); public int noticeInsert(NoticeVo notice); public int noticeUpdate(NoticeVo notice); public int noticeDelete(String notice_id); }
<NoticeDao.java 소스코드>
package com.spring.test.dao; import java.util.List; import javax.annotation.Resource; import org.springframework.stereotype.Service; import com.spring.test.vo.NoticeVo; @Service(value = "noticeDao") public class NoticeDao{ @Resource(name = "noticeMapper") private NoticeMapper noticeMapper; public int getNoticeListCount() { return noticeMapper.noticeListCount(); } public List<NoticeVo> getNoticeList(NoticeVo notice){ return noticeMapper.noticeList(notice); } public NoticeVo getNoticeOne(String notice_id) { return noticeMapper.noticeOne(notice_id); } public int NoticeInsert(NoticeVo notice) { return noticeMapper.noticeInsert(notice); } public int NoticeUpdate(NoticeVo notice) { return noticeMapper.noticeUpdate(notice); } public int NoticeDelete(String notice_id) { return noticeMapper.noticeDelete(notice_id); } }
-> NoticeDao.java에서 현재 소스에는 없지만 추후 트랜잭션을 처리하기에 용이할 것 같다.
<noticeMapper.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="com.spring.test.dao.NoticeMapper"> <resultMap id="NoticeVo" type="com.spring.test.vo.NoticeVo"> <result property="notice_id" column="notice_id"/> <result property="notice_title" column="notice_title"/> <result property="notice_coments" column="notice_coments"/> <result property="mod_date" column="mod_date"/> <result property="use_yn" column="use_yn"/> </resultMap> <select id="noticeListCount" resultType="Integer"> select count(*) from push_notice; </select> <select id="noticeList" resultMap = "NoticeVo"> select notice_id, notice_title, notice_coments, use_yn, DATE_FORMAT(mod_date, '%Y-%m-%d') mod_date from push_notice order by notice_id limit #{startIndex}, #{cntPerPage} </select> <select id="noticeOne" parameterType="String" resultMap = "NoticeVo"> select notice_id, notice_title, notice_coments from push_notice where notice_id = #{notice_id} </select> <insert id="noticeInsert" parameterType="com.spring.test.vo.NoticeVo"> insert into push_notice ( notice_title, notice_coments, mod_date ) values ( #{notice_title}, #{notice_coments}, NOW() ) </insert> <update id="noticeUpdate" parameterType="com.spring.test.vo.NoticeVo"> update push_notice set notice_title = #{notice_title}, notice_coments = #{notice_coments}, mod_date = NOW() where notice_id = #{notice_id} </update> <delete id="noticeDelete" parameterType="String"> delete from push_notice where notice_id = #{notice_id} </delete> </mapper>
비즈니스 로직을 다양한 방법으로 구성할 수 있으나 필자는 위와 같이 구성하는 것이 추후 유지보수 하거나 트랜잭션을 구성하는데 용이하다고 판단되었다.
3. View 단 구성
View단은 noticeList.jsp와 noticeDetail.jsp로 구성하였으며, jquery는 3.3.1버전을 사용하였다.
<noticeList.jsp 소스코드>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <link rel="stylesheet" type="text/css" href="/test/resources/css/noticeList.css"> <script type="text/javascript" src="/test/resources/js/jquery-3.3.1.min.js"></script> <script> $(document).ready(function(){ $("#notice_regi").on("click",function(){ location.href="/test/noticeRegi" }); }); function fn_paging(curPage){ location.href="/test/noticeList?curPage="+curPage; } function notice_push(notice_id){ alert(notice_id); } </script> </head> <body> <div class="title">공지사항</div> <div class="contents"> <input type="button" id="notice_regi" value="등록"> <div class="divTable greenTable"> <div class="divTableHeading"> <div class="divTableRow"> <div class="divTableHead"><input type="checkbox"></div> <div class="divTableHead">NO</div> <div class="divTableHead">제목</div> <div class="divTableHead">등록일자</div> <div class="divTableHead"></div> </div> </div> <c:forEach var="v" items="${noticeList}" varStatus="status"> <div class="divTableBody"> <div class="divTableRow"> <div class="divTableCell"><input type="checkbox"></div> <div class="divTableCell">${status.index+1+(paging.curPage-1)*10}</div> <div class="divTableCell"> <a href="/test/noticeDetail/${v.notice_id}">${v.notice_title}</a> </div> <div class="divTableCell">${v.mod_date}</div> <div class="divTableCell"><input type="button" onclick="notice_push(${v.notice_id})" value="전송"></div> </div> </div> </c:forEach> </div> <div class="greenTable outerTableFooter"> <div class="tableFootStyle"> <div class="links"> <a href="#" onClick="fn_paging(1)">[처음]</a> <c:if test="${paging.curPage ne 1}"> <a href="#" onClick="fn_paging(${paging.prevPage })">[이전]</a> </c:if> <c:forEach var="pageNum" begin="${paging.startPage }" end="${paging.endPage }"> <c:choose> <c:when test="${pageNum eq paging.curPage}"> <span style="font-weight: bold;"> <a href="#" onClick="fn_paging(${pageNum })" style="font-weight: bold; color:red;"> ${pageNum } </a> </span> </c:when> <c:otherwise> <a href="#" onClick="fn_paging(${pageNum })">${pageNum }</a> </c:otherwise> </c:choose> </c:forEach> <c:if test="${paging.curPage ne paging.pageCnt && paging.pageCnt > 0}"> <a href="#" onClick="fn_paging(${paging.nextPage })">[다음]</a> </c:if> <c:if test="${paging.curRange ne paging.rangeCnt && paging.rangeCnt > 0}"> <a href="#" onClick="fn_paging(${paging.pageCnt })">[끝]</a> </c:if> </div> </div> </div> <div> 총 게시글 수 : ${paging.listCnt } / 총 페이지 수 : ${paging.pageCnt } / 현재 페이지 : ${paging.curPage } / 현재 블럭 : ${paging.curRange } / 총 블럭 수 : ${paging.rangeCnt } </div> </div> </body> </html>
<noticeDetail.jsp 소스코드>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <link rel="stylesheet" type="text/css" href="/test/resources/css/noticeDetail.css"> <script type="text/javascript" src="/test/resources/js/jquery-3.3.1.min.js"></script> <script> $(document).ready(function(){ //공지사항 신규 등록 $("#notice_regist").on("click",function(){ var formData = $("#notice_form").serialize(); $.ajax({ type : "post", url : "/test/noticeInsert", data : formData, success : function(data){ if(data==1) alert("등록 완료"); else alert('등록 실패'); }, error : function(error){ alert("등록 실패"); console.log("notice insert fail : "+error); } }); }); //공지사항 수정 $("#notice_edit").on("click",function(){ var formData = $("#notice_form").serialize(); $.ajax({ type : "post", url : "/test/noticeUpdate", data : formData, success : function(data){ if(data==1) alert("수정 완료"); else alert('수정 실패'); }, error : function(error){ alert("수정 실패"); console.log("notice update fail : "+error); } }); }); //공지사항 삭제 $("#notice_delete").on("click",function(){ var formData = $("#notice_form").serialize(); alert(formData); $.ajax({ type : "post", url : "/test/noticeDelete", data : formData, success : function(data){ if(data==1){ alert("삭제 완료"); location.href="/push/noticeList"; }else alert('삭제 실패'); }, error : function(error){ alert("삭제 실패"); console.log("notice delete fail : "+error); } }); }); $("#notice_backPage").on("click",function(){ location.href="/test/noticeList"; }); }) </script> </head> <body> <div class="big_title">공지사항 수정/삭제/추가</div> <div class="big_contents"> <form id="notice_form"> <input type="hidden" id="notice_id" name="notice_id" value="${notice.notice_id }"> <div class="title"> <div> 제목 </div> <div> <input type="text" id="notice_title" name="notice_title" value="${notice.notice_title}"> </div> </div> <div class="contents"> <div> 내용 </div> <div> <textarea id="notice_coments" name="notice_coments">${notice.notice_coments}</textarea> </div> </div> </form> <div class="footer"> <c:if test="${null eq notice }"> <input type="button" id="notice_regist" value="등록"> </c:if> <c:if test="${null ne notice }"> <input type="button" id="notice_edit" value="수정"> </c:if> <input type="button" id="notice_backPage" value="뒤로"> <input type="button" id="notice_delete" value="삭제"> </div> </div> </body> </html>
noticeDetail.jsp에서 ajax를 사용하여 등록, 수정, 삭제 기능을 처리하였다.
-> ajax에서 serialize()메소드를 통해 POST로 넘길 form data들을 객체 변수로 생성하여 controller에서 NoticeVo 변수로 받을 수 있다.
<noticeList.css 소스코드>
.title{ position : relative; text-align : center; left : 50%; width : 200px; margin-left : -100px; } .contents{ position : relative; top : 30px; left : 50%; width : 1000px; margin-left : -500px; } div.greenTable { font-family: Georgia, serif; border: 0px solid #949494; background-color: #FFFFFF; width: 100%; border-collapse: collapse; } .divTable.greenTable .divTableCell, .divTable.greenTable .divTableHead { border: 1px solid #000000; padding: 3px 2px; } .divTable.greenTable .divTableBody .divTableCell { font-size: 13px; } .divTable.greenTable .divTableHeading .divTableHead { font-size: 15px; font-weight: bold; color: #000000; text-align: center; } .greenTable .tableFootStyle { font-size: 13px; font-weight: bold; } .greenTable .tableFootStyle { font-size: 13px; } .greenTable .tableFootStyle .links { text-align: right; } .greenTable .tableFootStyle .links a{ display: inline-block; color: #101010; padding: 2px 8px; border-radius: 5px; } .greenTable.outerTableFooter { border-top: none; } .greenTable.outerTableFooter .tableFootStyle { padding: 3px 5px; } .divTableRow>div:first-child{ width : 5px; text-align : center; } .divTableRow>div:nth-child(2){ width : 10px; text-align : center; } .divTableRow>div:nth-child(3){ width : 700px; } .divTableRow>div:nth-child(4){ width : 100px; text-align : center; } .divTableRow>div:nth-child(5){ width : 10px; text-align : center; } /* DivTable.com */ .divTable{ display: table; } .divTableRow { display: table-row; } .divTableHeading { display: table-header-group;} .divTableCell, .divTableHead { display: table-cell;} .divTableHeading { display: table-header-group;} .divTableFoot { display: table-footer-group;} .divTableBody { display: table-row-group;} .divTableBody a,.tableFootStyle a{ text-decoration:none; color : black; }
<noticeDetail.css 소스코드>
body{ margin : 0px; padding : 0px; } .big_title{ position : relative; text-align : center; left : 50%; width : 200px; margin-left : -100px; } .big_contents{ position : relative; top : 30px; left : 50%; width : 800px; margin-left : -400px; } .title{ position : absolute; border-top : 1px solid black; width : 100%; } .contents{ position : absolute; top : 30px; width : 100%; border-top : 1px solid black; border-bottom : 1px solid black; } .footer{ position : absolute; width : 140px; top : 560px; left: 50%; margin-left : -70px; } .title>div, .contents>div{ display : inline-block; padding : 3px 0 3px 0; width : 100%; } .title>div:first-child, .contents>div:first-child{ width : 100px; text-align : center; } .title>div:nth-child(2), .contents>div:nth-child(2){ width : 600px; } .title input[type=text] { width : 602px; } .contents textarea { width : 600px; height : 500px; } .contents>div:first-child{ position : relative; bottom : 250px; }
페이징 처리 로직은 https://gangnam-americano.tistory.com/18 을 참고하였다.(참고 사이트에서는 일부 소스가 누락되어있음)
4. 모두 적용이 되었다면 tomcat에 올려 실행시켜 보자.(tomcat 7을 사용하였다.)
<공지사항 리스트 페이지>
<공지사항 상세 페이지>
<공지사항 등록 페이지>
'spring' 카테고리의 다른 글
spring quartz 스케쥴링 java config (3) | 2019.02.15 |
---|---|
spring 스케쥴링 설정 (0) | 2019.02.12 |
view에서 특정 함수 반복 실행 방지 (0) | 2019.02.12 |
spring mybatis 셋팅 (0) | 2019.02.11 |