게시판 CRUD구현
출처 : 코드로 배우는 스프링 프로젝트, 출판사 : 남가람북스
어떤 웹 사이트를 만들던 게시판은 공통적으로 필요하다.
게시판은 기본적으로 아래와 같은 기능을 가지고 있다.
- 게시물의 CRUD(등록, 조회, 수정, 삭제)
- 페이징 처리
- 검색 처리
개발 준비
프로젝트 JDK 버전이 1.8인지 확인한다(프로젝트가 사용하는 JDK 버전이다).
pom.xml(의존성 설정파일)에 아래와 같이 의존성이 추가되었는지 확인한다.
<!_- 스프링과 mybatis가 정상적으로 연동되었는지 확인하기 위한 라이브러리
WAS 없이 테스트 가능 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- JDBC 연결을 위한 JDBC 드라이버 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<!-- 스프링에서 mybatis를 사용하기 위한 라이브러리 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!-- MyBatis 로그를 보기 위한 라이브러리 -->
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4</artifactId>
<version>1.16</version>
</dependency>
데이터베이스 연결 테스트
DataSourceTest로 테스트한다.
아래와 같이 Connection 객체가 만들어지는 것을 확인할 수 있어야 한다.
먼저 테이블을 생성한다.
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) -- 게시글 번호 PK 지정
);
STS에서 web.xml에서 한글 설정을 해준다. web.xml은 톰캣의 배포 서술자(Deployment Descriptor)로서 웹 프로젝트의 기본적인 환경설정을 담당한다.
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
화면의 UI를 담당하는 부트스트랩 세팅을 한다. 첨부파일을 다운받아 압축을 풀고 STS에 아래와 같이 파일을 붙여넣는다.
영속계층과 비지니스 계층 생성
프로젝트의 전체 구조가 갖춰졌다면 본격적으로 구현에 들어간다.
비지니스 계층이란 고객의 요구사항이 반영되는 공간이다. 일반적으로 Service라는 이름으로 표현된다. service 인터페이스로 설계를 하고 'xxxServiceImpl'로 구현한다.
비지니스 계층은 DAO와 컨트롤러를 이어주는 다리라고 생각하면 된다.
영속계층은 데이터베이스와 직접적으로 연결되어 데이터베이스관련 작업을 하는 공간이라 생각하면 된다.
영속계층 작업을 하자.
가장 먼저 테이블을 객체화 시키기 위해 VO를 만든다.
getter, setter는 생략했다.
DAO를 생성한다.
public interface BoardDAO {
public void create(BoardVO vo) throws Exception;
public BoardVO read(Integer bno) throws Exception;
public void update(BoardVO vo) throws Exception;
public void delete(Integer bno) throws Exception;
public List<BoardVO> listAll() throws Exception;
}
@Repository
public class BoardDAOImpl implements BoardDAO {
@Inject
private SqlSession session;
private static String namespace = "org.project.mapper.BoardMapper";
@Override
public void create(BoardVO vo) throws Exception {
session.insert(namespace + ".create", 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);
}
@Override
public void delete(Integer bno) throws Exception {
session.delete(namespace + ".delete", bno);
}
@Override
public List<BoardVO> listAll() throws Exception {
return session.selectList(namespace + ".listAll");
}
}
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="org.project.mapper.BoardMapper">
<insert id="create">
insert into tbl_board (title, content, writer)
values(#{title},#{content}, #{writer})
</insert>
<select id="read" resultType="org.project.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>
<select id="listAll" resultType="org.project.domain.BoardVO">
<![CDATA[
select
bno, title, content, writer, regdate, viewcnt
from
tbl_board
where bno > 0
order by bno desc, regdate desc
]]>
</select>
root-context.xml에서 Beans Graph 탭을 선택하여 빈이 제대로 등록되었는지 확인한다.
BoadDAO 테스트
아래와 같이 BoardDAOTest 클래스를 추가하고 위에서 부터 차례로 테스트를 진행해본다.
package org.project.web;
import java.util.List;
import javax.inject.Inject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.project.domain.BoardVO;
import org.project.domain.Criteria;
import org.project.domain.SearchCriteria;
import org.project.persistence.BoardDAO;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/**/root-context.xml" })
public class BoardDAOTest {
@Inject
private BoardDAO dao;
private static Logger logger = LoggerFactory.getLogger(BoardDAOTest.class);
@Test
public void testCreate() throws Exception {
BoardVO board = new BoardVO();
board.setTitle("새로운 글을 넣습니다. ");
board.setContent("새로운 글을 넣습니다. ");
board.setWriter("user00");
dao.create(board);
}
@Test
public void testRead() throws Exception {
logger.info(dao.read(1).toString());
}
@Test
public void testUpdate() throws Exception {
BoardVO board = new BoardVO();
board.setBno(1);
board.setTitle("수정된 글입니다.");
board.setContent("수정 테스트 ");
dao.update(board);
}
@Test
public void testDelete() throws Exception {
dao.delete(1);
}
}
MyBatis 설정파일에서 type alias 설정을 해준다.
<?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.project.domain"/>
</typeAliases>
</configuration>
위와 같이 작성하면 XML Mapper 파일에서 parameterType이나 resultType에 클래스명만 적을 수 있다.
비지니스 계층 구현
비즈니스 계층은 쉽게 말해서 컨트롤러와 DAO 사이의 접착제 역할을 한다.
비즈니스 계층을 구분해서 개발하면 개발의 양이 늘어나는 것은 사실이다. 그럼에도 몇 가지 이유가 있기 때문에 굳이 계층을 분리해서 개발한다.
- 비즈니스 계층은 고객마다 다른 부분을 처리할 수 있는 완충장치 역할을 한다.
- 각 회사마다 다른 로직이나 규칙을 데이터베이스에 무관하게 처리할 수 있는 완충 영역으로 존재할 필요가 있다.
- 컨트롤러와 같은 외부의 호출이 영속계층에 종속적인 상황을 막아준다.
- 만일 컨트롤러가 직접 영속 계층의 데이터베이스를 이용하게 되면 트랜잭션 처리나 예외의 처리 등 모든 로직이 컨트롤러로 집중된다. 비즈니스 계층은 컨트롤러로 하여금 처리해야 하는 일을 분업하게 만들어 준다.
root-context.xml에 service 패키지를 인식할 수 있도록 아래와 같이 입력한다.
<context:component-scan base-package="org.project.service">
</context:component-scan>
아래와 같이 클래스를 생성한다.
package org.project.service;
import java.util.List;
import org.project.domain.BoardVO;
import org.project.domain.Criteria;
import org.project.domain.SearchCriteria;
public interface BoardService {
public void regist(BoardVO board) throws Exception;
public BoardVO read(Integer bno) throws Exception;
public void modify(BoardVO board) throws Exception;
public void remove(Integer bno) throws Exception;
public List<BoardVO> listAll() throws Exception;
}
package org.project.service;
import java.util.List;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import org.project.domain.BoardVO;
import org.project.domain.Criteria;
import org.project.domain.SearchCriteria;
import org.project.persistence.BoardDAO;
@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);
}
@Override
public void modify(BoardVO board) throws Exception {
dao.update(board);
}
@Override
public void remove(Integer bno) throws Exception {
dao.delete(bno);
}
@Override
public List<BoardVO> listAll() throws Exception {
return dao.listAll();
}
}
여기서 비즈니스 계층은 간단한 게시판 CRUD 작업을 하기 때문에 DAO를 이용해서 작업을 실행하는 정도이다.
게시글 등록
게시글 등록을 위해서 컨트롤러를 생성한다. 컨트롤러는 기능당 하나씩 만드는 것이 좋다. 예를 들어 회원, 게시판 기능이 있다면 각각 하나씩 만드는 것이다. 그리고 컨트롤러는 대표적인 경로를 갖는것이 좋다. '/board'라는 대표적인 경로를 하나 가지고 필요에 따라 그 하위로 경로를 만드는 것이다.
아래와 같이 BoardController 만든다.
package org.project.controller;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
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.servlet.mvc.support.RedirectAttributes;
import org.project.domain.BoardVO;
import org.project.domain.Criteria;
import org.project.domain.PageMaker;
import org.project.service.BoardService;
@Controller
@RequestMapping("/board/*")
public class BoardController {
private static final Logger logger = LoggerFactory.getLogger(BoardController.class);
@Inject
private BoardService service;
//return 값이 없으면 자동으로 같은 경로의 있는 view 페이지로 이동된다.
@RequestMapping(value = "/register", method = RequestMethod.GET)
public void registerGET(BoardVO board, Model model) throws Exception {
logger.info("register get ...........");
}
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String registPOST(BoardVO board, RedirectAttributes rttr) throws Exception {
logger.info("regist post ...........");
logger.info(board.toString());
service.regist(board);
//model.addAttribute("result", "success");
//return "/board/success";
//redirect 시점에 한 번만 데이터가 전송되며 새로고침해도 계속 데이터가 넘어가지 않는다.
//URI 상에 보이지 않고 숨겨진 데이터 형태로 전달된다.
rttr.addFlashAttribute("msg", "success");
return "redirect:/board/listAll";
}
}
프로젝트 루트 경로를 '/'로 변경한다.
servlet-context.xml에 아래와 같이 등록한다.
<context:component-scan base-package="org.project.controller" />
View 페이지 구현
아래와 같이 register.jsp 등록 페이지를 등록한다.
아래와 같이 게시글 내용을 입력하고 Submit 버튼을 누르면 게시글이 등록되고 게시글 목록 페이지로 이동한다.
전체목록 구현
BoardController에 아래와 같이 추가한다.
@RequestMapping(value = "/listAll", method = RequestMethod.GET)
public void listAll(Model model) throws Exception {
logger.info("show all list......................");
model.addAttribute("list", service.listAll());
}
listAll.jsp를 추가한다. 페이징 처리와 검색기능은 추후 추가해야한다. 입력한 게시글 목록 전체가 보이고 게시글 클릭 시 조회가 되게 링크를 달았다.
...
<c:forEach items="${list}" var="boardVO">
<tr>
<td>${boardVO.bno}</td>
<td><a href='/board/read?bno=${boardVO.bno}'>${boardVO.title}</a></td>
<td>${boardVO.writer}</td>
<td><fmt:formatDate pattern="yyyy-MM-dd HH:mm"
value="${boardVO.regdate}" /></td>
<td><span class="badge bg-red">${boardVO.viewcnt }</span></td>
</tr>
</c:forEach>
...
조회 구현
BoardController 에 아래와 같이 read 메소드를 추가한다.
@RequestMapping(value = "/read", method = RequestMethod.GET)
public void read(@RequestParam("bno") int bno, Model model) throws Exception {
//model에 이름없이 세팅하면 자동으로 리턴되는 클래스 이름의 첫글자를 소문자로 한 이름을 사용한다.
model.addAttribute(service.read(bno));
}
read.jsp를 추가한다.
삭제/수정 처리
수정페이지와 입력 페이지를 같이 사용할 수 있지만 향후 유지보수를 위해서는 분리하는게 좋다.
수정작업의 경우에는 수정날짜를 기록하는게 좋다.
먼저 삭제작업부터 한다.
삭제는 Post 방식으로 처리한다. 아래와 같이 BoardController에 remove 메소드를 추가한다.
@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";
}
수정처리
BoardController에 modifyGET과 modifyPOST메소드를 추가한다.
@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 {
logger.info("mod post............");
service.modify(board);
rttr.addFlashAttribute("msg", "SUCCESS");
return "redirect:/board/listAll";
}
modify.jsp를 추가한다.
예외 처리
@ControllerAdvice를 통해 예외처리를 해보자. @ControllerAdvice는 메소드에서 발생된 Exception을 처리한다.
CommonExceptionAdvice을 추가한다.
package org.project.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
@ControllerAdvice
public class CommonExceptionAdvice {
private static final Logger logger = LoggerFactory.getLogger(CommonExceptionAdvice.class);
@ExceptionHandler(Exception.class) //파라미터에 Exception 객체의 타입만 사용가능
private ModelAndView errorModelAndView(Exception ex) {
//Model사용불가. ModelAndView를 사용한다.
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("/error_common");
modelAndView.addObject("exception", ex);
return modelAndView;
}
}
위 코드에서는 View 페이지에 Exception 객체를 전달하므로 에러원인을 알 수 있다.
error_common.jsp 를 추가한다.
'IT공부 > 스프링' 카테고리의 다른 글
게시판 페이징 처리 (0) | 2020.07.08 |
---|---|
STS에서 Git 사용하기 (0) | 2020.07.05 |
스프링에서 MyBatis 사용하기 (0) | 2020.07.04 |
스프링 MVC (0) | 2020.07.04 |
MyBatis 연동 (0) | 2020.07.03 |