Spring MVC 기본 crud 코드 정리

2019-10-06

제가 나름대로 공부한 내용을 정리해보겠습니다.

Mybatis + MySQL + Eclipse + Spring MVC 기반입니다.

연동과정에 대해서는 적어놓지 않았습니다.

popit이나 여러 블로그들을 보고 공부했습니다.

전체코드는 깃허브에서 보실 수 있습니다.

레이아웃 JSP

<a href="/regist" class="btn btn-primary">글쓰기</a>

버튼을 만들고 같은 폴더에 regist.jsp를 생성합니다.

Bootstrap의 기능을 사용하고 있기때문에 CDN을 넣지 않으시면 제대로 동작하지 않으니 참고하세요.

<!--regist.jsp-->
<body>
<form method="post">
	<div class="container form-group">
		<div class="row">
			<div class="col-lg-8">
			<input class="form-control" type="text" name ="title" placeholder="제목">
			<input class="form-control" type="text" name ="writer" placeholder="작성자">
			<textarea class="form-control" name="content"></textarea>
			<button type ="submit" class="btn btn-primary">등록</button>
			</div>
		</div>
	</div>
</form>
</body>

이번엔 작성한 글이 올라오는 list.jsp를 생성합니다.

<a class="nav-link" href="list">글목록</a>
<!--list.jsp-->
<body>
	<c:forEach items="${list}" var="boardVO">
	<div class="box card bg-white rounded">
		<div class="card-body">
			<p class="font-weight-bold"><a href='/read?bno=${boardVO.bno}'>${boardVO.title}</a></p>
            <div class="h5 m-0">${boardVO.writer}</div>
			<p class="font-weight-bold">${boardVO.content}</p>
			<p class="text"><fmt:formatDate pattern="yyyy-MM-dd HH:mm" value="${boardVO.regdate}"/></p>
			<p>${boardVO.viewcnt}</p>
		</div>
	</div>
</form>
</body>

게시물을 누르면 읽을 수 있게 read.jsp를 만듭니다.

<!--read.jsp-->
<body>
<form>
<div class="form-group">
<input class="form-control" type="text" name ="bno" value ="${boardVO.bno}" readonly="readonly">
<h1 class="mt-4">${boardVO.title}</h1>
<p class="lead">${boardVO.writer}</p>
</div>
<textarea class="form-control" name="content" readonly="readonly">${boardVO.content}</textarea>

<button type="submit" class="btn btn-warning" formaction="modify" formmethod="get">수정</button>
<button type="submit" class="btn btn-danger" formaction="remove" formmethod="post">삭제</button>
<button type="submit" class="btn btn-primary" formaction="listAll" formmethod="get">목록</button>
</form>
</body>

삭제는 Url로 요청만 하면 되니 수정 페이지만 만들어줍니다.

<!--modify.jsp-->
<!DOCTYPE html>
<html>
<body>
<form action="modify" method="post">
<div class="form-group">
글번호<input class="form-control" type="text" name ="bno" value ="${boardVO.bno}" readonly>
제목<input class="form-control" type="text" name ="title" value ="${boardVO.title}" >
작성자<input class="form-control" type="text" name="writer" value = "${boardVO.writer}">
<textarea class="form-control" name=content>${boardVO.content}</textarea>
<br>
<button type ="submit" class="btn btn-primary">완료</button>
</div>
</form>
</body>
</html>

UI는 완성했으니 이제 DB에 테이블을 만들어야 합니다.

MySQL에 이렇게 기본적인 내용만 만들어 주었습니다.

create table tbl_board(
bno INT NOT NULL AUTO_INCREMENT,
title VARCHAR(200) NOT NULL,
content VARCHAR(2000) NOT NULL,
writer VARCHAR(100) NOT NULL,
regdate TIMESTAMP NOT NULL default NOW(),
viewcnt INT DEFAULT 0,
PRIMARY KEY(bno)
);

Package, BoardVO

그리고 이제 스프링에 패키지를 만들고 하나씩 필요한 것들을 만들어봅시다.

메인 폴더 오른쪽 클릭하여 패키지를 아래 사진의 이름과 같이 만들어줍니다.

그리고 com.popit.domain을 오른쪽 클릭 후 New -> Class 클릭하여 BoardVO라는 이름으로 .java파일을 하나 생성합니다.

그리고 나서 아래와 같이 코드를 작성합니다.

public class BoardVO {
	private Integer bno;
	private String title;
	private String content;
	private String writer;
	private Date regdate;
	private int viewcnt;
}

작성 후 코드가 있는 화면에 대고 오른쪽 클릭을 하여 Source -> Generate getters and setters…를 클릭합니다.

Select All 후 아래에 Generate로 생성합니다.

마찬가지로 Source -> Generate toString()…을 눌러 생성해주면 아래와 같은 코드가 완성됩니다.

참고로 import 코드는 올리지 않았습니다.

Ctrl + Shift + O를 하거나 오류가 나는 곳에 마우스를 올리시면 쉽게 import 가능합니다.

public class BoardVO {
	private Integer bno;
	private String title;
	private String content;
	private String writer;
	private Date regdate;
	private int viewcnt;
	
	public Integer getBno() {
		return bno;
	}
	public void setBno(Integer bno) {
		this.bno = bno;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	public Date getRegdate() {
		return regdate;
	}
	public void setRegdate(Date regdate) {
		this.regdate = regdate;
	}
	public int getViewcnt() {
		return viewcnt;
	}
	public void setViewcnt(int viewcnt) {
		this.viewcnt = viewcnt;
	}
	@Override
	public String toString() {
		return "BoardVO [bno=" + bno + ", title=" + title + ", content=" + content + ", writer=" + writer + ", regdate="
				+ regdate + ", viewcnt=" + viewcnt + "]";
	}

}

DTO(=VO)와 DAO의 차이

DTO(=VO)는 gettersetter를 가진 클래스를 의미한다.

하지만 VO는 DTO와 다르게 readonly 속성을 가진다.

DAO는 데이터베이스에 접근하기 위해 만드는 클래스이다.

BoardDAO, Mapper

이번에는 com.popit.persistence라는 패키지를 생성 후 Class가 아닌 Interface로 BoardDAO.java를 생성한다. 생성부터 만들거지만 나중에 추가하기 귀찮으니까 아래와 같이 미리 CRUD 코드를 다 추가해놓는다.

public interface BoardDAO {
	public void create(BoardVO boardvo) throws Exception;
	
	public BoardVO read(Integer bno) throws Exception;
	
	public void update(BoardVO boardvo) throws Exception;
	
	void updateViewCnt(Integer bno) throws Exception;
	
	public void delete(Integer bno) throws Exception;
	
	public List<BoardVO> listAll() throws Exception;
}

그런 다음 BoardDAOImple.java를 만들어야 하는데 New -> Class를 누른 후 Interface 탭에서 Add를 눌러 방금 생성한 BoardDAO를 추가한다.

그러면 비어있는 함수를 다 만들어준다.

그 안에 로직을 구현할 코드만 넣어주면 된다.

@Repository는 데이터베이스와 관련한 클래스에 넣어준다.

@Repository
public class BoardDAOImpl implements BoardDAO {
	
	@Inject
	private SqlSession session;
	
	private static String namespace = "com.popit.mapper.BoardMapper";
	
	@Override
	public void create(BoardVO boardvo) throws Exception {
		session.insert(namespace+".create", boardvo);
	}

	@Override
	public BoardVO read(Integer bno) throws Exception {
		return session.selectOne(namespace + ".read", bno);
	}

	@Override
	public void update(BoardVO boaradvo) throws Exception {
		session.update(namespace+".update", boaradvo);
	}
	
	@Override
	public void updateViewCnt(Integer bno) throws Exception{
		session.update(namespace + ".updateViewCnt", bno);
	}

	@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");
	}
}

Mapper를 작성할때 #{}안에 있는 값은 본인이 선언한 변수를 의미한다.

그냥 써있는건 SQL에서 정의한 이름이다.

title은 SQL에서 정의한거 #{title}은 VO에서 선언한 값

그리고 #{}은 SQL Injection을 예방할 수 있다고 한다.

Mybatis를 통해 조금 더 편하게 DB를 다룰 수 있다.

물론 최근에는 JPA를 더 사용하는 것 같다.

<?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="com.popit.mapper.BoardMapper">

<insert id="create">
insert into tbl_board (title, content, writer)
values(#{title}, #{content}, #{writer})
</insert>

<select id="read" resultType="com.popit.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>

<update id="updateViewCnt">
update tbl_board
set viewcnt = viewcnt + 1
where bno = #{bno}
</update>

<delete id="delete">
delete from tbl_board where bno = #{bno}
</delete>

<select id="listAll" resultType="com.popit.domain.BoardVO">
<![CDATA[
select bno, title, content, writer, regdate, viewcnt
from tbl_board
where bno > 0 order by bno desc, regdate desc
]]>
</select>
</mapper>

BoardService

지금까지와 동일하게 com.popit.service라는 패키지를 만들고 BoardService라는 이름의 인터페이스를 생성합니다.

public interface BoardService {
	public void regist(BoardVO boardvo) throws Exception;

	public BoardVO read(Integer bno) throws Exception;

	public void modify(BoardVO boardvo) throws Exception;

	public void remove(Integer bno) throws Exception;

	public List<BoardVO> listAll() throws Exception;
}

그리고 BoardServiceImpl이라는 클래스를 만들고 생성할때 BoardService를 Add해서 자동으로 생성해줍니다.

@Service는 비즈니스 로직과 관련한 곳에 넣어준다.

@Service
public class BoardServiceImpl implements BoardService {
	
	@Inject
	private BoardDAO boarddao;

	@Override
	public void regist(BoardVO boardvo) throws Exception {
		boarddao.create(boardvo);
	}

	@Override
	public BoardVO read(Integer bno) throws Exception {
		boarddao.updateViewCnt(bno);
		return boarddao.read(bno);
	}

	@Override
	public void modify(BoardVO boardvo) throws Exception {
		boarddao.update(boardvo);
	}
	
	@Override
	public void remove(Integer bno) throws Exception {
		boarddao.delete(bno);
	}

	@Override
	public List<BoardVO> listAll() throws Exception {
		return boarddao.listAll();
	}
}

BoardController

이제 Controller를 만들면 끝난다.

@ControllerBoardController를 웹 컨트롤러로 사용하겠다고 선언한다.

@RequestMapping은 URL을 라우팅 해준다.

@Inject는 의존성 주입을 위해 사용하는데 @Autowired와 다른점은 자바에서 지원하는지 혹은 스프링에서 추가된 어노테이션이냐의 차이점 뿐이다.

@RequestParam을 통해 url에 받아올 값을 넣어서 사용할 수 있다.

@pathvariable라는 것도 있다. 한 어노테이션이 우위에 있다기 보다는 같이 사용한다고 한다.

model.addAttribute("list",service.listAll());의 원형은 아래와 같다.

model.addAttribute(String name, Object value) 그러면 저 안에 있는 “list”는 어디서 사용하는 걸까

list.jsp에 가보면 이런 코드가 있다. items="${list}" 이 코드는 반복을 하면서 리스트에 있는 값들을 가져온다. 즉 addAttribute에서 name을 정의하면 그걸 jsp에서 가져다 사용할 수 있게 되는 것이다.

@Controller
@RequestMapping(value = "/")
public class BoardController {	

	@Inject
	private BoardService service;

	@RequestMapping(value= "/listAll", method = RequestMethod.GET)
	public void listAll(Model model) throws Exception {
		model.addAttribute("list",service.listAll());
	}

	@RequestMapping(value = "/regist", method = RequestMethod.GET)
	  public void registerGET(BoardVO boardvo, Model model) throws Exception {
	}

	@RequestMapping(value = "/regist", method = RequestMethod.POST)
	  public String registPOST(BoardVO boardvo, RedirectAttributes rttr) throws Exception {
		service.regist(boardvo);
	    return "redirect:/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 = "/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 boardvo, RedirectAttributes rttr) throws Exception {
	    service.modify(boardvo);
	    return "redirect:/listAll";
	  }

	  @RequestMapping(value = "/remove", method = RequestMethod.POST)
	  public String removePOST(@RequestParam("bno") int bno, RedirectAttributes rttr) throws Exception{
		  service.remove(bno);
		  return "redirect:/listAll";
	  }
}

참고 스프링의 기본 구조