Last Modified 2025.5.5
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="maxTotal" value="100"/> <property name="maxWaitMillis" 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
//목록
@GetMapping("{boardCd}")
public String list(@PathVariable(name="boardCd") String boardCd, @RequestParam(name="page") Integer page,
@RequestParam(name="searchWord") 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";
}
//상세보기
@GetMapping("{boardCd}/{articleNo}")
public String view(@PathVariable(name="boardCd") String boardCd, @PathVariable(name="articleNo") Integer articleNo,
@RequestParam(name="page") Integer page, @RequestParam(name="searchWord") 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";
}
참고
