danDevlog

Spring - 10 (댓글 기능 - 1 기능 구현 테스트) 본문

Spring 게시판 만들기

Spring - 10 (댓글 기능 - 1 기능 구현 테스트)

단데기이 2022. 4. 22. 16:19
728x90

게시물 읽기 페이지에 들어간 다음 아래 부분에 댓글기능을 추가해볼려고한다.

댓글기능은 화면 전환 없이 글 등록, 수정, 삭제가 가능하다.

즉, 비동기 처리(Ajax)로 기능을 구현할 것이다.

동기식 처리 : 한 가지 일을 끝낼때까지 가만히 대기해야함

비동기식 처리 : 한 가지 일이 끝날때까지 다른 일을 할 수 있음.

 

REST방식을 채용한다.

REST : Representational State Transfer의 약자이며, 자원을 이름으로 구분하여 해당 자원의 상태(정보)를 주고 받는 모든 것을 의미한다.

 

REST방식을 테스트하기 하기위해서 TalendAPI Tester을 크롬의 확장프로그램으로 추가한다.

pom.xml 의존성을 추가한다.

		<!--https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind 
		자바 객체와 json 데이터를 연결시켜주기 위한 의존성 -->
		<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-databind</artifactId>
		<version>2.11.2</version>
		</dependency>
		<!--
		https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-xml -->
		<dependency>
		<groupId>com.fasterxml.jackson.dataformat</groupId>
		<artifactId>jackson-dataformat-xml</artifactId>
		<version>2.11.2</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
		<dependency>
		<groupId>com.google.code.gson</groupId>
		<artifactId>gson</artifactId>
		<version>2.8.5</version>
		</dependency>

JSON과 관련된 의존성들이다.

JSON은 텍스트로 이루어져 있으므로, 사람과 기계 모두 읽고 쓰기 쉽다. 프로그래밍 언어와 플랫폼에 독립적이므로, 서로 다른 시스템간에 객체를 교환하기에 좋다. 자바스크립트의 문법을 채용했기 때문에, 자바스크립트에서 eval 명령으로 곧바로 사용할 수 있다.

 

댓글 테이블 생성한다

create table tbl_reply(
rno number(10,0), -- 댓글 번호
bno number(10,0) not null, -- 게시물 번호
reply varchar2(1000) not null, -- 댓글 내용
replyer varchar2(50) not null, -- 댓글 작성자
replyDate date default sysdate, -- 작성일
updateDate date default sysdate -- 수정일
);

-- 댓글은 게시물 1개당 여러개 등록 가능.
create sequence seq_reply;
-- rno 시퀸스 처리 예정.
alter table tbl_reply add constraint pk_reply primary key(rno);
--테이블 생성후에 제약조건을 추가, pk는 rno.
alter table tbl_reply add constraint fk_reply_board
foreign key (bno) references tbl_board(bno);
-- 외래키로 tbl_board(bno) 사용.
commit;

혹시 ORA-02270: 이 열목록에 대해 일치하는 고유 또는 기본 키가 없습니다. 이러한 오류가 발생한다면,

TBL_BOARD테이블에서 bno가 primary key로 지정되어있지 않으므로, 지정해주면 오류없이 실행된다.

 

domain패키지에 댓글의 정보가 담길 ReplyVO를 생성한다.

package com.review.domain;

import java.util.Date;

import lombok.Data;

@Data
public class ReplyVO {
	private Long rno;
	private Long bno;
	
	private String reply;
	private String replyer;
	private Date replyDate;
	private Date updateDate;
	
}

mapper패키지에 ReplyMapper.java 인터페이스를 생성한다.

package com.review.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Param;

import com.review.domain.Criteria;
import com.review.domain.ReplyVO;

public interface ReplyMapper {
	public int insert(ReplyVO vo);
	
	public ReplyVO read(Long rno);
	
	public int delete(Long rno);
	
	public int update(ReplyVO reply);
	
	public List<ReplyVO> getListWithPaging(
			@Param("cri") Criteria cri,
			@Param("bno") Long bno);
	// 페이지 정보와 게시물 번호를 전달.
}

Mybatis의 SQL문장에 다수의 파라미터를 전달할 때는 전달되는 변수들에 꼭 @Param 어노테이션을 붙여줘야한다.

@Param 어노테이션을 붙이면 본인이 원하는 명으로 mapper에서 사용할 수 있다.

 

src/main/resources의 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.review.mapper.ReplyMapper">
  		<insert id="insert">
  			insert into tbl_reply (rno, bno, reply, replyer)
  			values (seq_reply.nextval, #{bno},#{reply},#{replyer})
  		</insert>
  		
  		<!-- 게시물당 조회가 아니라 댓글 1개 조회 -->
  		<select id="read" resultType="com.review.domain.ReplyVO">
  			select * from tbl_reply
  			where rno=#{rno}
  		</select>
  		
  		<delete id="delete">
  			delete from tbl_reply where rno=#{rno}
  		</delete>
  		
  		<update id="update">
  			update tbl_reply set reply=#{reply}, updatedate=sysdate
  			where rno=#{rno}
  		</update>
  		
  		<select id="getListWithPaging" resultType="com.review.domain.ReplyVO">
  			select rno, bno, reply, replyer, replydate, updatedate
  			from tbl_reply where bno=#{bno}
  			order by rno asc
  		</select>
  </mapper>

 

Service패키지에 ReplyService 인터페이스를 구현한다.

package com.review.service;

import java.util.List;

import org.apache.ibatis.annotations.Param;

import com.review.domain.Criteria;
import com.review.domain.ReplyVO;

public interface ReplyService {
	public int register(ReplyVO vo);
	public ReplyVO get(Long rno);
	public int remove(Long rno);
	public int modify(ReplyVO reply);
	public List<ReplyVO> getList(
			@Param("cri") Criteria cri,
			@Param("bno") Long bno);
	
}

ReplyServiceImpl 클래스를 구현한다.

package com.review.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.review.domain.Criteria;
import com.review.domain.ReplyVO;
import com.review.mapper.ReplyMapper;

import lombok.Setter;
import lombok.extern.log4j.Log4j;


@Service
@Log4j
public class ReplyServiceImpl implements ReplyService{
	@Setter(onMethod_ =@Autowired)
	private ReplyMapper mapper;
	
	@Override
	public int register(ReplyVO vo) {
		log.info("register...." + vo);
		return mapper.insert(vo);
	}

	@Override
	public ReplyVO get(Long rno) {
		log.info("get...." + rno);
		return mapper.read(rno);
	}

	@Override
	public int remove(Long rno) {
		log.info("remove....." + rno);
		return mapper.delete(rno);
	}

	@Override
	public int modify(ReplyVO reply) {
		log.info("modify....." + reply);
		return mapper.update(reply);
	}

	@Override
	public List<ReplyVO> getList(Criteria cri, Long bno) {
		log.info("get Reply list " + bno);
		return mapper.getListWithPaging(cri, bno);
	}

}

 

ReplyController를 구현한다.

package com.review.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import com.review.domain.ReplyVO;
import com.review.service.ReplyService;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;

@Controller
@RequestMapping("/replies/")
@Log4j
@AllArgsConstructor
public class ReplyController {
	
	private ReplyService service;
	
	// 요청이 /replies/new로 오면,
	// 정보를 조회해서 리턴 하는데, 정보 형태는 json이고, 전달 결과물은
	// 평범한 문자열 형태.
	
	@PostMapping(value="/new", consumes = "application/json"
			, produces = {MediaType.TEXT_PLAIN_VALUE})
	public ResponseEntity<String> create(@RequestBody ReplyVO vo) {
		// @RequestBody는 json 형태로 받은 값을 객체로 변환
		log.info("ReplyVO: " + vo);
		int insertCount = service.register(vo);
		log.info("Reply insert count: " + insertCount);
		
		return insertCount == 1 ? new ResponseEntity<>("success", HttpStatus.OK)
				: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
		
		// ResponseEntity : 웹페이지 생성(상태코드, 헤더, 응답, 데이터)
		// 3항 연산자 이용.
		// HttpStatus 페이지 상태를 전달.
		// 리턴에 코드는 길지만, 풀이하면
		// 정상 처리되면 정상 처리의 status 전달하고, 아니면 오류 status 전달.
	}
	
}

@RestController어노테이션이 사용된 컨트롤러는 각 메소드의 리턴이 ViewResolver로 가지 않는다.

따라서 리턴을 내 맘대로 할 수 있고, .jsp가 붙지 않는다.

 

consumes : 외부에서 전달받는 것. Ajax를 통해 전달받은 데이터의 타입.
Ajax의 contentType과 같은 것을 뜻한다.

consumes = "application/json"의 경우 json형식의 데이터를 전달받는다는 것을 뜻한다.

 

produces : 해당 메소드를 통해 응답할 데이터 형식.
Ajax의 success:function(result)에 있는 result로 전달할 데이터의 타입.
MediaType.[응답 타입] 형식으로 작성하여 이용한다.
TEXT_PLAIN_VALUE : 텍스트형식
APPLICATION_XML_VALUE : xml형식
APPLICATION_JSON_UTF8_VALUE : JSON형식

 

value : 메소드와 연결될 URL
value 속 중괄호 {} 는 변수의 선언이며, PATH이다.

 

ResponseEntity : 응답할 값과 상태 코드까지 보내준다. 서버의 상태코드, 응답 메세지 등을 담을 수 있는 타입이다.
브라우저에서 JSON타입으로 데이터를 전송하고 서버에서는 댓글의 처리 결과에 따라 문자열로 결과를 리턴한다.
new ResponseEntity<T>([정보], HttpStatus.[상태]) 또는 new ResponseEntity(HttpStatus.[상태]) 로 작성하여 이용한다.
HttpStatus.OK : status 200, OK
HttpStatus.INTERNAL_SERVER_ERROR : status 500, Server Error

 

@RequestBody 

이 어노테이션이 붙은 파라미터에는 http요청의 본문(body)이 그대로 전달된다.

일반적인 GET/POST의 요청 파라미터라면 @RequestBody를 사용할 일이 없을 것이다.

반면에 xml이나 json기반의 메시지를 사용하는 요청의 경우에 이 방법이 매우 유용하다.

HTTP 요청의 바디내용을 통째로 자바객체로 변환해서 매핑된 메소드 파라미터로 전달해준다. 

 

Talend에서 insert 테스트해본다.

존재하는 게시물 번호를 적용해야 동작한다. / 주소는 https 가아니라 http이다.

잘 작동하였으면 response 200, 오작동이면 500이 발생한다.

 

 

다음은 게시물별 댓글 목록을 확인해본다.

ReplyController에 다음 코드를 추가한다.

	@GetMapping(value="/pages/{bno}/{page}",
			produces= {MediaType.APPLICATION_JSON_VALUE})
	public ResponseEntity<List<ReplyVO>> getList(
			@PathVariable("page") int page,
			@PathVariable("bno") Long bno){
		// @PathVariable : url로 넘겨받은 값 이용
		log.info("getList......");
		Criteria cri = new Criteria(page, 10);
		log.info(cri);
		
		return new ResponseEntity<>(service.getList(cri, bno), HttpStatus.OK);
		// T<List<ReplyVO>> t = new T<> ();
		// 댓글 목록을 출력하고, 정상 처리 상태를 리턴.
	}

@PathVariable("key") : value에 중괄호{} 로 데이터를 받아온 경우에 해당 파라미터를 매개변수에 매칭시킨다.
{키}과 @PathVariable("키")의 키는 일치해야 한다.

 

Talend에서 조회를 테스트해본다.

다음은 댓글 1개만을 읽어본다. (ReplyController)

@GetMapping(value="/{rno}",
			produces = {MediaType.APPLICATION_JSON_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<>("success", HttpStatus.OK)
				: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
	}

 

댓글 수정을 테스트한다.

	@RequestMapping(method= {RequestMethod.PUT,
			RequestMethod.PATCH}, value="/{rno}",
			consumes = "application/json",
			produces = {MediaType.TEXT_PLAIN_VALUE})
	public ResponseEntity<String> modify(
			@RequestBody ReplyVO vo,
			@PathVariable("rno") Long rno){
		// put, patch 둘다 수정 처리 가르킴.
		// 생성되는 정보의 형태는 json에 일반적인 문자열 이용.
		// @RequestBody : json으로 생성된 정보를 객체화.
		vo.setRno(rno);
		log.info("rno:  " + rno);
		log.info("modify:  " + vo);
		
		return service.modify(vo) == 1
				? new ResponseEntity<>("success", HttpStatus.OK)
						: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
	}

 

Comments