ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Camel][Spring] 게시판 만들기 #7-1. 댓글처리 구현
    Spring/게시판 만들기 2020. 6. 27. 16:55

    [Camel][Spring] 게시판 만들기 #7-1. 댓글처리 구현

     

    본 게시판 만들기 프로젝트는 더블에스 Devlog Spring-MVC 를 참조하여 작성했음을 알려드립니다. 또한 개인적인 학습을 목적으로한 포스팅이기 때문에 완벽하지 않을 수 있음을 알려드립니다. 문제점이나 궁금한점은 댓글로 남겨주시면 감사하겠습니다. 프로젝트 생성에 앞서 이번 게시판 만들기 프로젝트는 이클립스를 사용하여 구현하였습니다. 

     

    댓글 처리를 구현하기에 앞서 댓글 처리는 Rest 방식을 사용해서 구현할 것을 미리 알려드립니다.

    Rest 방식에 대한 설명은 아래의 글에서 확인 가능합니다. 

    2020/06/28 - [Spring/개념 정리] - [Camel][Spring] REST 란? Controller와 REST Controller의 차이점은?

     

    [Camel][Spring] REST 란? Controller와 REST Controller의 차이점은?

    [Camel][Spring] REST 란? Controller와 REST Controller의 차이점은? 1. REST  REST는 REpresentational State Transfer의 약어로서 부수적인 레이어나 세션 관리를 추가하지 않고도 HTTP프로토콜로..

    cameldev.tistory.com

     

    1. 댓글 테이블 생성

    그리고 다음으로는 댓글에 대한 Data를 저장할 테이블을 생성하도록 하겠습니다. 댓글은 하나의 게시글에 여러개 존재할 수 있는 1:N 관계를 가지기 때문에 댓글 테이블을 생성한 뒤 article 테이블의 article_no를 참조하여 댓글 참조키를 설정해주어야 합니다. 

     

     이에 따른 SQL문은 아래와 같습니다. SQL문은 작성환경에 따라 조금씩 상이할 수 있음을 알려드립니다. 

    CREATE TABLE 스키마명.tb_reply (
      reply_no INT NOT NULL AUTO_INCREMENT,
      article_no INT NOT NULL DEFAULT 0,
      reply_text VARCHAR(1000) NOT NULL,
      reply_writer VARCHAR(50) NOT NULL,
      reg_date TIMESTAMP NOT NULL DEFAULT NOW(),
      update_date TIMESTAMP NOT NULL DEFAULT NOW(),
      PRIMARY KEY (reply_no)
    );
    
    ALTER TABLE 스키마명.tb_reply ADD CONSTRAINT FK_ARTICLE
    FOREIGN KEY (article_no) REFERENCES 스키마명.tb_article (article_no);

     

    저는 MySQL Workbench를 사용하여 위의 SQL문을 직접입력하지 않고 아래와 같은 방법으로 댓글 테이블을 생성하였습니다. 

     

     

     

    2.  ReplyVO 생성 및 작성

    댓글 테이블을 생성했다면 생성한 테이블의 구조를 객체화 시키기 위해 ReplyVO 클래스를 src/main/java/기본패키지/domain 경로에 생성한 후 아래와 같이 작성해 줍니다. 

    package com.cameldev.mypage.domain;
    
    import java.sql.Date;
    
    public class ReplyVO {
    
        private Integer reply_no;
        private Integer article_no;
        private String reply_text;
        private String reply_writer;
        private Date reg_date;
        private Date update_date;
    
        @Override
        public String toString() {
          return "ReplyVO [reply_no=" + reply_no + 
                        ", article_no="+ article_no + 
                        ", reply_text="+ reply_text + 
                        ", reply_writer="+ reply_writer + 
                        ", reg_date="+ reg_date + 
                        ", update_date="+ update_date+ "]" ;
        }
        
        // Getter, Setter는 가독성을 위해서 생략했습니다. 
        // 반드시 추가해주시길 바랍니다. 
    
    }

     

    3. ReplyDAO, ReplyDAOImpl, replyMapper 생성 및 작성 (Persistence 계층 구현)

    3-1. ReplyDAO 인터페이스 작성

    /src/java/main/기본패키지/persistence 패키지에 ReplyDAO인터페이스를 생성하고 아래와 같이 작성합니다. 

    package com.cameldev.mypage.persistence;
    
    import java.util.List;
    
    import com.cameldev.mypage.domain.ReplyVO;
    
    public interface ReplyDAO {
    
        List<ReplyVO> list(Integer article_no) throws Exception;
    
        void create(ReplyVO replyVO) throws Exception;
    
        void update(ReplyVO replyVO) throws Exception;
    
        void delete(Integer reply_no) throws Exception;
    
    }

     

    3-2. ReplyDAOImpl 클래스 작성

    이어서 /src/java/main/기본패키지/persistence 패키지에 ReplyDAOImpl 클래스를 생성하고 아래와 같이 작성합니다. 

    package com.cameldev.mypage.persistence;
    
    import java.util.List;
    
    import javax.inject.Inject;
    
    import org.apache.ibatis.session.SqlSession;
    import org.springframework.stereotype.Repository;
    
    import com.cameldev.mypage.domain.ReplyVO;
    
    @Repository
    public class ReplyDAOImpl implements ReplyDAO {
    
        private static String NAMESPACE = "com.cameldev.mypage.mappers.reply.ReplyMapper";
    
        private final SqlSession sqlSession;
    
        @Inject
        public ReplyDAOImpl(SqlSession sqlSession) {
            this.sqlSession = sqlSession;
        }
    
        @Override
        public List<ReplyVO> list(Integer article_no) throws Exception {
            return sqlSession.selectList(NAMESPACE + ".list", article_no);
        }
    
        @Override
        public void create(ReplyVO replyVO) throws Exception {
            sqlSession.insert(NAMESPACE + ".create", replyVO);
        }
    
        @Override
        public void update(ReplyVO replyVO) throws Exception {
            sqlSession.update(NAMESPACE + ".update", replyVO);
        }
    
        @Override
        public void delete(Integer reply_no) throws Exception {
            sqlSession.delete(NAMESPACE + ".delete", reply_no);
        }
    }
    

     

    3-3. replyMapper.xml 작성

    src/main/resources/mappers/reply 경로에 replyMappe.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="com.cameldev.mypage.mappers.reply.ReplyMapper">
    	
    	<select id="list" resultMap="ReplyResultMap">
            SELECT
              reply_no
              , article_no
              , reply_text
              , reply_writer
              , reg_date
              , update_date
            FROM tb_reply
            WHERE article_no = #{article_no}
            ORDER BY reply_no
        </select>
    
        <insert id="create">
            INSERT INTO tb_reply (
                article_no
                , reply_text
                , reply_writer
            ) VALUES (
                #{article_no}
                , #{reply_text}
                , #{reply_writer}
            )
        </insert>
    
        <update id="update">
            UPDATE tb_reply
            SET
                reply_text = #{reply_text}
                , update_date = NOW()
            WHERE reply_no = #{reply_no}
        </update>
    
        <delete id="delete">
            DELETE FROM tb_reply
            WHERE reply_no = #{reply_no}
        </delete>
    
        <resultMap id="ReplyResultMap" type="ReplyVO">
            <id property="reply_no" column="reply_no"/>
            <result property="article_no" column="article_no"/>
            <result property="reply_text" column="reply_text"/>
            <result property="reply_writer" column="reply_writer"/>
            <result property="reg_date" column="reg_date"/>
            <result property="update_date" column="update_date"/>
        </resultMap>
    
        <resultMap id="ArticleResultMap" type="ArticleVO">
            <id property="article_no" column="article_no"/>
            <result property="title" column="title"/>
            <result property="content" column="content"/>
            <result property="writer" column="writer"/>
            <result property="regDate" column="regdate"/>
            <result property="viewCnt" column="viewcnt"/>
        </resultMap>
       
    
    </mapper>

     

    추가적으로 replyMapper.xml에서 resultMap이나 resultType을 패키지 경로와 클래스명을 사용할 때 편리하게 사용하기 위해 mybatis-config.xml 파일을 수정하여 ReplyVO 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>
            <typeAlias type="com.cameldev.mypage.domain.ArticleVO" alias="ArticleVO" />
            <typeAlias type="com.cameldev.mypage.domain.ReplyVO" alias="ReplyVO" />
        </typeAliases>
    
    </configuration>

     

     

    4. ReplyService, ReplyServiceImpl 생성 및 작성 (Service 계층 구현)

    4-1. ReplyService인터페이스 작성

    /src/java/main/기본패키지/service 패키지에 ReplyService 인터페이스를 생성하고 아래와 같이 작성합니다. 

    package com.cameldev.mypage.service;
    
    import java.util.List;
    
    import com.cameldev.mypage.domain.ReplyVO;
    
    public interface ReplyService {
    
        List<ReplyVO> list(Integer article_no) throws Exception;
    
        void create(ReplyVO replyVO) throws Exception;
    
        void update(ReplyVO replyVO) throws Exception;
    
        void delete(Integer reply_no) throws Exception;
    
    }

     

    4-2. ReplyServiceImpl 클래스 작성

    이어서 /src/java/main/기본패키지/service 패키지에 ReplyServiceImpl 클래스를 생성하고 아래와 같이 작성합니다. 

    package com.cameldev.mypage.service;
    
    import java.util.List;
    
    import javax.inject.Inject;
    
    import org.springframework.stereotype.Service;
    
    import com.cameldev.mypage.domain.ReplyVO;
    import com.cameldev.mypage.persistence.ReplyDAO;
    
    @Service
    public class ReplyServiceImpl implements ReplyService {
    
        private final ReplyDAO replyDAO;
    
        @Inject
        public ReplyServiceImpl(ReplyDAO replyDAO) {
            this.replyDAO = replyDAO;
        }
    
        @Override
        public List<ReplyVO> list(Integer article_no) throws Exception {
            return replyDAO.list(article_no);
        }
    
        @Override
        public void create(ReplyVO replyVO) throws Exception {
            replyDAO.create(replyVO);
        }
    
        @Override
        public void update(ReplyVO replyVO) throws Exception {
            replyDAO.update(replyVO);
        }
    
        @Override
        public void delete(Integer reply_no) throws Exception {
            replyDAO.delete(reply_no);
        }
    }

     

     

    3. ReplyController 생성 및 작성

    포스팅 초반에 언급했다시피 ReplyController는 Rest방식을 사용해서 구현하도록 하겠습니다. Rest방식의 컨트롤러를 작성하기에 앞서 브라우저에서 PUT, PATCH, DELETE 전송방식을 지원하기 위해서는 web.xml 파일에 필터를 추가해 줘야합니다. 

     

    3-1. web.xml 수정

    브라우저에 따라서 PUT, PATCH, DELETE 전송방식을 지원하지 않을 수 있기 때문에 브라우저에서 POST 방식으로 전송하지만, 추가적인 정보를 이용해 PUT, PATCH, DELETE와 같은 정보를 함께 전송하는 Overloaded POST를 사용하겠습니다. Overloaded POST는 <form> 태그를 사용해 데이터를 전송할 때 POST 방식으로 전송하고, 추가정보로 _method를 사용합니다. 

     Spring에서는 이러한 방식을 위해 HiddenHttpMethodFilter라는 것을 제공하고 있습니다. <form>태그 내에 <input type="hidden" name="_method" value="PUT">와 같은 형태로 사용함으로써 GET, POST방식만을 지원하는 브라우저에서 REST방식을 사용할 수 있게 되는 것입니다.

     

    web.xml 파일에 아래와 같은 코드를 추가해주겠습니다. 

    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

     

     

    3-2. ReplyController 작성

    /src/main/java/기본패키지/controller 패키지에 ReplyController 생성한 뒤 아래의 코드와 같이 작성해줍니다. 

    package com.cameldev.mypage.controller;
    
    import java.util.List;
    
    import javax.inject.Inject;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.cameldev.mypage.domain.ReplyVO;
    import com.cameldev.mypage.service.ReplyService;
    
    @RestController
    @RequestMapping("/replies")
    public class ReplyController {
    
        private final ReplyService replyService;
    
        @Inject
        public ReplyController(ReplyService replyService) {
            this.replyService = replyService;
        }
        
        //Reply Register
        @RequestMapping(value = "", method = RequestMethod.POST)
        public ResponseEntity<String> register(@RequestBody ReplyVO replyVO) {
            ResponseEntity<String> entity = null;
            try {
                replyService.create(replyVO);
                entity = new ResponseEntity<String>("regSuccess", HttpStatus.OK);
            } catch (Exception e) {
                e.printStackTrace();
                entity = new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
            }
            return entity;
        }
        
        //Reply List
        @RequestMapping(value = "/all/{articleNo}", method = RequestMethod.GET)
        public ResponseEntity<List<ReplyVO>> list(@PathVariable("article_no") Integer article_no) {
            ResponseEntity<List<ReplyVO>> entity = null;
            try {
                entity = new ResponseEntity<List<ReplyVO>>(replyService.list(article_no), HttpStatus.OK);
            } catch (Exception e) {
                e.printStackTrace();
                entity = new ResponseEntity<List<ReplyVO>>(HttpStatus.BAD_REQUEST);
            }
            return entity;
        }
        
        //Reply Modify
        @RequestMapping(value = "/{reply_no}", method = {RequestMethod.PUT, RequestMethod.PATCH})
        public ResponseEntity<String> update(@PathVariable("reply_no") Integer reply_no, @RequestBody ReplyVO replyVO) {
            ResponseEntity<String> entity = null;
            try {
                replyVO.setReply_no(reply_no);
                replyService.update(replyVO);
                entity = new ResponseEntity<String>("modSuccess", HttpStatus.OK);
            } catch (Exception e) {
                e.printStackTrace();
                entity = new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
            }
            return entity;
        }
        
        //Reply Delete
        @RequestMapping(value = "/{reply_no}", method = RequestMethod.DELETE)
        public ResponseEntity<String> delete(@PathVariable("reply_no") Integer reply_no) {
            ResponseEntity<String> entity = null;
            try {
                replyService.delete(reply_no);
                entity = new ResponseEntity<String>("delSuccess", HttpStatus.OK);
            } catch (Exception e) {
                e.printStackTrace();
                entity = new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
            }
            return entity;
        }
    }

     

    4. 댓글 페이징 처리

    게시글 목록에서 페이징 처리를 해주었던 것과 마찬가지로 댓글 목록 역시 페이징 처리를 하도록 하겠습니다. 그러기 위해서 게시글 페이징 처리를 구현할 때 사용했던 Criteria 클래스를 사용하겠습니다. 

     

    4-1. ReplyDAO 인터페이스

    먼저 ReplyDAO 인터페이스에 댓글 수를 계산하는 메소드와 페이징 처리된 댓글 목록을 보여주기 위한 추상메소드를 추가해줍니다. 

    List<ReplyVO> listPaging(Integer article_no, Criteria criteria) throws Exception;
    
    int countReplies(Integer article_no) throws Exception;

     

    4-2. ReplyDAOImpl 클래스

     ReplyDAO 인터페이스에서 추가해주었던 추상메소드를 ReplyDAOImpl 클래스에 오버라이딩하여 메소드를 추가해줍니다. 

    @Override
    public List<ReplyVO> listPaging(Integer article_no, Criteria criteria) throws Exception {
    
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("article_no", article_no);
        paramMap.put("criteria", criteria);
    
        return sqlSession.selectList(NAMESPACE + ".listPaging", paramMap);
    }
    
    @Override
    public int countReplies(Integer article_no) throws Exception {
        return sqlSession.selectOne(NAMESPACE + ".countReplies", article_no);
    }

     

    4-3. replyMapper.xml 

    replyMapper.xml에 페이징 처리와 댓글 수와 관련됨 SQL문을 추가해 줍니다. 

    <select id="listPaging" resultMap="ReplyResultMap">
        SELECT
            reply_no
            , article_no
            , reply_text
            , reply_writer
            , reg_date
            , update_date
        FROM tb_reply
        WHERE article_no = #{article_no}
        ORDER BY reply_no
        LIMIT #{criteria.pageStart}, #{criteria.perPageNum}
    </select>
    
    <select id="countReplies" resultType="int">
        SELECT
            COUNT(article_no)
        FROM tb_reply
        WHERE article_no = #{article_no}
    </select>

     

    4-4. ReplyService 인터페이스

    ReplyService 인터페이스에도 댓글 수를 계산하는 메소드와 페이징 처리된 댓글 목록을 보여주기 위한 추상메소드를 추가해줍니다. 

    List<ReplyVO> getRepliesPaging(Integer article_no, Criteria criteria) throws Exception;
    
    int countReplies(Integer article_no) throws Exception;

     

    4-5. ReplyServiceImpl 클래스

    ReplyService 인터페이스에서 추가해주었던 추상메소드를 ReplyServiceImpl 클래스에 오버라이딩하여 메소드를 추가해줍니다. 

    @Override
    public List<ReplyVO> getRepliesPaging(Integer article_no, Criteria criteria) throws Exception {
        return replyDAO.listPaging(article_no, criteria);
    }
    
    @Override
    public int countReplies(Integer article_no) throws Exception {
        return replyDAO.countReplies(article_no);
    }

     

    4-6. ReplyController

    페이징 처리한 목록을 보여주기위해 컨트롤러를 수정해줍니다.

    //Reply Paging List
        @RequestMapping(value = "/{article_no}/{page}", method = RequestMethod.GET)
        public ResponseEntity<Map<String, Object>> listPaging(@PathVariable("article_no") Integer article_no,
                                                              @PathVariable("page") Integer page) {
    
            ResponseEntity<Map<String, Object>> entity = null;
    
            try {
    
                Criteria criteria = new Criteria();
                criteria.setPage(page);
    
                List<ReplyVO> replies = replyService.getRepliesPaging(article_no, criteria);
                int repliesCount = replyService.countReplies(article_no);
    
                PageMaker pageMaker = new PageMaker();
                pageMaker.setCriteria(criteria);
                pageMaker.setTotalCount(repliesCount);
    
                Map<String, Object> map = new HashMap<String, Object>();
                map.put("replies", replies);
                map.put("pageMaker", pageMaker);
    
                entity = new ResponseEntity<Map<String, Object>>(map, HttpStatus.OK);
    
            } catch (Exception e) {
    
                e.printStackTrace();
                entity = new ResponseEntity<Map<String, Object>>(HttpStatus.OK);
    
            }
    
            return entity;
        }

     

     

    다음 포스팅에 이어서...

     

    5. 포스팅을 마치며

    이번 포스팅에서는 댓글 처리를 위한 Persistence, Service, Controller 영역을 작성했습니다. 다음 포스팅에서는 View 영역에서의 댓글처리 기능을 구현해보도록 하겠습니다. 

     

     

    다음 포스팅

    2020/06/27 - [Spring/게시판 만들기] - [Camel][Spring] 게시판 만들기 #7-2. 댓글처리 구현 (댓글 등록, 수정, 삭제)

     

    [Camel][Spring] 게시판 만들기 #7-2. 댓글처리 구현 (댓글 등록, 수정, 삭제)

    [Camel][Spring] 게시판 만들기 #7-2. 댓글처리 구현 (댓글 등록, 수정, 삭제) 본 게시판 만들기 프로젝트는 더블에스 Devlog Spring-MVC 를 참조하여 작성했음을 알려드립니다. 또한 개인적인 학습을 ��

    cameldev.tistory.com

     

     

    글을 읽으시면서 잘못된 부분이나 궁금하신 사항은 댓글 달아주시면 빠른 시일내에 수정 및 답변하도록 하겠습니다. 

    댓글

Camel`s Tistory.