4/09/2018

(구버전) 스프링 게시판 만들기 #1. 기본 설정 및 작성 기능 구현

'(구버전) 스프링 게시판 만들기'는 내용이 부족하다고 판단하여
스프링 게시판 만들기를 새로 작성하였습니다.

링크 및 참조용으로 현재 게시물은 남겨두겠지만,
가급적이면 새로운 스프링 게시판 만들기를 참조해주시기 바랍니다.

웹 프로젝트의 기본기인 게시판 만들기입니다. '게시판'이라고 말했지만 응용력에 따라 블로그, 갤러리, 쇼핑몰이 될 수 있습니다.

새로운 프로젝트를 생성하고, 기본설정을 끝낸 상태에서 진행합니다.

오라클과 마이바티스 연동
톰캣 서버 추가
한글 설정

오라클로 게시판에 필요한 테이블과 시퀀스를 생성합니다.

테이블은 글 번호, 제목, 내용, 작성자, 작성일자, 조회수로 구성되어있고 시퀀스는 테이블의 글 번호를 자동으로 1씩 증가시켜주는 기능입니다.

create table myBoard (
    bno       number            not null,
    title     varchar2(30)      not null,
    content   varchar2(2000)    not null,
    writer    varchar2(30)      not null,
    regDate   date              default sysdate,
    viewCnt   number            default 0,
    primary key(bno)
);
create sequence myBoard_seq;

또, 주요 쿼리를 먼저 작성해서 직접 실행해봅니다.
게시판에서 기본적이지만 가장 중요한 기능인 작성-조회-수정-삭제입니다.

디벨로퍼에서 작성하고 이상없이 실행되는 쿼리들은 그대로 스프링에 옮겨서 사용합니다.

작업한 내용은 꼭 커밋(commit)시켜서 적용시킵니다. 커밋을 하지 않으면, 지금까지 작업한 모든 내용은 적용되지 않습니다.

쿼리가 정상적으로 작동되면 매퍼에 옮깁니다.
각 기능의 아이디(ID)는 한눈에 알기 쉽고 겹치지 않도록 합니다.

<?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.kuzuro.mappers.boardMapper">
   
    <!-- 작성 -->
 <insert id="write">
  insert into myBoard(bno, title, content, writer)
      values (myBoard_seq.nextval, #{title}, #{content}, #{writer})
 </insert>
 
 <!-- 조회 -->
 <select id="read" resultType="com.kuzuro.domain.BoardVO">
  select bno, title, content, writer, regDate, viewCnt
   from myBoard
    where bno = #{bno}
 </select>

 <!-- 수정 --> 
 <update id="update">
  update myBoard
   set
    title = #{title},
    content = #{content}
   where bno = #{bno}  
 </update>
 
 <!-- 삭제 -->
 <delete id="delete">
  delete from
   myBoard
  where bno = #{bno}
 </delete>
 
</mapper>

자바 코드를 작성하기 전, src/main/java 하위에 용도별로 패키지를 생성합니다.

controller : 프로젝트 생성시 작성한 메인 패키지
domain : VO(Value Object) 패키지
persistence : DAO(Data Access Object) 패키지
service : 서비스 패키지

controller는 프로젝트 생성시 작성한 패키지로, 뷰(사용자가 보는 화면)에서 모든 정보를 받아 자바로 넘겨줍니다.

VO는 값을 가지고있는 객체로서, 데이터 베이스의 테이블과 같은 역할입니다. DTO(Data Transfer Object)와 조금 다르지만 구분없이 사용합니다.

DAO는 이름 그대로 데이터에 접속하는 객체입니다. VO를 기반의 값을 DAO를 통하여 실제 데이터 베이스에 전달합니다.

service는 controller에서 받은 정보를 가공하는 역할입니다.

domain에 BoardVO 클래스를 생성합니다.

테이블을 만들 때 사용했던 쿼리문을 가져와서 주석을 걸어둡니다. 필수는 아니지만, 작업할 때 실수하는 일이 줄어듭니다.

단축키 Alt + Shift + S 또는 상단 메뉴의 Source에서 Generate Getters and Setters를 선택합니다.

모두 선택(Select All)을 누르고 확인(OK)버튼을 클릭합니다.

모든 VO는 이런 방법으로 작성합니다.

package com.kuzuro.domain;

import java.util.Date;

public class BoardVO {
/* 
    bno       number            not null,
    title     varchar2(30)      not null,
    content   varchar2(200)     not null,
    writer    varchar2(30)      not null,
    regDate   date              default sysdate,
    viewCnt   number            default 0,
*/
 private int bno;
 private String title;
 private String content;
 private String writer;
 private Date regDate;
 private int viewCnt;
 
 
 public int getBno() {
  return bno;
 }
 public void setBno(int 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;
 }
 
 
}

Persistence에 인터페이스(BoardDAO)와 구현부(BoardDAOImpl)를 작성합니다. 아직은 기본적인 기능만 있기 때문에 그렇게 복잡하진 않습니다.

package com.kuzuro.persistence;

import com.kuzuro.domain.BoardVO;

public interface BoardDAO {
 
 // 작성
 public void write(BoardVO vo) throws Exception;
 
 // 조회
 public BoardVO read(int bno) throws Exception;
 
 // 수정
 public void update(BoardVO vo) throws Exception;
 
 // 삭제
 public void delete(int bno) throws Exception;
 
}
package com.kuzuro.persistence;

import javax.inject.Inject;
import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;
import com.kuzuro.domain.BoardVO;

@Repository
public class BoardDAOImpl implements BoardDAO {

 // 마이바티스 
 @Inject
 private SqlSession sql;
 
 // 매퍼
 private static String namespace = "com.kuzuro.mappers.boardMapper";
  
 // 작성
 @Override
 public void write(BoardVO vo) throws Exception {
  sql.insert(namespace + ".write", vo);
 }
 // 조회

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

 // 수정
 @Override
 public void update(BoardVO vo) throws Exception {
  sql.update(namespace + ".update", vo);
 }

 // 삭제
 @Override
 public void delete(int bno) throws Exception {
  sql.delete(namespace + ".delete", bno);
 }

}

Service에 Persistence와 마찬가지로 인터페이스(BoardService)와 구현부(BoardServiceImpl)를 작성합니다.

이미 HomeController가 이미 있지만, 게시판만을 위한 전용 컨트롤러를 생성합니다.

package com.kuzuro.controller;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.kuzuro.domain.BoardVO;
import com.kuzuro.service.BoardService;

@Controller
@RequestMapping("/board/*")
public class BoardController {

private static final Logger logger = LoggerFactory.getLogger(BoardController.class);
 
 @Inject
 BoardService service;
 
 // 글 작성 get
 @RequestMapping(value = "/write", method = RequestMethod.GET)
 public void getWrite() throws Exception {
  logger.info("get write");
 }

 // 글 작성 post
 @RequestMapping(value = "/write", method = RequestMethod.POST)
 public String postWrite(BoardVO vo) throws Exception {
  logger.info("post write");
  
  service.write(vo);
  
  return "redirect:/";
 }
 
  
}

@RequestMapping의 값(value)을 통하여 브라우저로 접속할 수 있습니다.
GET은 해당 페이지에 접속, POST는 해당 페이지에서 값을 전송하는 것입니다.

스프링이 추가한 패키지를 인식할 수 있도록, root-context.xml파일에 위 코드를 추가합니다.

 <!-- 패키지 -->

 <context:component-scan base-package="com.kuzuro.domain" />
 <context:component-scan base-package="com.kuzuro.persistence" />
 <context:component-scan base-package="com.kuzuro.service" />

src → main → webapp → WEB-INF → views 하위에 board 폴더를 생성하고, 그 하위에 write.jsp파일을 생성하여 코드를 작성합니다.

이름대로, 이 파일은 게시물을 작성하는 용도입니다.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
 <title>kuzuro 게시판</title>
</head>
<body>

<div id="root">
 <header>
  <h1>kuzuro 게시판</h1>
 </header>

<hr />
 
 <nav>
  처음화면 - 글쓰기 - 로그인
 </nav>

<hr />

 <section id="container">
 
  <form role="form" method="post" autocomplete="off">
   <p>
    <label for="title">글 제목</label><input type="text" id="title" name="title" />
   </p>
   <p>
    <label for="content">글 내용</label><textarea id="content" name="content"></textarea>
   </p>
   <p>
    <label for="writer">작성자</label><input type="text" id="writer" name="writer" />
   </p>
   <p>
    <button type="submit">작성</button>
   </p>  
  </form>

 </section>

<hr />

 <footer>
  <p>만든이 : kuzuro</p>  
 </footer>

</div>

</body>
</html>

home.jsp파일을 수정합니다. 불필요한 코드를 지우고, 첫화면에서 글쓰기 화면으로 바로 이동할 수 있도록 링크를 추가했습니다.

프로젝트를 실행하면, 링크만 하나 덩그러니있는 심심한 화면이 보입니다.
링크를 클릭하면 게시물 작성 화면으로 이동할 수 있습니다.

입력한 값이 제대로 잘 저장되는지 확인하도록, 아무 글이나 적고 작성 버튼을 클릭합니다.

작성 버튼을 누르면, 처음 화면으로 돌아옵니다. 컨트롤러에서 리턴값을 처음 화면으로 했기 때문입니다.

글이 제대로 저장되었는지 확인하려면, 디벨로퍼에서 확인해야합니다. 왜냐하면 아직 조회용 jsp파일을 생성하지 않았으니 말이죠.

입력했던 글이 잘 저장되어있습니다.

게시물 수정
  1. boardController가 동작을 하지않는거 같아요 ㅜㅜ
    home.jsp는 잘 노출이 되는데 글작성을 누르면 404error가 납니다. BoardController의 로그도 안찍히는데,,,
    homeController 의 return부분을 write로 고치면 write.jsp가 노출이되긴하는데 home.jsp에서 글작성눌러서 진입하면 404 ㅜㅜ

    답글삭제
    답글
    1. 아,,,,해결되었습니다.

      삭제
    2. 익명1/09/2020

      어떻게 해결하셨나요?? 똑같은 오류로 고생중입니다 ㅠㅠ

      삭제
    3. 안녕하세요? 방문해주셔서 감사합니다.

      루트 컨텍스트의 패키지, 컨트롤러의 어노테이션등을 확인해보시기 바랍니다.

      삭제
    4. 버전때문인지 무엇때문인지는 모르겠지만 기본프로젝트와 다른점을 찾아보니 패키지인식 root-context.xml에 넣은 부분을 servlet-context.xml의 아랫줄에 넣어주니 해결 되었습니다.(하위X)
      ex)





      삭제
    5. 을 servlet-context.xml에 넣었더니 해결됐습니다.

      삭제
    6. <context:component-scan base-package="com.kuzuro.controller" />
      를 servlet-context.xml에 넣었더니 해결됐습니다.

      삭제
  2. 감사합니다 ㅎ

    답글삭제
  3. 익명1/12/2019

    Service에 Persistence와 마찬가지로 인터페이스(BoardService)와 구현부(BoardServiceImpl)를 작성합니다.
    부분이 누락된것 같습니다. 며칠전에는 보였던것 같은데... 확인 부탁드립니다..

    답글삭제
    답글
    1. 블로그 템플릿 작업중에 수정한된 부분이 문제가 있었네요.
      수정 완료했고, 방문해주셔서 감사합니다.

      삭제
  4. pom.xml 같은 설정파일들에 대한 설명도 참고하고 싶은데 찾지를 못 하겠어요... ㅜㅜㅜㅜ
    깃허브도 제가 사용법을 잘 모르겠어서 그러는데
    공부목적으로 소스공유 부탁드려도 될까요 ?? ㅠㅠㅠㅠ

    답글삭제
    답글
    1. 안녕하세요?

      게시물 상단에 있는 스프링 게시판 만들기 페이지에서 다운받으실 수 있습니다.

      깃허브를 잘 모르시더라도, 위 링크로 이동한 뒤 초록색 clone or download 버튼을 클릭하시고 Download ZIP을 클릭하시면 압축 파일을 받으실 수 있습니다.

      삭제
  5. 임포트해서 돌리면
    The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.
    이게 뜨는데 뭐가 문젤까요? ㅠㅠ

    답글삭제
    답글
    1. 저도 이렇게 뜨는데 해결하셨나요?

      삭제
    2. 서블릿 또는 서버에 대한 설정이 누락되어 생기는 에러로 기억합니다. 서블릿에 올바른 빈이 등록되어있는지 확인해보시고 서버의 루트컨텍트스가 비어있는지, 비어있다면 /를 입력하시면 해결되리라 봅니다.

      삭제
    3. root-context.xml 에




























      이렇게 해놨는데 잘못된게 있나요 ? 답변감사드립니다.

      삭제
    4. 글이 안올라가네요 ㅜ ...




      데이타 소스가 잘못된건가요 ㅠ

      삭제
  6. 익명3/27/2019

    자세한 설명 정말 감사합니다. 공부에 많은 도움이되었어요 설명을 이해가 잘 되게 해주셨네요.

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사하니다.부족한것 같아 좀 더 상세하게 다시 작성할 예정이었는데, 도움이 되셨다니 다행입니다.

      삭제
  7. 익명5/23/2019

    DAO에서 public void write(BoardVO vo) throws Exception; 이부분과
    구현에서 @Override
    public void write(BoardVO vo) throws Exception {
    sql.insert(namespace + ".write", vo);
    }

    에서 write 메소드(BoardVo vo)를 해준이유가 무엇인가요?

    답글삭제
    답글
    1. 안녕하세요?

      write 메서드에 매개변수로 BoardVO vo를 넣어준 이유 말씀이시라면...
      말그대로, write 메서드에 BoardVO형태의 데이터를 전달해주기 위함입니다.

      form태그에 내부에 데이터를 입력했으니, 그 데이터를 DB에 넣어야하겠지요?
      form태그의 입력요소(input, textarea)는 이름은 BoardVO의 이름을 부여하여, 컨트롤러러로 데이터가 전달될 때 BoardVO의 형태가 됩니다.

      컨트롤러에선 이를 받고, 서비스와 DAO에 전달해야하는데 BoardVO형태를 따로 가공할 필요가 없이 그대로 전달하면 됩니다.

      삭제
  8. Error creating bean with name 'dataSource' defined in ServletContext resource [/WEB-INF/spring/root-context.xml]: Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:


    서버 실행하면 이런 에러가 뜨는데 뭐가문제 일까요?

    답글삭제
    답글
    1. Cannot find class [org.mybatis.spring.SqlSessionFactoryBean] for bean with name 'sqlSessionFactory' defined in ServletContext resource [/WEB-INF/spring/root-context.xml]; nested exception is java.lang.ClassNotFoundException: org.mybatis.spring.SqlSessionFactoryBean


      이런에러가뜨네요 ..하

      삭제
    2. 안녕하세요? 방문해주셔서 감사합니다.
      에러 메시지를 보니 root-context의 SqlSessionFactory에 문제가 있는것 같습니다.
      정상적으로 잘 입력되었음에도 계속해서 에러가 발생하는 경우, 상단 메뉴의 프로젝트 - 프로젝트 클린을 이용해 잡파일(?)을 제거해주시면 될것 같습니다.

      삭제
    3. 그래도 에러가 지워지질 않습니다 clean말고는 방법이 없나요?

      삭제
    4. mapper에 주석을 제거하니 오류가 해결되었습니다

      삭제
  9. org.mybatis.spring.MyBatisSystemException 오류 어떻게 해결해야하나요..

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      에러 내용을 좀 더 봐야할 필요가 있는것 같은데.. 제가 너무 늦게 봤네요ㅠㅠ 죄송합니다

      삭제
  10. 익명4/10/2020

    인텔리제이로 해도 될까요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      인텔리제이로 하셔도 괜찮습니다.

      삭제
    2. 익명4/10/2020

      감사합니다!

      삭제
  11. 제가 아주 초보라 ㅠㅠ 질문 한번 드려봅니다..
    write.jsp에서 submit 누르면 write.jsp요청에 해당하는 BoardController에서 @RequestMapping(value = "/write" 부분 따라가고 Controller는 BoardService를 상속받은 BoardServiceImpl에 있는 write(vo)값을 받아와서 넘겨주고, BoardServiceImpl은 그 값을 BoardDAO를 상속받은 BoardDAOImpl에 있는 write(vo) 값을 전달해주고, 이 값을 Mapper에 전달해줘서 SQL문장을 실행한다. 라고 이해하면 되는건가요?



    그리고 또 왜 write controller에는 get,post 두가지가 있고 getWrite랑 postWrite의 차이는 어떤건지 간략하게 설명 부탁드릴수 있을까요?

    정말 좋은 게시물 열심히 항상 공부하고갑니다

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      1. 페이지 주소를 입력 또는 링크를 통해 /board/write 에 접속
      2. 컨트롤러중 /board/ 와 매칭되는 컨트롤러를 검색
      3. /board/와 매칭되는 컨트롤러중 write 와 매칭되는 메서드를 검색
      4. write 와 매칭되는 메서드중 get 타입을 검색
      5. 4번에 해당되는 메서드의 코드를 실행하고, 해당 경로에 맞는 jsp화면(/board/write) 출력

      이라고 보시면 됩니다.


      여기서 get과 post가 나누어진 이유는.. 항상 그런건 아니지만, 용도의 차이라고 보시면 되겠습니다.
      화면을 출력하는 용도(단순 페이지 접속)는 get
      화면에서 데이터를 전송하는 용도(로그인, 게시물 쓰기등의 데이터 전송)는 post

      삭제
    2. 캄사합니다 ㅠ,.ㅠ

      삭제
  12. https://okky.kr/article/830838 이런 오류가 뜨는데 어떻게 하나요 ㅠ

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      블로그 본문에서 복사→붙여넣기 하셔서 에러가 발생한 경우
      마이바티스 사이트에서 해당 코드를 복사→붙여넣기 해보시면 될겁니다..라고 하려 했는데

      이미 해결된거같네요;

      삭제
  13. 작성자가 댓글을 삭제했습니다.

    답글삭제