스프링 - Ajax 댓글 처리 구현
목차
스프링 - Ajax 댓글 처리 구현
1. 프로젝트의 구성
REST 처리를 위해 pom.xml에서 수정된 내용이 대부분이므로, ex03에서 사용하던 소스 코드를 복사해서 사용한다.
1.2 pom.xml 에서 json 라이브러리 추가
<!-- JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
2. 댓글 처리를 위한 영속 영역
---------------------------
-- 댓글 테이블 추가
CREATE SEQUENCE SEQ_REPLY;
CREATE TABLE TBL_REPLY(
RNO NUMBER(10),
BNO NUMBER(10) NOT NULL,
REPLY VARCHAR2(1000) NOT NULL, -- 댓글
REPLYER VARCHAR2(100) NOT NULL, -- 작성자
REPLYDATE DATE DEFAULT SYSDATE, -- 작성날짜
UPDATEDATE DATE DEFAULT SYSDATE -- 수정날짜
);
ALTER TABLE TBL_REPLY ADD CONSTRAINT PK_REPLY PRIMARY KEY(RNO);
-- ALTER TABLE TBL_REPLY DROP CONSTRAINT FK_REPLY;
ALTER TABLE TBL_REPLY ADD CONSTRAINT FK_REPLY FOREIGN KEY(BNO)
REFERENCES TBL_BOARD(BNO) ON DELETE CASCADE;
SELECT * FROM TBL_REPLY;
2.1 ReplyVO 클래스의 추가 (com.koreait.domain)
package com.koreait.domain;
import lombok.Data;
@Data
public class ReplyVO {
private Long rno;
private Long bno;
private String reply;
private String replyer;
private String replyDate;
private String updateDate;
}

2.2 ReplyMapper 클래스와 XML 처리
com.koreait.mapper
패키지에 ReplyMapper 인터페이스를 처리하고 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.koreait.mapper.ReplyMapper">
</mapper>
ReplyMapper 테스트
src/test/java
에 com/koreait/mapper
밑 ReplyMapperTests.java
추가
package com.koreait.mapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
// SpringRunner는 SpringJUnit4ClassRunner의 자식이며,
// 4.3 버전 이상부터 사용 가능한 확장판이다.
@RunWith(SpringRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class ReplyMapperTests {
@Setter(onMethod_ = @Autowired)
private ReplyMapper mapper;
@Test
public void testMapper( ) {
log.info(mapper);
}
}

testMapper()로 ReplyMapper 타입 객체가 정상 사용 가능한지 확인
2.3 CRUD 작업
등록 create
우선은 외래키를 사용하는 등록 작업 진행
com.koreait.mapper
에 있는 ReplyMapper
메소드 추가
public int insert(ReplyVO vo);
xml
<mapper namespace="com.koreait.mapper.ReplyMapper">
<insert id="insert">
insert into tbl_reply (rno, bno, reply, replyer)
values (seq_reply.nextval, #{bno}, #{reply}, #{replyer})
</insert>
</mapper>
@Setter(onMethod_ = @Autowired)
private BoardMapper board;
private Long[] bnoArr = {1376256L, 1376255L, 1376254L, 1376253L, 1376252L}; // 방법2
@Test
public void testCreate() {
java.util.List<BoardVO> boards = board.getListWithPaging(new Criteria(1, 5));
IntStream.rangeClosed(1, 10).forEach(i -> {
ReplyVO reply = new ReplyVO();
reply.setReply("댓글 테스트 " + i);
reply.setReplyer("테드한 " + i);
// 0~n : n+1로 나눈 나머지
reply.setBno(boards.get(i % 5 ).getBno());
// reply.setBno(bnoArr[i%5]); // 이렇게 해도 되는 방법2
mapper.insert(reply);
log.info(reply);
});
}
조회(read) 테스트
xml
<select id="read" resultType="com.koreait.domain.ReplyVO">
SELECT RNO, BNO, REPLY, REPLYER, REPLYDATE, UPDATEDATE FROM TBL_REPLY WHERE RNO = #{rno}
</select>
mapper
public ReplyVO read(Long bno);
ReplyMapperTests
@Test
public void testRead() {
Long targetRno = 5L;
ReplyVO vo = mapper.read(targetRno);
log.info(vo);
}
삭제(delete)
Mapper 인터페이스와 xml에 삭제 처리 추가
mapper
public int delete(Long rno);
xml
<delete id="delete">
DELETE FROM TBL_REPLY WHERE RNO = #{rno}
</delete>
ReplyMapperTests
@Test
public void testDelete() {
Long rno = 2L;
log.info(mapper.delete(rno));
}
수정(update)
Mapper 인터페이스와 xml에 수정 처리 추가
public int update(ReplyVO reply);
<update id="update">
UPDATE TBL_REPLY
SET REPLY = #{reply}, UPDATEDATE = SYSDATE
WHERE RNO = #{rno}
</update>
테스트
@Test
public void testUpdate() {
Long rno = 3L;
ReplyVO reply = mapper.read(rno);
reply.setReply("수정 댓글");
log.info("UPDATE COUNT: " + mapper.update(reply));
}
2.4. @Param 어노테이션과 댓글 목록
MyBatis는 두개 이상이 데이터를 파라미터로 전달하기 위해선
1) 별도의 객체로 구성하거나 2)Map 이용 방식 3) Param을 이용해 이름을 사용하는 방식이 있다.
@Param 의 속성값은 MyBatis에서 SQL을 이용할 떄 #{}의 이름으로 사용 가능하다.
ReplyMapper Interface
package com.koreait.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.koreait.domain.Criteria;
import com.koreait.domain.ReplyVO;
public interface ReplyMapper {
public int insert(ReplyVO reply);
public ReplyVO read(Long bno);
public int delete(Long rno);
public int update(ReplyVO reply);
// 해당 게시글에 있는 전체 댓글
/*
* MyBatis는 두 개 이상의 데이터를 파라미터로 전달하기 위해선
* 1) 별도의 객체로 구성하거나 2) Map 이용 방식 3) Param을 이용해 이름을 사용하는 방식이 있다.
* @Param 의 속성값은 MyBatis에서 SQL을 이용할 때#{}의 이름으로 사용 가능하다.
*/
public List<ReplyVO> getListWithPaging(
@Param("cri") Criteria cri,
@Param("bno") Long bno
);
}
ReplyMapper.xml
<select id="getListWithPaging" resultType="com.koreait.domain.ReplyVO">
SELECT RNO, BNO, REPLY, REPLYER, REPLYDATE, UPDATEDATE FROM TBL_REPLY
WHERE BNO = #{bno}
</select>
테스트
@Test
public void testGetListWithPaging() {
Criteria cri = new Criteria();
List<ReplyVO> replies = mapper.getListWithPaging(cri, bnoArr[0]);
replies.forEach(reply->log.info(reply));
// mapper.getListWithPaging(bnoArr[0]).forEach(reply -> log.info(reply));
}
3. 서비스 영역과 Controller 처리
ReplyService
Interface
package com.koreait.service;
import java.util.List;
import com.koreait.domain.Criteria;
import com.koreait.domain.ReplyVO;
public interface ReplyService {
public int register(ReplyVO reply); // 등록하기
public ReplyVO get(Long rno); // 특정 댓글 가져오기
public int modify(ReplyVO reply); // 수정하기
public int remove(Long rno); // 삭제하기
// 전체 댓글 가져오기
public List<ReplyVO> getList(Criteria cri, Long bno);
}
ReplyServiceImple
package com.koreait.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.koreait.domain.Criteria;
import com.koreait.domain.ReplyVO;
import com.koreait.mapper.ReplyMapper;
import lombok.AllArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@Service
@Log4j
@AllArgsConstructor
public class ReplyServiceImple implements ReplyService {
// @Setter(onMethod_ = @Autowired)
private ReplyMapper mapper;
@Override
public int register(ReplyVO reply) {
log.info("register............" + reply);
return mapper.insert(reply);
}
@Override
public ReplyVO get(Long rno) {
log.info("get.........."+rno);
return mapper.read(rno);
}
@Override
public int modify(ReplyVO reply) {
log.info("modify........."+reply);
return mapper.update(reply);
}
@Override
public int remove(Long rno) {
log.info("remove............."+rno);
return mapper.delete(rno);
}
@Override
public List<ReplyVO> getList(Criteria cri, Long bno) {
log.info("get Reply List of a Board " + bno);
return mapper.getListWithPaging(cri, bno);
}
}
3.1. ReplyController의 설계
URL : 사용자의 요청과 그에 맞는 응답을 주소로 나타낸 부분. 항상 페이지로 나타내진다.
URI : 사용자의 요청을 대표하는 데이터 혹은 응답에 대한 데이터를 나타낸 부분. 대부분 데이터로 나타내진다.
REST(Representational State Transfer) : "하나의 URI는 하나의 고유한 리소스를 대표하도록 설계된다."
예) /board/123 : 게시글 중 123번
REST로 설계하는 이유
- 데이터 통신에 제약이 없다.
- 데이터 소켓 경량화
- 다른 서버끼리도 데이터를 주고 받을 수 있다.
@RestController 어노테이션으로 다음 같은 URL을 기준으로 동작하도록 작성
작업 | URL | HTTP 전송 방식 |
---|---|---|
등록 | /replies/new | POST |
조회 | /replies/:rno | GET |
삭제 | /replies/:rno | DELETE |
수정 | /replies/:rno | PUT or PATCH |
페이지 | /replies/pages/:bno/:page | GET |
REST 방식으로 동작하는 URL을 설계할 땐 PK를 기준으로 작성하는 게 좋다.
다만 댓글의 목록은 PK를 사용할 수 없으므로 bno와 page 정보들을 URL에서 표현하는 방식 사용.
com.koreait.controller 패키지 밑에 ReplyController.java 생성
package com.koreait.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.koreait.domain.ReplyVO;
import com.koreait.service.ReplyService;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RequestMapping("/replies/")
@RestController // 각 메소드의 리턴은 자동으로 view resolver로 가지 않는다.
@Log4j
//@AllArgsConstructor
public class ReplyController {
@Setter(onMethod_ = @Autowired)
private ReplyService service;
}
브라우저에서 JSON 타입으로 데이터를 전송하고 서버에서는 댓글의 처리 결과에 따라 문자열로 결과를 리턴한다.
consumes : Ajax를 통해 전달받은 데이터의 타입 ( 외부에서 전달한 타입 )
produces : Ajax의 success: function(result)에 있는 result로 전달할 데이터의 타입 ( 외부로 전달할 타입 )
json 방식 데이터만 처리하도록 하고 문자열을 반환하도록 설계,
ResponseEntity : 서버의 상태 코드, 응답 메시지등을 담을 수 있는 타입.
create의 파라미터 @RequestBody를 적용해 JSON 데이터를 ReplyVO 타입으로 반환하도록 지정.
@RequestBody : 외부에서 전달받은 데이터를 parse (파싱) 해주는 속성이다.
create는 내부적으로 ReplyServiceImple을 호출해 register를 부르고 댓글이 추가된 숫자를 확인해서 브라우저에
200 = OK 혹은 500 = ERROR를 반환하도록 한다.
3.2 댓글 등록 작업과 테스트
크롬 확장프로그램 설치
Talend API Tester 설치
REST 방식 처리에서 주의점은 브라우저나 외부에서 서버를 호출할 때 데이터 포맷과 서버에서 보내주는 데이터의 타입을 명확히 설계해야 하는 것.
예를 들어 댓글 등록은 브라우저에선 JSON 타입으로 된 댓글 데이터를 전송하고 서버에선 댓글의 처리 결과가 정상적으로 되었는지 문자열로 결과를 알려 주도록 한다.
// 댓글 등록
@PostMapping(value = "/new", consumes = "application/json", produces = {MediaType.TEXT_PLAIN_VALUE})
public ResponseEntity<String> create(@RequestBody ReplyVO reply) {
log.info("ReplyVO: " + reply);
int insertCount = service.register(reply);
log.info("Reply INSERT COUNT : " + insertCount);
return insertCount == 1 ? new ResponseEntity<> ("success", HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
)
데이터가 잘 들어갔는지 확인해본다.
)
3.3 특정 게시물의 댓글 조회
ReplyController
@GetMapping(value="/pages/{bno}/{page}", produces = {MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_UTF8_VALUE
})
public ResponseEntity<List<ReplyVO>> getList(
@PathVariable("page") int page,
@PathVariable("bno") Long bno) {
log.info("getList..........");
Criteria cri = new Criteria(page, 10);
log.info(cri);
return new ResponseEntity<>(service.getList(cri, bno), HttpStatus.OK);
}
uri에다 {} 중괄호가 들어가면 변수 선언이다. value="/page/{bno}/{page}
uri로 넘겨 매핑하기 위해 어노테이션 @PathVariable을 사용한다.
URL 주소로 localhost:8080/replies/pages/게시글번호/페이지번호 로 확인한다.
)
이게 백 엔드 개발자 이다. 서버의 통신으로 내가 원하는 데이터를 주고 받을 수 있는 REST 방식을 사용해야 한다.
JSON으로 확인하는법은 url뒤에 끝에 .json 확장자를 붙인다.
URL : corner-mini.com/replies/pages/1376256/1.json
크롬 json Formatter 확장프로그램 사용 시
@RestController는 잘 사용하지 않기도 한다.
@Controller 어노테이션을 사용할 때 반환(return) 타입을 .jsp로도 보낼 수 있도록 하고 싶을 때
@ResponseBody 라는 어노테이션을 사용하여 @Controller에서 Body를 응답하기 위해서는 (viewResolver로 가지않게 하기 위해서) 사용된다.
3.4 특정 댓글 조회 및 삭제
ReplyController
// 댓글 조회
@GetMapping(value="/{rno}", produces = {
MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE
})
public ResponseEntity<ReplyVO> get(@PathVariable("rno") Long rno) {
log.info("get....... : "+rno);
return new ResponseEntity<>(service.get(rno), HttpStatus.OK);
}
댓글 삭제
@DeleteMapping(value="/{rno}", produces = {MediaType.TEXT_PLAIN_VALUE})
public ResponseEntity<String> remove(@PathVariable("rno") Long rno) {
log.info("remove ... : " +rno);
return service.remove(rno) == 1
? new ResponseEntity<String>("success", HttpStatus.OK)
: new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
}

3.5 댓글 수정
PUT :자원 전체 수정, 자원 내 모든 필드를 전달해야 함, 일부만 전달할 경우 전달되지 않은 필드는 모두 초기화 처리가 된다.
PATCH : 자원 일부 수정, 수정할 필드만 전송
PUT 방식보단 PATCH 방식을 많이 사용한다.
// 댓글 수정
// PUT :자원 전체 수정, 자원 내 모든 필드를 전달해야 함, 일부만 전달할 경우 전달되지 않은 필드는 모두 초기화 처리가 된다.
// PATCH : 자원 일부 수정, 수정할 필드만 전송
// @RequestMapping을 사용하는 이유는 여러개의 메소드를 받기 위해.
@RequestMapping(method = {RequestMethod.PUT, RequestMethod.PATCH},
value = "/{rno}",
consumes = "application/json",
produces = {MediaType.TEXT_PLAIN_VALUE})
public ResponseEntity<String> modify(@RequestBody ReplyVO reply, @PathVariable("rno") Long rno){
reply.setRno(rno);
log.info("rno : " + rno);
log.info("modify : " + reply);
return service.modify(reply) == 1
? new ResponseEntity<String> ("success", HttpStatus.OK)
: new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
}
전체 수정 (put)


일부 수정


'⚙️ Backend > 스프링(Spring) Framework' 카테고리의 다른 글
스프링(Spring) - Ajax 댓글 처리 모듈화 (0) | 2021.05.24 |
---|---|
스프링(Spring) - 컨트롤러 result 보낼 때 한글 깨짐 현상 (0) | 2021.05.24 |
스프링 - 검색 페이징 처리 (0) | 2021.05.14 |
스프링 - 페이징 화면 처리에 관하여.. (0) | 2021.05.13 |
스프링 - 검색 처리 구현, 동적 쿼리에 관하여.. (0) | 2021.05.13 |
댓글