spring mybatis 셋팅이 끝났다면 이제 게시판을 만들 차례이다.
공지사항 게시판을 예로 들어 먼저 공지사항 리스트 페이지를 만들어 보자.
가장 먼저 게시판을 위한 controller, dao, mapper, vo를 생성하자.
<프로젝트 패키지 구성>
필자는 위와 같이 패키지와 클래스를 생성하였다.
Controller 소스를 구성하기 전에 기존에 pom.xml에 등록했던 lombok 라이브러리의 기능을 사용하기 위해 lombok.config라는 파일을 생성하자.
경로 : 프로젝트 하위(pom.xml과 동일한 경로)
<lombok.config 소스 코드>
1 | lombok.log.fieldName=logger |
이 설정은 @Slf4j 어노테이션 사용 시 static logger를 생성하지 않고 바로 log를 찍을 수 있게 해준다.(아래의 소스들을 보면 확인이 가능하다.)
<게시판 table 생성>
1 2 3 4 5 6 7 8 | 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 소스 코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | 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 소스코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | 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 소스코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 소스코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 소스코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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 소스 코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | <? xml version = "1.0" encoding = "UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" < 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 소스코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | < p ><%@ 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 > </ p > |
<noticeDetail.jsp 소스코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | < p ><%@ 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 > </ p > |
noticeDetail.jsp에서 ajax를 사용하여 등록, 수정, 삭제 기능을 처리하였다.
-> ajax에서 serialize()메소드를 통해 POST로 넘길 form data들을 객체 변수로 생성하여 controller에서 NoticeVo 변수로 받을 수 있다.
<noticeList.css 소스코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | .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 소스코드>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | 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 |