2017 - 10 - 23 (월)
코드로 배우는 스프링 웹프로젝트[구멍가게 코딩단 지음] 참조
게시판 구현 소스 : https://github.com/shj7242/SpringProject/blob/master/ex6.zip
1 .. 기초 작업
라이브러리 추가 항목 : https://shj7242.github.io/2017/10/16/Spring11/
이외로 datasource 생성 및 sqlSessionFactory, sqlSession을 생성해준다.(root-context.xml)
2 .. 게시판 구현과정
1 . DB에 게시판에 활용하고자 하는 Attribute를 생각하여 테이블을 생성한다.
(게시물번호, 게시물제목, 게시물내용, 게시자, 게시시간, 조회수 로 생성)
--예시
create table tbl_board(
bno Int NOT NULL AUTO_INCREMENT,
title VARCHAR(200) NOT NULL,
content TEXT NULL,
writer VARCHAR(50) NOT NULL,
regdate TIMESTAMP NOT NULL DEFAULT now(),
viewcnt INT DEFAULT 0,
PRIMARY KEY(bno)
);
2 . 생성한 테이블의 Attribute 와 동일한 필드를 가진 VO를 생성하고 GETTER/SETTER 메소드 정의 (BoardVO.java 로 생성)
3 . DAO인터페이스를 생성한다. (CRUD 형태로 구성한다. + 전체목록을 가져올 메서드도 정의 한다.)
C(create) - Insert
R(read) - select
U(update) - update
D(delete) - delete
4 . resource 폴더 내에 Mapper 를 생성한다. (boardMapper.xml이라는 이름으로 생성함)
Mapper에는 후에 DAO 가 Mapper 의 sql을 가져다 사용할 수 있도록 mapper의 경로를 잡아준다.
- 이때 주의할 점은 boardMapper의 첫 글자를 대문자로 써줘야한다.
<?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="org.zerock.mapper.BoardMapper">
<insert id="create">
insert into tbl_board (title, content, writer)
values(#{title},#{content}, #{writer})
</insert>
<select id="read" resultType="org.zerock.domain.BoardVO">
select
bno, title, content, writer, regdate, viewcnt
from
tbl_board
where bno = #{bno}
</select>
<update id="update">
update tbl_board set title =#{title}, content =#{content}
where bno = #{bno}
</update>
<delete id="delete">
delete from tbl_board where bno = #{bno}
</delete>
5 . DAO 인터페이스의 구현 : 기존 기본설정에는 root-context.xml에서 DAO 가 속해있는 persistence 를 component-scan 한다.
DAOImpl 에서는 자동으로 Bean을 생성해주기 위해서 @Repository 라는 @Component 역할을 하는 애너테이션을 붙여준다.
기존에 root-context.xml에 설정한 sqlSession객체를 생성하여 @Inject 애너테이션을 활용하여 의존성을 주입해준다. (Connection)
Mapper의 sql문을 활용하기 위해 namespace에 사용하고자 하는 Mapper의 경로를 변수로 정의해준다.
인터페이스에서 정의한 메소드를 구현해준다.
구현하는 형태는 대표적으로 아래와 같다.
@Override
public void create(BoardVO vo) throws Exception {
session.insert(namespace + ".create", vo);
}
// 매퍼의 경로.매퍼에 정의한 sql문의 이름 , 보낼 값 (insert의 경우 VO의 정보 모두다 집어넣기에 vo를 보냄)
@Override
public BoardVO read(Integer bno) throws Exception {
return session.selectOne(namespace + ".read", bno);
}
@Override
public void update(BoardVO vo) throws Exception {
session.update(namespace + ".update", vo);
}
6 . typeAliases 적용하기 : mybatis-config.xml 파일에 다음 코드를 넣어주면 Mapper를 이용할 때 클래스의 이름을 org.zerock.domain처럼 앞의 패키지명을 생략하는게 가능하다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="org.zerock.domain"/>
</typeAliases>
</configuration>
business layer
7 . Service 인터페이스 구현. (BoardService로 생성)
service는 기본적으로 컨트롤러와 DAO를 연결해주는 역할을 한다.
예를들자면 client가 게시물 등록 버튼을 클릭하게 되면 이 요청은 Controller에 매핑되며 이 컨트롤러에서는 특정 service메서드를 실행하게 되고 이 Service 에서는 특정 DAO 메서드를 실행하게된다.
8 . ServiceImpl 의 구현 : root-context.xml에서 해당 패키지에 component-scan을 설정하고 @Service 애너테이션을 통해 bean을 자동등록한다. 또한 @Inject 애너테이션으로 서비스에 필요한 DAO객체 생성과 의존성을 주입해준다.
대표적으로 서비스는 다음과 같이 구현한다.
@Service
public class BoardServiceImpl implements BoardService {
@Inject
private BoardDAO dao;
@Override
public void regist(BoardVO board) throws Exception {
dao.create(board);
}
@Override
public BoardVO read(Integer bno) throws Exception {
return dao.read(bno);
}
9 . 컨트롤러 구현 : 컨트롤러 구현시 가장 중점을 두어야하는 점이 URI를 어떤 방식으로 쓸지 이다.
어떤 작업에는 GET 어떤작업에는 POST 를 쓸지는 중요한 부분이다.
아래처럼 스토리 보드를 만들어 작업하는게 효율적이다.
방식 | URI | 설명 |
---|---|---|
GET | /board/register | 게시물의 등록 페이지를 보여준다. |
POST | /board/register | 게시물을 등록한다. |
GET | /board/read?bno=xxx | 특정 게시물을 조회한다. |
POST | /board/remove | 게시물을 삭제한다. |
컨트롤러는 다음과 같이 작성한다. Controller 는 servlet-context.xml에서 component-scan을 등록하며 @Controller 애너테이션을 사용하여 자동 빈을 생성한다.
Front 에서 (view) 들어오는 요청중 board/xxxx 로 들어오는 요청은 boardController가 모두 받아 적절한 메소드에 매핑한다.
redirect의 활용 : 아래 /register 로 매핑되는 void형태의 registerGET 과 반환형의 registerPOST 를 보면 게시물의 등록작업의 플로우는 최초로 registerGET에 매핑하여 Void 반환형이므로 매핑된 주소값인 board/register를 반환하게되고 여기서 최초로 어떤 글을 쓴 후 등록 버튼을 누르게 된다면 특정페이지를 반환하는 registerPOST가 실행되는 것이다. 이 때 redirect를 활용하지 않을 경우 새로고침 시 POST방식으로 다시 데이터를 요청하게되어 중복게시가 된다. 이 문제점을 방지하기위해 Redirect를 써서 페이지를 옮기는 것이다. (이 때 addFlashAttribute를 사용해서 처음 redirect될 때 특정 key값에 값을 싫어 보낼 수 있다. -일회성)
@Controller
@RequestMapping("/board/*")
public class BoardController {
@Inject
private BoardService service;
@RequestMapping(value = "/register", method = RequestMethod.GET)
public void registerGET(BoardVO board, Model model) throws Exception {
}
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String registPOST(BoardVO board, RedirectAttributes rttr) throws Exception {
service.regist(board);
rttr.addFlashAttribute("msg", "success"); //redirect로
return "redirect:/board/listAll";
}
@RequestMapping(value = "/listAll", method = RequestMethod.GET)
public void listAll(Model model) throws Exception {
model.addAttribute("list", service.listAll());
}
@RequestMapping(value = "/read", method = RequestMethod.GET)
public void read(@RequestParam("bno") int bno, Model model) throws Exception {
model.addAttribute(service.read(bno));
}
@RequestMapping(value = "/remove", method = RequestMethod.POST)
public String remove(@RequestParam("bno") int bno, RedirectAttributes rttr) throws Exception {
service.remove(bno);
rttr.addFlashAttribute("msg", "SUCCESS");
return "redirect:/board/listAll";
}
@RequestMapping(value = "/modify", method = RequestMethod.GET)
public void modifyGET(int bno, Model model) throws Exception {
model.addAttribute(service.read(bno));
}
@RequestMapping(value = "/modify", method = RequestMethod.POST)
public String modifyPOST(BoardVO board, RedirectAttributes rttr) throws Exception {
service.modify(board);
rttr.addFlashAttribute("msg", "SUCCESS");
return "redirect:/board/listAll";
}
10 . view페이지 작성(jsp)
예를 들어 게시물 등록하는 폼이다. 특이점은 action 속성이 없다. 이러한 경우 현재 경로를 그대로 action의 대상 경로로 잡는다.
- 주의할 점 : jsp 에서 ${boardVO.bno}처럼 BoardVO 객체의 필드를 사용할경우 첫글자를 소문자로 쓴다. (Select 사용시)
<form role="form" method="post">
<div class="box-body">
<div class="form-group">
<label for="exampleInputEmail1">Title</label>
<input type="text"
name='title' class="form-control" placeholder="Enter Title">
</div>
<div class="form-group">
<label for="exampleInputPassword1">Content</label>
<textarea class="form-control" name="content" rows="3"
placeholder="Enter ..."></textarea>
</div>
<div class="form-group">
<label for="exampleInputEmail1">Writer</label>
<input type="text"
name="writer" class="form-control" placeholder="Enter Writer">
</div>
</div>
<!-- /.box-body -->
<div class="box-footer">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
JQUERY를 활용하여 수정/제거/전체보기 버튼에 대한 이벤트로서 각각의 기능별로 컨트롤러 메서드 경로를 매핑시킨다.
아래와 같이 사용한다.
$(document).ready(function(){
var formObj = $("form[role='form']");
console.log(formObj);
$(".btn-warning").on("click", function(){
formObj.attr("action", "/board/modify");
formObj.attr("method", "get");
formObj.submit();
});
$(".btn-danger").on("click", function(){
formObj.attr("action", "/board/remove");
formObj.submit();
});
$(".btn-primary").on("click", function(){
self.location = "/board/listAll";
});
});