[Spring] 게시판 생성

Posted by 신희준 on October 23, 2017


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