Spring Lec07
================= ex02 프로젝트 ( Ajax 댓글 처리 ) =================
■ ex02 프로젝트 생성 및 설정
01. 생성
프로젝트 네임 : ex02
템플릿 : Spring MVC Project
패키지 이름 : org.zerock.controller
02. 설정
● Project Facets , Java Compiler : 11 (JDK) 버전 수정
● JUint 4 추가
● pom.xml ( 수정 및 추가 )
● root-context.xml ( 수정 및 추가 )
● servlet-context.xml ( 경로 수정 )
● web.xml ( 스프링의 UTF-8 처리 필터 등록 )
● mappers 패키지 생성 , mybatis-config.xml 생성
● src/main/java - 패키지 생성
■ Ajax 댓글 처리
01. Ajax 방식의 이해
02. @RestController
03. json 데이터의 처리
04. handlebars를 이용한 탬플릿 처리
Ajax (Asnychronous JavaScript and XML) = 비동기화된 자바스크립트와 XML ... 약어이다.
브라우저에서 대화형으로 서버와 데이터를 주고받는 형태의 메시지 전송 방식을 의미한다.
1. 브라우저 화면 전환이 없기 때문에 사용자 사용이 좋다는 점
2. 서버에서 화면에 필요한 모든 데이터를 만드는 대신 서버는 필요한 데이터만 전달하기 때문에, 개발의 무게 중심이 브라우저 쪽으로 많이 배분된다는 점
ㄴ@RestController ( 간략설명 )
REST는 'Representational State Transfer'의 약어로 하나의 URI는 하나의 고유한 리소스(resource)를 대표하도록 설계된 개념이다.
최근에는 서버에 접근하는 기기의 종류가 다양해 지면서 다양한 기기에서 공통으로 데이터를 처리할 수 있는 규칙을 만들려고 하는데 이러한 시도가 REST 방식이다.
REST API는 외부에서 위와 같은 방식으로 특정 URI를 통해서 사용자가 원하는 정보를 제공하는 방식이다.
최근에 Open API에서 많이 사용되면서 REST방식으로 제공되는 외부 연결 URI를 REST API라고 하고, REST 방식의 서비스를 제공이 가능한 것을 'Restful'하다고 표현한다.
@RestController 애노테이션의 경우 기존의 특정한 JSP와 같은 뷰를 만들어 내는 것이 목적이 아닌 REST 방식의 데이터 처리를 위해서 사용하는 애노테이션이다.
Android, iPnone과 같은 모바일 환경에서 서버의 데이터를 이용하거나, HTML5나 Ajax 등을 이용하는 경우에 많이 사용됩니다.
* 스프링 3버전까지 컨트롤러는 주로 @Controller 애노테이션만 사용해서 처리하였고, 화면 처리를 담당하는 JSP가 아닌 데이터 자체를 서비스하려면 해당 메소드나 리턴타입에 @ResponseBody 애노테이션을 추가하는 형태로 작성되었다.
ㄴㄴ▶ SampleController.java ( 테스트 )
package org.zerock.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.zerock.domain.SampleVO;
@RestController
@RequestMapping("/sample")
public class SampleController {
private static final Logger logger = LoggerFactory.getLogger(SampleController.class);
// 문자열 데이터는 기본적으로 브라우저에는 text/html타입으로 처리된다.
// 1.단순 문자열 경우
//@RequestMapping(value = "/hello", produces="text/plain;charset=UTF-8") //한글 깨질경우
@RequestMapping("/hello")
public String sayHello() {
logger.info("sayHello()");
return "Hello World.. 안녕";
}
// @RestController의 경우 별도의 처리가 없어도 자동으로 JSON로 처리된다.
// 2. 객체를 JSON 으로 반환하는 경우
// Content-Type: appliation/json;charset=utf-8 (브라우저 Response Headers 부분 확인 가능 )
// HTTP Status 406 - ( 에러가 난다면 pom.xml 에 jackson-databind 라이브러리 디펜던시 추가 )
@RequestMapping("/sendVO")
public SampleVO sendVO() {
SampleVO vo = new SampleVO();
vo.setFirstName("길동");
vo.setLastName("홍");
vo.setMno(123);
return vo;
}
// 3. 컬렉션 타입의 객체를 반환하는 경우 (List)
// JDK 1.7이상의 경우 new ArrayList<>() 와 같이 클래스에 제네릭 타입을 명시하지 않아도 된다.
// 만일 문제가 있다면 new ArrayList<SampleVO>() 예전 방식으로 작성합니다.
@RequestMapping("/sendList")
public List<SampleVO> sendList(){
List<SampleVO> list = new ArrayList<>();
for(int i=0; i<=10; i++ ) {
SampleVO vo = new SampleVO();
vo.setFirstName("규태");
vo.setLastName("박");
vo.setMno(i);
list.add(vo);
}
return list;
}
// 4. 컬렉션 타입의 객체를 반환하는 경우 (Map)
@RequestMapping("/sendMap")
public Map<Integer, SampleVO> sendMap(){
Map<Integer, SampleVO> map = new HashMap<Integer, SampleVO>();
for(int i=0; i<=10; i++ ) {
SampleVO vo = new SampleVO();
vo.setFirstName("규태");
vo.setLastName("박");
vo.setMno(i);
map.put(i,vo);
}
return map;
}
// 5. ResponseEntity 타입
// @RestController는 별도의 view를 제공하지 않는 형태로 서비스를 실행하기 때문에, 때로는 결과 데이터가 예외적인 상황에서 문제가 발생할 수 있다.
// 실행시 HTTP ERROR 400 에러가 난다.
@RequestMapping("/sendErrorAuth")
public ResponseEntity<Void> sendListAuth(){
return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST); // 400번대 에러
}
// 3.5 조인
// 스프링에서 제공하는 ResponseEntity 타입은 개발자가 직접 결과 데이터 + HTTP의 상태 코드를 직접 제어할 수 있는 클래스이다.
// 상태코드와 데이터를 함께 전송할 수 있기 때문에 좀 더 세밀한 제어가 필요한 경우에 사용한다.
@RequestMapping("/sendErrorNot")
public ResponseEntity<List<SampleVO>> sendListNot(){
List<SampleVO> list = new ArrayList<SampleVO>();
for(int i=0; i<=10; i++ ) {
SampleVO vo = new SampleVO();
vo.setFirstName("규태");
vo.setLastName("박");
vo.setMno(i);
list.add(vo);
}
return new ResponseEntity<List<SampleVO>>(list, HttpStatus.NOT_FOUND); // 400 에러
}
}
ㄴㄴSampleVO.java ( 모델 )
package org.zerock.domain;
import lombok.Data;
@Data
public class SampleVO {
private Integer mno;
private String firstName;
private String lastName;
}
== 설명 ==
2. 객체를 JSON 으로 반환 실패시 pom.xml 추가
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
4. 컬렉션 타입의 객체를 반환하는 경우 (Map)
javaScript의 JSON 형식으로 보여지게 된다.
키(key) 와 값(value) 으로 구성되어 '{ }' 표현된다.
5. ResponseEntity 타입
@RestController는 별도의 뷰를 제공하니 않는 형태로 서비스를 실행하기 때문에,
때로는 결과 데이터가 예외적인 상황에서 문제가 발생할 수 있다.
웹의 경우 HTTP 상태 코드가 이러한 정보를 나타내는데 사용합니다.
주로 많이 사용되는 상태 코드는 아래와 같다.
* 100번대 : 현재 데이터의 처리중인 상태
- 100: 데이터의 일부를 서버가 받은 상태
* 200번대 : 현재 데이터의 처리중인 상태
- 200: 에러 없이 정상처리
- 204: 정상 처리되었으나 서버에서 보내줄 데이터가 없는경우
* 300번대 : 현재 데이터의 처리중인 상태
- 301: 요청된 페이지가 새 URI 로 변경되었음
- 304: 이미 기존의 데이터와 변경된 것이 없음
* 400번대 : 현재 데이터의 처리중인 상태
- 400: 전송된 Request에 문제가 있어서 서버가 인식할 수 없음
- 403: 서버에서 허락되지 않음
- 404: URL에 해당하는 자원을 찾을수 없음
- 406: 전송 방식이 허락되지 않음(REST에서 자주 발생)
* 500번대 : 현재 데이터의 처리중인 상태
- 500: 서버에서 처리 시 문제가 발생
- 502: 게이트웨이나 프록시 상태의 문제(과부하 등)
- 503: 일시적인 과부하나 서비스 중단 상태
- 504: 지정된 처리시간이 지나서 처리되지 못함
3.5 조인
결과 데이터와 HTTP 의 상태 코드를 같이 사용해야 하는 경우를 보여준다.
코드의 리턴 구문을 보면 바로 위의 List 데이터와 HTTP 상태 코드 404를 전송하게 된다.
특이한 점은 일반적인 404 메시지와는 달리 전송한 결과는 그대로 보여주면서 상태 코드를 전달한다는 점이다.
이런 방식은 주로 호출한 쪽으로 에러의 원인 메시지를 전송할 때 사용하는 방식이다.
예를 들어 Android , iPhone과 같은 모바일 환경에서 서버의 데이터를 이용하거나, HTML5, Ajax 등을 이용하는 경우에 많이 사용된다.
● Map 컬렉션
Map 컬렉션은 키(key)와 값(value)으로 구성된 Map.Entry 객체를 저장하는 구조를 가지고 있습니다.
여기서 키와 값은 모두 객체입니다.
키는 중복될 수 없지만 값은 중복 저장될 수 있습니다.
만약 기존에 저장된 키와 동일한 키로 값을 저장하면 기존의 값은 없어지고 새로운 값으로 대체된다.
Test_Map.java
package ch07;
import java.util.HashMap;
import java.util.Map;
public class Test_Map {
public static void main(String[] args) {
Book b = new Book("어린왕자","생텍쥐페리");
String s = "부산시 동래구 명장동";
int a = 22;
//Object 클래스는 모든 자바 클래스의 최고 조상 클래스
Map<String,Object> m = new HashMap<>();
m.put("age", a);
m.put("address", s);
m.put("book", b);
System.out.println(m.get("age"));
System.out.println(m.get("address"));
System.out.println(m.get("book"));
}
}
■ JSON 데이타 사용 시 : pom.xml ( jackson-databind 라이브러리 디펜던시 추가 )
. JSON 데이타 사용할 경우
. pom.xml 아래 코드 추가
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
■ Advanced REST client 앱 설치
크롬 > Advanced REST client : 검색 > 설치(추가)
● 댓글을 위한 객체 설계 , 전달 방식과 처리 방식의 결정
URI | 전송방식 | 설명 |
/replies/all/12 | GET | 게시물 bno 12번의 모든 댓글 리스트 |
/replies/ + 데이터 | POST | 새로운 댓글 추가 |
/replies/123 + 데이터 | PUT/PATCH | 댓글 rno 123의 수정 |
/replies/123 | DELETE | 댓글 rno 123의 삭제 |
REST 방식을 이용할 것이라면 우선, 해당하는 컨트롤러를 먼저 작성하고, 그에 맞는 URI를 결정하는 것이 첫단계.
ㄴ◆ tbl_reply 테이블 생성
create table tbl_reply (
rno int not null auto_increment,
bno int not null default 0,
replytext varchar(1000) not null,
replyer varchar(50) not null,
regdate varchar(20) default '',
primary key(rno)
);
== 외래키 (foreign key) 추가 ==
alter table tbl_reply add constraint fk_board foreign key (bno) references tbl_board (bno);
댓글의 테이블은 tbl_reply라는 이름으로 생성하고, 1:N의 관계이므로, 댓글쪽에는 원래 게시물의 번호를 보관하는 bno 칼럼이 존재
외래키를 추가 하는 이유는 데이터 무결성이 가장 큰 이유이다.
ㄴReplyVO.javae3
package org.zerock.domain;
import lombok.Data;
@Data
public class ReplyVO {
private Integer rno;
private Integer bno;
private String replytext;
private String replyer;
private String regdate;
}
ㄴReplyDAO.java ( interface )
public interface ReplyDAO {
public List<ReplyVO> list(Integer bno) throws Exception; //목록
public void create(ReplyVO vo) throws Exception; //추가
public void update(ReplyVO vo) throws Exception; //수정
public void delete(Integer rno) throws Exception; //삭제
}
ㄴReplyDAOImpl.java
@Repository
public class ReplyDAOImpl implements ReplyDAO {
@Inject
private SqlSession session;
private static String namespace = "org.zerock.mapper.ReplyMapper";
@Override
public List<ReplyVO> list(Integer bno) throws Exception {
return session.selectList(namespace+".list", bno); // ReplyMapper에 존재하는 id속성의 값 ( list )
}
@Override
public void create(ReplyVO vo) throws Exception {
session.insert(namespace+".create", vo);
}
@Override
public void update(ReplyVO vo) throws Exception {
session.update(namespace+".update", vo);
}
@Override
public void delete(Integer rno) throws Exception {
session.delete(namespace+".delete", rno);
}
}
ㄴ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="org.zerock.mapper.ReplyMapper">
<select id="list" resultType="ReplyVO">
select * from tbl_reply where bno = #{bno} order by rno desc
</select>
<insert id="create">
insert into tbl_reply (bno, replytext, replyer, regdate) values (#{bno},#{replytext},#{replyer},#{regdate})
</insert>
<update id="update">
update tbl_reply set replytext = #{replytext} where rno = #{rno}
</update>
<delete id="delete">
delete from tbl_reply where rno = #{rno}
</delete>
</mapper>
ㄴReplyService.java ( interface )
public interface ReplyService {
public List<ReplyVO> listReply(Integer bno) throws Exception;
public void addReply(ReplyVO vo) throws Exception;
public void modifyReply(ReplyVO vo) throws Exception;
public void removeReply(Integer rno) throws Exception;
}
ㄴReplyServiceImpl.java
@Service
public class ReplyServiceImpl implements ReplyService {
@Inject
private ReplyDAO dao;
@Override
public List<ReplyVO> listReply(Integer bno) throws Exception {
return dao.list(bno);
}
@Override
public void addReply(ReplyVO vo) throws Exception {
dao.create(vo);
}
@Override
public void modifyReply(ReplyVO vo) throws Exception {
dao.update(vo);
}
@Override
public void removeReply(Integer rno) throws Exception {
dao.delete(rno);
}
}
● REST 방식 추가 및 설명, 페이징 처리
URI | 전송방식 | 설명 |
/replies/ + JSON 데이터 | POST | 새로운 댓글 등록 |
/replies/ + JSON 데이터 | PUT, PATCH | 댓글 수정 |
/replies/댓글 번호 | DELETE | 댓글 삭제 |
/replies/all/게시물 번호 | GET | 글 목록 |
/replies/게시물 번호/페이지 번호 | GET | 목록 페이징 |
. REST 방식의 처리에서 사용하는 특별한 애노테이션은 다음과 같다.
1. @PathVariable - URI 경로에서 원하는 데이터를 추출하는 용도로 사용
2. @RequestBody - 전송된 JSON 데이터를 객체로 변환해 주는 애노테이션으로 @ModelAttribute와 유사한 역할을 하지만 JSON에서 사용된다는 점이 차이다.
ㄴReplyDAO.java
public List<ReplyVO> listPage(Integer bno, Criteria cri) throws Exception; // limit
public int count(Integer bno) throws Exception; // 총 게시물 수
ㄴReplyDAOImpl.java
@Override
public List<ReplyVO> listPage(Integer bno, Criteria cri) throws Exception {
Map<String, Object> m = new HashMap<>();
m.put("bno", bno);
m.put("cri", cri);
return session.selectList(namespace + ".listPage", m);
}
@Override
public int count(Integer bno) throws Exception {
return session.selectOne(namespace + ".count", bno);
}
== 설명 ==
Map<String, Object> :
먼저 Map의 Key는 String타입이고, Value는 String,int,배열,객체 등 여러 종류의 데이터 타입이 올수 있으므로, 최상위 타입인 Object로 선언한다.
ㄴreplyMapper.xml
<select id="listPage" resultType="ReplyVO">
select * from tbl_reply where bno = #{bno} order by rno desc limit #{cri.pageStart}, #{cri.perPageNum}
</select>
<select id="count" resultType="int">
select count(bno) from tbl_reply where bno =#{bno}
</select>
ㄴReplyService.java
public List<ReplyVO> listReplyPage(Integer bno, Criteria cri) throws Exception;
public int count(Integer bno) throws Exception;
ㄴReplyServiceImpl.java
@Override
public List<ReplyVO> listReplyPage(Integer bno, Criteria cri) throws Exception {
return dao.listPage(bno, cri);
}
@Override
public int count(Integer bno) throws Exception {
return dao.count(bno);
}
ㄴReplyController.java
@RestController
@RequestMapping("/replies/")
public class ReplyController {
@Inject
private ReplyService service;
//리턴 타입 ResponseEntity<String> entity 설계
//새로운 댓글을 등록 실패시 BAD_REQUEST(400)를 결과로 전송된다.
//JSON으로 전송된 데이터를 ReplyVO타입의 객체(vo)로 변환해주는 역할을 @RequestBody가 한다.
//글 등록
@RequestMapping(value = "", method = RequestMethod.POST)
public ResponseEntity<String> register(@RequestBody ReplyVO vo) {
//오늘 날짜
java.util.Date today = new java.util.Date();
SimpleDateFormat cal = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String signdate = cal.format(today);
vo.setRegdate(signdate);
ResponseEntity<String> entity = null;
try {
service.addReply(vo);
entity = new ResponseEntity<String>("success", HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
entity = new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST); //400에러
}
return entity;
}
//@RequestMapping()을 보면 URI 내의 경로에 {bno}를 활용한다.
//{bno}는 메소드의 파라미터에서 @PathVariable("bno")로 활용된다.
//메소드의 처리가 성공하는 경우 - HttpStatus.OK 헤더를 전송하고, 데이터를 같이 전송처리한다.
//글 목록
@RequestMapping(value = "/all/{bno}", method = RequestMethod.GET)
public ResponseEntity<List<ReplyVO>> list(@PathVariable("bno") Integer bno) {
// @PathVariable - URI 경로에서 원하는 데이터를 추출하는 용도로 사용
ResponseEntity<List<ReplyVO>> entity = null;
try {
entity = new ResponseEntity<>(service.listReply(bno), HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
entity = new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
return entity;
}
//글 수정
@RequestMapping(value = "/{rno}", method = { RequestMethod.PUT, RequestMethod.PATCH })
public ResponseEntity<String> update(@PathVariable("rno") Integer rno, @RequestBody ReplyVO vo) {
ResponseEntity<String> entity = null;
try {
vo.setRno(rno);
service.modifyReply(vo);
entity = new ResponseEntity<String>("success", HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
entity = new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
return entity;
}
//글 삭제
@RequestMapping(value = "/{rno}", method = RequestMethod.DELETE)
public ResponseEntity<String> remove(@PathVariable("rno") Integer rno) {
ResponseEntity<String> entity = null;
try {
service.removeReply(rno);
entity = new ResponseEntity<String>("success", HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
entity = new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
return entity;
}
// - /replies/게시물 번호/페이지 번호
//페이징 처리를 위해서는 PageMaker를 가져와야 한다.
//Ajax로 호출될 것이므로 Model 아닌 Map 타입의 객체를 생성 이용한다.
//페이징 처리
@RequestMapping(value = "/{bno}/{page}", method = RequestMethod.GET)
public ResponseEntity<Map<String, Object>> listPage(
@PathVariable("bno") Integer bno,
@PathVariable("page") Integer page) {
ResponseEntity<Map<String, Object>> entity = null;
try {
Criteria cri = new Criteria();
cri.setPage(page);
PageMaker pageMaker = new PageMaker();
pageMaker.setCri(cri);
Map<String, Object> map = new HashMap<String, Object>();
List<ReplyVO> list = service.listReplyPage(bno, cri);
map.put("list", list);
int replyCount = service.count(bno);
pageMaker.setTotalCount(replyCount);
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.BAD_REQUEST);
}
return entity;
}
}
ㄴ▶ JSON 테스트 ( Advanced REST client 앱 이용 )
=====================================================================================
01. 글 등록 (bno 값은 tbl_board 에서 실존하는 값으로 입력해야 한다.) Method = POST
=====================================================================================
02. 글 목록 (bno 값은 tbl_board 에서 실존하는 값으로 입력해야 한다.) Method = GET
=====================================================================================
03. 글 수정 (rno 값은 tbl_reply 에서 실존하는 값으로 입력해야 한다.) Method = PUT
=====================================================================================
04. 글 삭제 (rno 값은 tbl_reply 에서 실존하는 값으로 입력해야 한다.) Method = DELETE
=====================================================================================
05. 글 목록 페이징 (bno 값은 tbl_board 에서 실존하는 값, 1페이지) Method = GET
ㄴ■ web.xml ( REST 전송 방식 추가 )
<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>
● JSP에서 Ajax 처리 - 냉무
ㄴSearchBoardController.java
// 추가
@RequestMapping(value = "test", method = RequestMethod.GET)
public void ajaxTest() {
logger.info("test get ...........");
}
ㄴ/include/header.jsp
<script src="http://code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="http://code.jquery.com/jquery-migrate-1.4.1.min.js"></script>
<a href="/sboard/test">[jsonTest]</a>
ㄴ/sboard/test.jsp ( getJSON() 이용 )
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ include file="/WEB-INF/views/include/header.jsp" %>
<center>
<h3>AJAX TEST</h3>
Total : <span id="total_conunt"></span>
<script>
var bno = 24; // tbl_board에서 존재하는 bno값 입력
$.getJSON("/replies/all/"+bno,
function(data){
$("#total_conunt").text(data.length); //객체 길이
}
);
</script>
<%@ include file="/WEB-INF/views/include/footer.jsp" %>
==== 설명 ====
@RestController의 경우 객체를 JSON 방식으로 전달하기 때문에 jQuery를 이용해서 호출할 때는 getJSON()을 이용합니다.
# database > tbl_reply 내용
ㄴ/sboard/test.jsp ( 리스트 출력 )
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ include file="/WEB-INF/views/include/header.jsp" %>
<center>
<h3>AJAX TEST</h3>
Total : <span id="total_conunt"></span>
<!-- 댓글 목록 출력 영역 -->
<table id="replies" border=1>
</table>
<script>
var bno = 24; // tbl_board에서 존재하는 bno값 입력
getAllList(); //호출
// 글 목록
function getAllList() { // 리스트 출력 확인후 목록 갱신시 함수 호출로 처리
$.getJSON("/replies/all/"+bno,
function(data){
$("#total_conunt").text(data.length);
var str="";
$(data).each(
function(){
str += "<tr>"
+ "<td>"+this.rno+"</td>"
+ "<td>"+this.bno+"</td>"
+ "<td>"+this.replyer+"</td>"
+ "<td>"+this.replytext+"</td>"
+ "<td data-rno='"+this.rno+"' class='replyLi'><button>mode</button></td>"
+ "</tr>"
}
);
$("#replies").html(str);
}
);
}
</script>
<%@ include file="/WEB-INF/views/include/footer.jsp" %>
==== 설명 ====
주목할 부분은 <td>태그의 속성으로 data-rno 입니다.
'data-'로 시작되는 속성은 이름이나 개수에 관계없이 태그 내에서 자유롭게 사용할 수 있는 속성이므로 id, name 속성을 대신해서 사용하기 편리하다.
ㄴ/sboard/test.jsp ( 글 작성 )
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ include file="/WEB-INF/views/include/header.jsp" %>
<center>
<h3>AJAX TEST</h3>
Total : <span id="total_conunt"></span>
<!-- 글 작성 -->
<table border=1>
<tr>
<td>글쓴이</td>
<td><input type='text' name='replyer' id='newReplyWriter'></td>
<td>댓글 내용</td>
<td><input type='text' name='replytext' id='newReplyText'></td>
<td></td>
<td><button id="replyAddBtn">댓글 작성</button></td>
</tr>
</table>
<br>
<!-- 댓글 목록 출력 영역 -->
<table id="replies" border=1>
</table>
<script>
var bno = 24; // tbl_board에서 존재하는 bno값 입력
getAllList();
// 글 목록
function getAllList() { // 리스트 출력 확인후 목록 갱신시 함수 호출로 처리
$.getJSON("/replies/all/"+bno,
function(data){
$("#total_conunt").text(data.length);//리스트 갯수
var str="";
$(data).each(
function(){
str += "<tr>"
+ "<td>"+this.rno+"</td>"
+ "<td>"+this.bno+"</td>"
+ "<td>"+this.replyer+"</td>"
+ "<td>"+this.replytext+"</td>"
+ "<td data-rno='"+this.rno+"' class='replyLi'><button>mode</button></td>"
+ "</tr>"
}
);
$("#replies").html(str);
}
);
}
// 글 등록
$("#replyAddBtn").on("click",function(){
var replyer = $("#newReplyWriter").val();
var replytext = $("#newReplyText").val();
$.ajax({
type: 'post',
url: '/replies/',
headers: {
"Content-Type" : "application/json"
},
dataType: 'text',
data:JSON.stringify({
bno : bno,
replyer : replyer,
replytext : replytext
}),
success: function(result){
if(result == 'success'){
alert("글 등록됨");
getAllList(); //목록 출력
//글 작성후 null처리
$("#newReplyWriter").val("");
$("#newReplyText").val("");
}
}
});
});
</script>
<%@ include file="/WEB-INF/views/include/footer.jsp" %>
== 설명 ==
JSON.stringify() : stringify 메소드는 JSON 객체를 String 객체로 변환시켜준다.
JSON.parse() : parse 메소드는 string 객체를 JSON 객체로 변환시켜준다.
ㄴ/sboard/test.jsp ( mode 버튼 )
<center>
<h3>AJAX TEST</h3>
Total : <span id="total_conunt"></span>
<style>
#modDiv {
width: 300px;
height: 100px;
background-color: gray;
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px;
margin-left: -150px;
padding: 10px;
z-index: 1000;
}
</style>
<!-- MOD 버튼 -->
<table id='modDiv' style="display:none;">
<tr>
<td class='modal-title'></td>
</tr>
<tr>
<td><input type='text' id='replytext'></td>
</tr>
<tr>
<td>
<button type="button" id="replyModBtn">수정</button>
<button type="button" id="replyDelBtn">삭제</button>
<button type="button" id='closeBtn'>닫기</button>
</td>
</tr>
</table>
<!-- 글 작성 -->
<table border=1>
<tr>
<td>글쓴이</td>
<td><input type='text' name='replyer' id='newReplyWriter'></td>
<td>댓글 내용</td>
<td><input type='text' name='replytext' id='newReplyText'></td>
<td></td>
<td><button id="replyAddBtn">댓글 작성</button></td>
</tr>
</table>
<br>
<!-- 댓글 목록 출력 영역 -->
<table id="replies" border=1>
</table>
<script>
var bno = 24; // tbl_board에서 존재하는 bno값 입력
getAllList();
// 글 목록
function getAllList() { // 리스트 출력 확인후 목록 갱신시 함수 호출로 처리
$.getJSON("/replies/all/"+bno,
function(data){
$("#total_conunt").text(data.length);//리스트 갯수
var str="";
$(data).each(
function(){
str += "<tr>"
+ "<td>"+this.rno+"</td>"
+ "<td>"+this.bno+"</td>"
+ "<td>"+this.replyer+"</td>"
+ "<td>"+this.replytext+"</td>"
+ "<td data-rno='"+this.rno+"' data-str='"+this.replytext+"' class='replyLi'><button>mode</button></td>"
+ "</tr>"
}
);
$("#replies").html(str);
}
);
}
// 글 등록
$("#replyAddBtn").on("click",function(){
var replyer = $("#newReplyWriter").val();
var replytext = $("#newReplyText").val();
$.ajax({
type: 'post',
url: '/replies',
headers: {
"Content-Type" : "application/json"
},
dataType: 'text',
data:JSON.stringify({
bno : bno,
replyer : replyer,
replytext : replytext
}),
success: function(result){
if(result == 'success'){
alert("글 등록됨");
getAllList(); //목록 출력
//글 작성후 null처리
$("#newReplyWriter").val("");
$("#newReplyText").val("");
}
}
});
});
//mode 열기
$("#replies").on("click", ".replyLi button", function() {
var reply = $(this).parent(); //자기 자신의 부모 요소를 선택한다. <tr>
var rno = reply.attr("data-rno");
var replytext = reply.attr("data-str");
//alert(rno + " : " + replytext);
$(".modal-title").html(rno);
$("#replytext").val(replytext);
$("#modDiv").show("slow");
});
// mode닫기
$("#closeBtn").on("click", function() {
$("#modDiv").hide("slow");
getAllList();
});
</script>
ㄴ/sboard/test.jsp ( 글 수정,삭제 )
// 글 수정
$("#replyModBtn").on("click",function(){
var rno = $(".modal-title").html();
var replytext = $("#replytext").val();
$.ajax({
type:'put',
url:'/replies/'+rno,
headers: {
"Content-Type": "application/json",
"X-HTTP-Method-Override": "PUT"
},
dataType:'text',
data:JSON.stringify({
replytext : replytext
}),
success:function(result){
if(result == 'success'){
alert("수정 되었습니다.");
$("#modDiv").hide("slow");
getAllList();
}
}
});
});
// 글 삭제
$("#replyDelBtn").on("click", function() {
var rno = $(".modal-title").html();
$.ajax({
type : 'delete',
url : '/replies/' + rno,
headers : {
"Content-Type" : "application/json",
"X-HTTP-Method-Override" : "DELETE"
},
dataType : 'text',
success : function(result) {
if (result == 'success') {
alert("삭제 되었습니다.");
$("#modDiv").hide("slow");
getAllList();
}
}
});
});
</script>
<%@ include file="/WEB-INF/views/include/footer.jsp" %>
[[ 설명 ]]
HTTP 메소드 오버라이드 추가하기 ( "X-HTTP-Method-Override": "PUT" 또는 "X-HTTP-Method-Override" : "DELETE" )
어떤 HTTP 프록시는 임시적인 HTTP 메소드나 새로운 HTTP 메소드 (PATCH 같은) 를 지원하지 않는다. 그런 경우에 프로토콜 전체를 위반하는 방식으로 HTTP 메소드를 다른 HTTP 메소드로 “프록시” 하는 것이 가능하다.
이렇게 동작하는 방식은 클라이언트가 HTTP POST로 요청하고 X-HTTP-Method-Override 헤더를 설정하고 그 값으로 의도하는 HTTP 메소드 (PATCH 와 같은)를 설정하면 된다.
ㄴ■ 페이징 설명
http://localhost:8080/replies/24/1
페이징 처리는 ReplyController 에서 '/replies/게시물번호/페이지번호'로 이루어진다.
위 주소 검색 확인(JSON 타입) 가능하다.
결과 데이터는 댓글의 목록에 해당하는 'list' 테이터와 페이지 구성에 필요한 'pageMaker' 데이터로 구성된다.
pageMaker에는 페이징 처리에 필요한 데이터가 이미 계산된 상태이므로 이를 이용해서 페이지를 처리한다.
ㄴㄴ/sboard/test.jsp ( 페이징 )
<center>
<h3>AJAX TEST</h3>
Total : <span id="total_conunt"></span>
<style>
#modDiv {
width: 300px;
height: 100px;
background-color: gray;
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px;
margin-left: -150px;
padding: 10px;
z-index: 1000;
}
.active{
font-weight:bold;
}
</style>
<!-- MOD 버튼 -->
<table id='modDiv' style="display:none;">
<tr>
<td class='modal-title'></td>
</tr>
<tr>
<td><input type='text' id='replytext'></td>
</tr>
<tr>
<td>
<button type="button" id="replyModBtn">수정</button>
<button type="button" id="replyDelBtn">삭제</button>
<button type="button" id='closeBtn'>닫기</button>
</td>
</tr>
</table>
<!-- 글 작성 -->
<table border=1>
<tr>
<td>글쓴이</td>
<td><input type='text' name='replyer' id='newReplyWriter'></td>
<td>댓글 내용</td>
<td><input type='text' name='replytext' id='newReplyText'></td>
<td></td>
<td><button id="replyAddBtn">댓글 작성</button></td>
</tr>
</table>
<br>
<!-- 댓글 목록 출력 영역 -->
<table id="replies" border=1>
</table>
<!-- 페이징 -->
<table>
<tr class="pagination">
</tr>
</table>
<script>
var bno = 24; // tbl_board에서 존재하는 bno값 입력
// getAllList();
var replyPage = 1;
getPageList(1);
// 글 목록
function getAllList() { // 리스트 출력 확인후 목록 갱신시 함수 호출로 처리
$.getJSON("/replies/all/"+bno,
function(data){
$("#total_conunt").text(data.length);//리스트 갯수
var str="";
$(data).each(
function(){
str += "<tr>"
+ "<td>"+this.rno+"</td>"
+ "<td>"+this.bno+"</td>"
+ "<td>"+this.replyer+"</td>"
+ "<td>"+this.replytext+"</td>"
+ "<td data-rno='"+this.rno+"' data-str='"+this.replytext+"' class='replyLi'><button>mode</button></td>"
+ "</tr>"
}
);
$("#replies").html(str);
}
);
}
// 글 등록
$("#replyAddBtn").on("click",function(){
var replyer = $("#newReplyWriter").val();
var replytext = $("#newReplyText").val();
$.ajax({
type: 'post',
url: '/replies',
headers: {
"Content-Type" : "application/json"
},
dataType: 'text',
data:JSON.stringify({
bno : bno,
replyer : replyer,
replytext : replytext
}),
success: function(result){
if(result == 'success'){
alert("글 등록됨");
//getAllList();
getPageList(1);
//글 작성후 null처리
$("#newReplyWriter").val("");
$("#newReplyText").val("");
}
}
});
});
//mode 열기
$("#replies").on("click", ".replyLi button", function() {
var reply = $(this).parent(); //자기 자신의 부모 요소를 선택한다.
var rno = reply.attr("data-rno");
var replytext = reply.attr("data-str");
//alert(rno + " : " + replytext);
$(".modal-title").html(rno);
$("#replytext").val(replytext);
$("#modDiv").show("slow");
});
// mode닫기
$("#closeBtn").on("click", function() {
$("#modDiv").hide("slow");
//getAllList();
getPageList(replyPage);
});
// 글 수정
$("#replyModBtn").on("click",function(){
var rno = $(".modal-title").html();
var replytext = $("#replytext").val();
$.ajax({
type:'put',
url:'/replies/'+rno,
headers: {
"Content-Type": "application/json",
"X-HTTP-Method-Override": "PUT"
},
dataType:'text',
data:JSON.stringify({
replytext:replytext
}),
success:function(result){
if(result == 'success'){
alert("수정 되었습니다.");
$("#modDiv").hide("slow");
//getAllList();
getPageList(replyPage);
}
}
});
});
// 글 삭제
$("#replyDelBtn").on("click", function() {
var rno = $(".modal-title").html();
$.ajax({
type : 'delete',
url : '/replies/' + rno,
headers : {
"Content-Type" : "application/json",
"X-HTTP-Method-Override" : "DELETE"
},
dataType : 'text',
success : function(result) {
if (result == 'success') {
alert("삭제 되었습니다.");
$("#modDiv").hide("slow");
//getAllList();
getPageList(1);
}
}
});
});
//페이징
//페이지 번호를 입력받아 데이터를 처리
//서버에서 전송된 데이터 중 댓글 목록인 list 데이터를 이용해서 댓글 내용 표시
//페이징 처리를 위해 만들어진 pageMaker 데이터를 이용해서 printPaging() 호출
function getPageList(page){
$.getJSON("/replies/"+bno+"/"+page , function(data){
var str="";
$(data.list).each( // Map 타입의 속성 list
function(){
str += "<tr>"
+ "<td>"+ this.rno + "</td>"
+ "<td>"+ this.replytext +"</td>"
+ "<td data-rno='"+this.rno+"' data-str='"+this.replytext+"' class='replyLi'><button>mode</button></td>"
+ "</tr>";
}
);
$("#replies").html(str);
printPaging(data.pageMaker); // Map 타입의 속성 pageMaker
});
}
//pageMaker를 이용해서 화면에 페이지 번호를 출력
//상단에 getPageList(1); 추가
function printPaging(pageMaker){
var str = "";
if(pageMaker.prev){
str += "<td><a href='"+(pageMaker.startPage-1)+"'> << </a></td>";
}
for(var i=pageMaker.startPage, len = pageMaker.endPage; i <= len; i++){
var strClass= pageMaker.cri.page == i?'class=active':''; //현페 페이지 클래스 적용
str += "<td "+strClass+"><a href='"+i+"'>"+i+"</a></td>";
}
if(pageMaker.next){
str += "<td><a href='"+(pageMaker.endPage + 1)+"'> >> </a></td>";
}
$('.pagination').html(str);
}
//상단에 replyPage=1; 추가
$(".pagination").on("click", "td a", function(event){
event.preventDefault(); // <a href="">태그의 기본 동작인 페이지 전환을 막는 역활을 한다.
replyPage = $(this).attr("href"); // 클릭된 페이지의 번호를 얻는 역활을 한다.
getPageList(replyPage);
});
</script>
ㄴ◆ /sboard/read.jsp ( 적용 )
<center>
<h3>READ PAGE</h3>
<table border=1 width=570>
<tr>
<td width=70>제목</td>
<td width=500>${boardVO.title }</td>
</tr>
<tr>
<td>내용</td>
<td>${boardVO.content }</td>
</tr>
<tr>
<td>작성자</td>
<td>${boardVO.writer }</td>
</tr>
</table>
<br>
<!-- test.jsp 내용 추가 -->
<!-- <script> var bno = 값; 매칭 처리 수정 -->
<%@ include file="test.jsp" %>
<br>
<table border=0 width=570>
<tr>
<td width=50%>
<a href="list${pageMaker.makeSearch(pageMaker.cri.page)}">[list]</a>
</td>
<td width=50% align="right">
<a href="modify${pageMaker.makeSearch(pageMaker.cri.page)}&bno=${boardVO.bno }">[수정]</a>
<a href="remove${pageMaker.makeSearch(pageMaker.cri.page)}&bno=${boardVO.bno }">[삭제]</a>
</td>
</tr>
</table>
</center>
ㄴ▶ /sboard/test.jsp ( include 적용으로 변경 )
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<center>
Total : <span id="total_conunt"></span>
<style>
#modDiv {
width: 300px;
height: 100px;
background-color: gray;
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px;
margin-left: -150px;
padding: 10px;
z-index: 1000;
}
.active{
font-weight:bold;
}
</style>
<!-- MOD 버튼 -->
<table id='modDiv' style="display:none;">
<tr>
<td class='modal-title'></td>
</tr>
<tr>
<td><input type='text' id='replytext'></td>
</tr>
<tr>
<td>
<button type="button" id="replyModBtn">수정</button>
<button type="button" id="replyDelBtn">삭제</button>
<button type="button" id='closeBtn'>닫기</button>
</td>
</tr>
</table>
<!-- 글 작성 -->
<table border=1 width=570>
<tr>
<td>글쓴이</td>
<td><input type='text' name='replyer' id='newReplyWriter'></td>
<td>댓글 내용</td>
<td><input type='text' name='replytext' id='newReplyText'></td>
<td></td>
<td><button id="replyAddBtn">댓글 작성</button></td>
</tr>
</table>
<br>
<!-- 댓글 목록 출력 영역 -->
<table id="replies" border=1 width=570>
</table>
<!-- 페이징 -->
<table>
<tr class="pagination">
</tr>
</table>
<script>
var bno = "${param.bno}"; // tbl_board에서 존재하는 bno값 입력
getAllList(); //총 게시물 수
var replyPage = 1;
getPageList(1); //페이징 처리된 리스트
// 총 게시물 수
function getAllList() {
$.getJSON("/replies/all/"+bno,
function(data2){
$("#total_conunt").text(data2.length); //리스트 갯수
}
);
}
// 글 목록 ▲ 총 게시물 수로 변경
function getAllList() { // 리스트 출력 확인후 목록 갱신시 함수 호출로 처리
$.getJSON("/replies/all/"+bno,
function(data){
$("#total_conunt").text(data.length);//리스트 갯수
var str="";
$(data).each(
function(){
str += "<tr>"
+ "<td>"+this.rno+"</td>"
+ "<td>"+this.bno+"</td>"
+ "<td>"+this.replyer+"</td>"
+ "<td>"+this.replytext+"</td>"
+ "<td data-rno='"+this.rno+"' data-str='"+this.replytext+"' class='replyLi'><button>mode</button></td>"
+ "</tr>"
}
);
$("#replies").html(str);
}
);
}
// 글 등록
$("#replyAddBtn").on("click",function(){
var replyer = $("#newReplyWriter").val();
var replytext = $("#newReplyText").val();
$.ajax({
type: 'post',
url: '/replies/',
headers: {
"Content-Type" : "application/json"
},
dataType: 'text',
data:JSON.stringify({
bno : bno,
replyer : replyer,
replytext : replytext
}),
success: function(result){
if(result == 'success'){
alert("글 등록됨");
getAllList(); //총 게시물 수
getPageList(1);
//글 작성후 null처리
$("#newReplyWriter").val("");
$("#newReplyText").val("");
}
}
});
});
//mode 열기
$("#replies").on("click", ".replyLi button", function() {
var reply = $(this).parent(); //자기 자신의 부모 요소를 선택한다.
var rno = reply.attr("data-rno");
var replytext = reply.attr("data-str");
//alert(rno + " : " + replytext);
$(".modal-title").html(rno);
$("#replytext").val(replytext);
$("#modDiv").show("slow");
});
// mode닫기
$("#closeBtn").on("click", function() {
$("#modDiv").hide("slow");
//getAllList();
getPageList(replyPage);
});
// 글 수정
$("#replyModBtn").on("click",function(){
var rno = $(".modal-title").html();
var replytext = $("#replytext").val();
$.ajax({
type:'put',
url:'/replies/'+rno,
headers: {
"Content-Type": "application/json",
"X-HTTP-Method-Override": "PUT"
},
dataType:'text',
data:JSON.stringify({
replytext:replytext
}),
success:function(result){
if(result == 'success'){
alert("수정 되었습니다.");
$("#modDiv").hide("slow");
getAllList();
getPageList(replyPage);
}
}
});
});
// 글 삭제
$("#replyDelBtn").on("click", function() {
var rno = $(".modal-title").html();
$.ajax({
type : 'delete',
url : '/replies/' + rno,
headers : {
"Content-Type" : "application/json",
"X-HTTP-Method-Override" : "DELETE"
},
dataType : 'text',
success : function(result) {
if (result == 'success') {
alert("삭제 되었습니다.");
$("#modDiv").hide("slow");
getAllList();
getPageList(1);
}
}
});
});
//페이징
//페이지 번호를 입력받아 데이터를 처리
//서버에서 전송된 데이터 중 댓글 목록인 list 데이터를 이용해서 댓글 내용 표시
//페이징 처리를 위해 만들어진 pageMaker 데이터를 이용해서 printPaging() 호출
function getPageList(page){
$.getJSON("/replies/"+bno+"/"+page , function(data){
var str="";
$(data.list).each( // Map 타입의 속성 list
function(){
str += "<tr>"
+ "<td>"+ this.rno + "</td>"
+ "<td>"+ this.replytext +"</td>"
+ "<td data-rno='"+this.rno+"' data-str='"+this.replytext+"' class='replyLi'><button>mode</button></td>"
+ "</tr>";
}
);
$("#replies").html(str);
printPaging(data.pageMaker); // Map 타입의 속성 pageMaker
});
}
//pageMaker를 이용해서 화면에 페이지 번호를 출력
//상단에 getPageList(1); 추가
function printPaging(pageMaker){
var str = "";
if(pageMaker.prev){
str += "<td><a href='"+(pageMaker.startPage-1)+"'> << </a></td>";
}
for(var i=pageMaker.startPage, len = pageMaker.endPage; i <= len; i++){
var strClass= pageMaker.cri.page == i?'class=active':'';
str += "<td "+strClass+"><a href='"+i+"'>"+i+"</a></td>";
}
if(pageMaker.next){
str += "<td><a href='"+(pageMaker.endPage + 1)+"'> >> </a></td>";
}
$('.pagination').html(str);
}
//상단에 replyPage=1; 추가
$(".pagination").on("click", "td a", function(event){
event.preventDefault(); // <a href="">태그의 기본 동작인 페이지 전환을 막는 역활을 한다.
replyPage = $(this).attr("href"); // 클릭된 페이지의 번호를 얻는 역활을 한다.
getPageList(replyPage);
});
</script>
== 설명 ==
이 파일은 read.jsp 페이지에서 include 처리로 인해 필요 없는 include.header.jsp , include.footer.jsp 등 삭제한다.