MySQL을 사용하는 스프링 게시판 프로그램
오라클을 사용하는 스프링 MVC 게시판을 MySql을 사용하도록 수정해 보자.
데이터베이스 디지인
root 계정으로 mysql 데이터베이스에 접속한다.
mysql --user=root --password mysql
접속된 상태에서 다음을 실행한다.
mysql --user=root --password mysql create user 'java'@'%' identified by 'school'; grant all privileges on *.* to 'java'@'%'; create database javaskool; exit; mysql --user=java --password javaskool create table member ( email varchar(60) PRIMARY KEY, passwd varchar(200) NOT NULL, name varchar(20) NOT NULL, mobile varchar(20) ); create table authorities ( email VARCHAR(60) NOT NULL, authority VARCHAR(20) NOT NULL, CONSTRAINT fk_authorities FOREIGN KEY(email) REFERENCES member(email) ); CREATE UNIQUE INDEX ix_authorities ON authorities(email,authority); create table board ( boardcd varchar(20), boardnm varchar(40) NOT NULL, boardnm_ko varchar(40) NOT NULL, constraint PK_BOARD PRIMARY KEY(boardcd) ); create table article ( articleno int NOT NULL AUTO_INCREMENT, boardcd varchar(20), title varchar(200) NOT NULL, content text NOT NULL, email varchar(60), hit bigint, regdate datetime, constraint PK_ARTICLE PRIMARY KEY(articleno), constraint FK_ARTICLE FOREIGN KEY(boardcd) REFERENCES board(boardcd) ); create table comments ( commentno int NOT NULL AUTO_INCREMENT, articleno int, email varchar(60), memo varchar(4000) NOT NULL, regdate datetime, constraint PK_COMMENTS PRIMARY KEY(commentno) ); create table attachfile ( attachfileno int NOT NULL AUTO_INCREMENT, filename varchar(255) NOT NULL, filetype varchar(255), filesize bigint, articleno int, email varchar(60), filekey varchar(255), creation datetime, constraint PK_ATTACHFILE PRIMARY KEY(attachfileno) ); create table views ( no int primary key AUTO_INCREMENT, articleNo int, ip varchar(60), yearMonthDayHour char(10), unique key (articleNo, ip, yearMonthDayHour) ); insert into board values ('chat','Chat','자유게시판'); commit; exit;
소스
아래는 오라클용 스프링 MVC 게시판과 다른 부분이다.
오라클용 스프링 MVC 게시판과 같은 artifactId를 사용하는 건 바람직하지 않으니 바꾼다.
pom.xml
<artifactId>spring-bbs-mysql</artifactId> <build> <finalName>spring-bbs-mysql</finalName>
MySql은 대소문자를 구별한다. MySql에서 테이블 이름을 member로 생성했다면 select * from MEMBER;은 오라클과 달리 에러를 발생시킨다. securty.xml을 열고 인증 쿼리를 아래를 참조해 수정한다.
security.xml
<jdbc-user-service data-source-ref="dataSource" users-by-username-query="select email as username,passwd as password,1 as enabled from member where email = ?" authorities-by-username-query="select email as username,authority from authorities where email = ?" />
데이터소스 설정을 MySql용으로 아래와 같이 수정한다.
applicationContext.xml
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/javaskool?useUnicode=yes&characterEncoding=UTF-8" /> <property name="username" value="java"/> <property name="password" value="school"/> <property name="maxActive" value="100"/> <property name="maxWait" value="1000"/> <property name="poolPreparedStatements" value="true"/> <property name="defaultAutoCommit" value="true"/> <property name="validationQuery" value="SELECT 1" /> </bean>
기존 BoardMapper.xml을 MySql용으로 아래와 같이 수정한다.
BoardMapper.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="net.java_school.mybatis.BoardMapper"> <select id="selectListOfArticles" parameterType="hashmap" resultType="Article"> SELECT a.articleno, a.title, a.regdate, a.hit, m.name, count(distinct(f.attachfileno)) attachfileNum, count(distinct(c.commentno)) commentNum FROM article as a left join attachfile as f on a.articleno = f.articleno left join comments as c on a.articleno = c.articleno left join member as m on a.email = m.email WHERE a.boardcd = #{boardCd} <if test="searchWord != null and searchWord != ''"> AND (title LIKE '%${searchWord}%' OR content LIKE '%${searchWord}%') </if> GROUP BY a.articleno, title, a.regdate, hit, m.name ORDER BY articleno DESC LIMIT #{offset}, #{rowCount} </select> <select id="selectCountOfArticles" parameterType="hashmap" resultType="int"> SELECT count(*) FROM article WHERE boardcd = #{boardCd} <if test="searchWord != null and searchWord != ''"> AND (title LIKE '%${searchWord}%' OR content LIKE '%${searchWord}%') </if> </select> <insert id="insert" parameterType="Article" useGeneratedKeys="true" keyProperty="articleNo"> INSERT INTO article (boardcd, title, content, email, hit, regdate) VALUES (#{boardCd}, #{title}, #{content}, #{email}, 0, now()) </insert> <insert id="insertAttachFile" parameterType="AttachFile"> INSERT INTO attachfile (filename, filetype, filesize, articleno, email) VALUES (#{filename}, #{filetype}, #{filesize}, #{articleNo}, #{email}) </insert> <update id="update" parameterType="Article"> UPDATE article SET title = #{title}, content = #{content} WHERE articleno = #{articleNo} </update> <delete id="delete" parameterType="int"> DELETE FROM article WHERE articleno = #{articleNo} </delete> <update id="updateHitPlusOne" parameterType="int"> UPDATE article SET hit = hit + 1 WHERE articleno = #{articleNo} </update> <select id="selectOne" parameterType="int" resultType="Article"> SELECT articleno, title, content, a.email, ifNull(name, 'Anonymous') name, hit, regdate FROM article as a left join member as m on a.email = m.email WHERE articleno = #{articleNo} </select> <select id="selectNextOne" parameterType="hashmap" resultType="Article"> SELECT articleno, title FROM article WHERE boardCd = #{boardCd} AND articleno > #{articleNo} <if test="searchWord != null and searchWord != ''"> AND (title LIKE '%${searchWord}%' OR content LIKE '%${searchWord}%') </if> ORDER BY articleno LIMIT 1 </select> <select id="selectPrevOne" parameterType="hashmap" resultType="Article"> SELECT articleno, title FROM article WHERE boardCd = #{boardCd} AND articleno < #{articleNo} <if test="searchWord != null and searchWord != ''"> AND (title LIKE '%${searchWord}%' OR content LIKE '%${searchWord}%') </if> ORDER BY articleno DESC LIMIT 1 </select> <select id="selectListOfAttachFiles" parameterType="int" resultType="AttachFile"> SELECT attachfileno, filename, filetype, filesize, articleno, email FROM attachfile WHERE articleno = #{articleNo} ORDER BY attachfileno </select> <delete id="deleteFile" parameterType="int"> DELETE FROM attachfile WHERE attachfileno = #{attachFileNo} </delete> <select id="selectOneBoard" parameterType="string" resultType="string"> SELECT * FROM board WHERE boardcd = #{boardCd} </select> <insert id="insertComment" parameterType="Comment"> INSERT INTO comments (articleno, email, memo, regdate) VALUES (#{articleNo}, #{email}, #{memo}, now()) </insert> <update id="updateComment" parameterType="Comment"> UPDATE comments SET memo = #{memo} WHERE commentno = #{commentNo} </update> <delete id="deleteComment" parameterType="int"> DELETE FROM comments WHERE commentno = #{commentNo} </delete> <select id="selectListOfComments" parameterType="int" resultType="Comment"> SELECT commentno, articleno, c.email, ifNull(name, 'Anonymous') name, memo, regdate FROM comments as c left join member as m on c.email = m.email WHERE articleno = #{articleNo} ORDER BY commentno DESC </select> <select id="selectOneAttachFile" parameterType="int" resultType="AttachFile"> SELECT attachfileno, filename, filetype, filesize, articleno, email FROM attachfile WHERE attachfileno = #{attachfileno} </select> <select id="selectOneComment" parameterType="int" resultType="Comment"> SELECT commentno, articleno, email, memo, regdate FROM comments WHERE commentno = #{commentNo} </select> </mapper>
UserMapper.xml은 오라클용과 내용이 같다.
페이징 처리에 사용되는 다음 VO 클래스는 오라클용과 함께 사용해도 된다.
NumbersForPaging.java
package net.java_school.commons; public class NumbersForPaging { private int totalPage; private int firstPage; private int lastPage; private int prevBlock; private int nextBlock; private int listItemNo; public int getTotalPage() { return totalPage; } public void setTotalPage(int totalPage) { this.totalPage = totalPage; } public int getFirstPage() { return firstPage; } public void setFirstPage(int firstPage) { this.firstPage = firstPage; } public int getLastPage() { return lastPage; } public void setLastPage(int lastPage) { this.lastPage = lastPage; } public int getPrevBlock() { return prevBlock; } public void setPrevBlock(int prevBlock) { this.prevBlock = prevBlock; } public int getNextBlock() { return nextBlock; } public void setNextBlock(int nextBlock) { this.nextBlock = nextBlock; } public int getListItemNo() { return listItemNo; } public void setListItemNo(int listItemNo) { this.listItemNo = listItemNo; } }
페이징 처리를 위한 숫자를 생산하는 다음 클래스는 오라클용과 함께 사용해도 된다.
Paginator.java
package net.java_school.commons; public class Paginator { public NumbersForPaging getNumbersForPaging(int totalRecord, int page, int numPerPage, int pagePerBlock) { int totalPage = totalRecord / numPerPage; if (totalRecord % numPerPage != 0) totalPage++; int totalBlock = totalPage / pagePerBlock; if (totalPage % pagePerBlock != 0) totalBlock++; int block = page / pagePerBlock; if (page % pagePerBlock != 0) block++; int firstPage = (block - 1) * pagePerBlock + 1; int lastPage = block * pagePerBlock; int prevPage = 0; if (block > 1) { prevPage = firstPage - 1; } int nextPage = 0; if (block < totalBlock) { nextPage = lastPage + 1; } if (block >= totalBlock) { lastPage = totalPage; } int listItemNo = totalRecord - (page - 1) * numPerPage; NumbersForPaging numbers = new NumbersForPaging(); numbers.setTotalPage(totalPage); numbers.setFirstPage(firstPage); numbers.setLastPage(lastPage); numbers.setPrevBlock(prevPage); numbers.setNextBlock(nextPage); numbers.setListItemNo(listItemNo); return numbers; } }
페이지를 위한 숫자를 생산해야 하는 컨트롤러는 Paginator 클래스를 상속하게 한다.
BbsController.java
@Controller @RequestMapping("/bbs") public class BbsController extends Paginator
목록과 상세보기에서 레코드를 그룹화할 때 필요한 숫자가 오라클과 MySql이 다르다. 아래 코드를 참조해 레코드을 그룹화하는 부분의 코드를 MySql에 맞게 수정한다.
BbsController.java
//목록 @RequestMapping(value="/{boardCd}", method=RequestMethod.GET) public String list(@PathVariable String boardCd, Integer page, String searchWord, Locale locale, Model model) { if (page == null) page = 1; int numPerPage = 10; int pagePerBlock = 10; int totalRecord = boardService.getTotalRecord(boardCd, searchWord); NumbersForPaging numbers = this.getNumbersForPaging(totalRecord, page, numPerPage, pagePerBlock); //mysql Integer offset = (page - 1) * numPerPage; HashMap<String, String> map = new HashMap<String, String>(); map.put("boardCd", boardCd); map.put("searchWord", searchWord); map.put("offset", offset.toString()); Integer rowCount = numPerPage; map.put("rowCount", rowCount.toString()); List<Article> list = boardService.getArticleList(map); Integer listItemNo = numbers.getListItemNo(); Integer prevPage = numbers.getPrevBlock(); Integer nextPage = numbers.getNextBlock(); Integer firstPage = numbers.getFirstPage(); Integer lastPage = numbers.getLastPage(); model.addAttribute("list", list); model.addAttribute("listItemNo", listItemNo); model.addAttribute("prevPage", prevPage); model.addAttribute("nextPage", nextPage); model.addAttribute("firstPage", firstPage); model.addAttribute("lastPage", lastPage); String lang = locale.getLanguage(); List<Board> boards = boardService.getBoards(); String boardName = this.getBoardName(boardCd, lang); model.addAttribute("boards", boards); model.addAttribute("boardName", boardName); //boardCd는 파라미터로 전달되지 않으므로 model.addAttribute("boardCd", boardCd); return "bbs/list"; } //상세보기 @RequestMapping(value="/{boardCd}/{articleNo}", method=RequestMethod.GET) public String view(@PathVariable String boardCd, @PathVariable Integer articleNo, Integer page, String searchWord, Locale locale, Model model) { if(page == null) page = 1; String lang = locale.getLanguage(); boardService.increaseHit(articleNo); Article article = boardService.getArticle(articleNo);//상세보기에서 볼 게시글 List<AttachFile> attachFileList = boardService.getAttachFileList(articleNo); Article nextArticle = boardService.getNextArticle(articleNo, boardCd, searchWord); Article prevArticle = boardService.getPrevArticle(articleNo, boardCd, searchWord); List<Comment> commentList = boardService.getCommentList(articleNo); String boardName = this.getBoardName(boardCd, lang); //상세보기에서 볼 게시글 관련 정보 String title = article.getTitle();//제목 String content = article.getContent();//내용 content = content.replaceAll(WebContants.LINE_SEPARATOR, "<br />"); int hit = article.getHit();//조회수 String name = article.getName();//작성자 이름 String email = article.getEmail();//작성자 ID Date regdate = article.getRegdate();//작성일 model.addAttribute("title", title); model.addAttribute("content", content); model.addAttribute("hit", hit); model.addAttribute("name", name); model.addAttribute("email", email); model.addAttribute("regdate", regdate); model.addAttribute("attachFileList", attachFileList); model.addAttribute("nextArticle", nextArticle); model.addAttribute("prevArticle", prevArticle); model.addAttribute("commentList", commentList); //목록관련 int numPerPage = 10;//페이지당 레코드 수 int pagePerBlock = 10;//블록당 페이지 링크수 int totalRecord = boardService.getTotalRecord(boardCd, searchWord); NumbersForPaging numbers = this.getNumbersForPaging(totalRecord, page, numPerPage, pagePerBlock); //mysql Integer offset = (page - 1) * numPerPage; HashMap<String, String> map = new HashMap<String, String>(); map.put("boardCd", boardCd); map.put("searchWord", searchWord); map.put("offset", offset.toString()); Integer rowCount = numPerPage; map.put("rowCount", rowCount.toString()); List<Article> list = boardService.getArticleList(map); int listItemNo = numbers.getListItemNo(); int prevPage = numbers.getPrevBlock(); int nextPage = numbers.getNextBlock(); int firstPage = numbers.getFirstPage(); int lastPage = numbers.getLastPage(); model.addAttribute("list", list); model.addAttribute("listItemNo", listItemNo); model.addAttribute("prevPage", prevPage); model.addAttribute("firstPage", firstPage); model.addAttribute("lastPage", lastPage); model.addAttribute("nextPage", nextPage); model.addAttribute("boardName", boardName); List<Board> boards = boardService.getBoards(); model.addAttribute("boards", boards); //articleNo와 boardCd는 파라미터로 전달되지 않기에 model.addAttribute("articleNo", articleNo); model.addAttribute("boardCd", boardCd); return "bbs/view"; }
수정을 마쳤다면 컴파일한다.
mvn clean compile war:inplace
톰캣 컨텍스트 파일
ROOT 애플리케이션으로 테스트해야 한다. 다음과 같이 ROOT.xml을 수정한다.
cd /etc/tomcat7/Catalina/localhost sudo nano ROOT.xml
ROOT.xml
<?xml version="1.0" encoding="UTF-8"?> <Context docBase="/home/kim/Lab/SpringBbs/src/main/webapp" reloadable="true"> </Context>
테스트
톰캣을 재실행한다.
sudo service tomcat8 restart
http://localhost:8080 방문한다.
참고