KSI일기장
0509spring 게시물 댓글(1) 댓글조회 (댓글 DB테이블 생성, jsp,js포함) 본문
-DB에 Reply(댓글) 테이블 생성 및 테스트 댓글 삽입
-- 댓글 테이블
CREATE TABLE "REPLY_S" (
"REPLY_NO" NUMBER PRIMARY KEY,
"REPLY_CONTENT" VARCHAR2(1000) NOT NULL,
"REPLY_ST" CHAR(1) DEFAULT 'N' CHECK ("REPLY_ST" IN ('Y','N')),
"CREATE_DT" DATE DEFAULT SYSDATE NOT NULL,
"MEMBER_NO" NUMBER REFERENCES MEMBER_S,
"BOARD_NO" NUMBER REFERENCES BOARD,
"PARENT_REPLY_NO" NUMBER REFERENCES REPLY_S
);
COMMENT ON COLUMN "REPLY_S"."REPLY_NO" IS '댓글 번호';
COMMENT ON COLUMN "REPLY_S"."REPLY_CONTENT" IS '댓글 내용';
COMMENT ON COLUMN "REPLY_S"."REPLY_ST" IS '댓글 상태';
COMMENT ON COLUMN "REPLY_S"."CREATE_DT" IS '댓글 작성일';
COMMENT ON COLUMN "REPLY_S"."MEMBER_NO" IS '회원 번호';
COMMENT ON COLUMN "REPLY_S"."BOARD_NO" IS '게시글 번호';
COMMENT ON COLUMN "REPLY_S"."PARENT_REPLY_NO" IS '부모 댓글 번호';
CREATE SEQUENCE SEQ_REPLY_NO_S; -- 댓글 번호 시퀀스
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모1', DEFAULT, DEFAULT, 1, 500, NULL);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모1-자식1', DEFAULT, DEFAULT, 1, 500, 1);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모1-자식2', DEFAULT, DEFAULT, 1, 500, 1);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모1-자식3', DEFAULT, DEFAULT, 1, 500, 1);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모2', DEFAULT, DEFAULT, 1, 500, NULL);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모3', DEFAULT, DEFAULT, 1, 500, NULL);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모4', DEFAULT, DEFAULT, 1, 500, NULL);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모2-자식1', DEFAULT, DEFAULT, 1, 500, 5);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모2-자식2', DEFAULT, DEFAULT, 1, 500, 5);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모2-자식3', DEFAULT, DEFAULT, 1, 500, 5);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모1-자식3-손자1', DEFAULT, DEFAULT, 1, 500, 4);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모1-자식3-손자2', DEFAULT, DEFAULT, 1, 500, 4);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '부모1-자식3-손자3', DEFAULT, DEFAULT, 1, 500, 4);
INSERT INTO REPLY_S VALUES(SEQ_REPLY_NO_S.NEXTVAL, '대댓글테스트', DEFAULT, DEFAULT, 1, 500, 14);
COMMIT;
SELECT * FROM REPLY_S
ORDER BY 1;
-reply.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div id="reply-area">
<!-- 댓글 목록 -->
<div class="reply-list-area">
<ul id="reply-list">
<c:forEach var="reply" items="${rList}">
<li class='reply-row <c:if test="${reply.parentReplyNo != 0}"> child-reply </c:if>'>
<p class="reply-writer">
<c:if test="${empty reply.profileImage}">
<!-- 프로필 이미지가 없을 경우 -->
<img src="${contextPath}/resources/images/user.png">
</c:if>
<c:if test="${!empty reply.profileImage}">
<!-- 프로필 이미지가 있을 경우 -->
<img src="${contextPath}${reply.profileImage}">
</c:if>
<span>${reply.memberNickname}</span>
<span class="reply-date">(${reply.createDate})</span>
</p>
<p class="reply-content">${reply.replyContent}</p>
<%-- 로그인 상태인 경우 답글 버튼 출력 --%>
<c:if test="${!empty loginMember}">
<div class="reply-btn-area">
<button onclick="showInsertReply(${reply.replyNo}, this)">답글</button>
<%-- 로그인한 회원의 댓글인 경우 --%>
<c:if test="${loginMember.memberNo == reply.memberNo}">
<button onclick="showUpdateReply(${reply.replyNo}, this);">수정</button>
<button onclick="deleteReply(${reply.replyNo})">삭제</button>
</c:if>
</div>
</c:if>
</li>
</c:forEach>
</ul>
</div>
<!-- 댓글 작성 부분 -->
<div class="reply-write-area">
<textarea id="replyContent"></textarea>
<button id="addReply">
댓글<br>
등록
</button>
</div>
</div>
-reply.js
// selectReplyList();
// 댓글 목록 조회(AJAX)
function selectReplyList(){
// contextPath, boardNo, memberNo 전역 변수 사용
$.ajax({
url : contextPath + "/reply/selectReplyList",
data : {"boardNo" : boardNo},
type : "GET",
dataType : "JSON", // JSON 형태의 문자열 응답 데이터를 JS 객체로 자동 변환
success : function(rList){
// rList : 반환 받은 댓글 목록
console.log(rList);
// 화면에 출력되어 있는 댓글 목록 삭제
const replyList = document.getElementById("reply-list"); // ul태그
replyList.innerHTML = "";
// rList에 저장된 요소를 하나씩 접근
for(let reply of rList){
// 행
const replyRow = document.createElement("li");
replyRow.classList.add("reply-row");
// 답글일 경우 child-reply 클래스 추가
if(reply.parentReplyNo != 0) replyRow.classList.add("child-reply");
// 작성자
const replyWriter = document.createElement("p");
replyWriter.classList.add("reply-writer");
// 프로필 이미지
const profileImage = document.createElement("img");
if( reply.profileImage != null ){ // 프로필 이미지가 있을 경우
profileImage.setAttribute("src", contextPath + reply.profileImage);
}else{ // 없을 경우 == 기본이미지
profileImage.setAttribute("src", contextPath + "/resources/images/user.png");
}
// 작성자 닉네임
const memberNickname = document.createElement("span");
memberNickname.innerText = reply.memberNickname;
// 작성일
const replyDate = document.createElement("span");
replyDate.classList.add("reply-date");
replyDate.innerText = "(" + reply.createDate + ")";
// 작성자 영역(p)에 프로필,닉네임,작성일 마지막 자식으로(append) 추가
replyWriter.append(profileImage , memberNickname , replyDate);
// 댓글 내용
const replyContent = document.createElement("p");
replyContent.classList.add("reply-content");
// 왜 innerHTML? <br> 태그 인식을 위해서
replyContent.innerHTML = reply.replyContent;
// 행에 작성자, 내용 추가
replyRow.append(replyWriter, replyContent);
// 로그인이 되어있는 경우 답글 버튼 추가
if(loginMemberNo != ""){
// 버튼 영역
const replyBtnArea = document.createElement("div");
replyBtnArea.classList.add("reply-btn-area");
// 답글 버튼
const childReplyBtn = document.createElement("button");
childReplyBtn.setAttribute("onclick", "showInsertReply("+reply.replyNo+", this)");
childReplyBtn.innerText = "답글";
// 버튼 영역에 답글 버튼 추가
replyBtnArea.append(childReplyBtn);
// 로그인한 회원번호와 댓글 작성자의 회원번호가 같을 때만 버튼 추가
if( loginMemberNo == reply.memberNo ){
// 수정 버튼
const updateBtn = document.createElement("button");
updateBtn.innerText = "수정";
// 수정 버튼에 onclick 이벤트 속성 추가
updateBtn.setAttribute("onclick", "showUpdateReply("+reply.replyNo+", this)");
// 삭제 버튼
const deleteBtn = document.createElement("button");
deleteBtn.innerText = "삭제";
// 삭제 버튼에 onclick 이벤트 속성 추가
deleteBtn.setAttribute("onclick", "deleteReply("+reply.replyNo+")");
// 버튼 영역 마지막 자식으로 수정/삭제 버튼 추가
replyBtnArea.append(updateBtn, deleteBtn);
} // if 끝
// 행에 버튼영역 추가
replyRow.append(replyBtnArea);
}
// 댓글 목록(ul)에 행(li)추가
replyList.append(replyRow);
}
},
error : function(req, status, error){
console.log("에러 발생");
console.log(req.responseText);
}
});
}
//-------------------------------------------------------------------------------------------------
// 댓글 등록
const addReply = document.getElementById("addReply");
const replyContent = document.getElementById("replyContent");
addReply.addEventListener("click", function(){ // 댓글 등록 버튼이 클릭이 되었을 때
// 1) 로그인이 되어있나? -> 전역변수 loginMemberNo 이용
if(loginMemberNo == ""){ // 로그인 X
alert("로그인 후 이용해주세요.");
return;
}
// 2) 댓글 내용이 작성되어있나?
if(replyContent.value.trim().length == 0){ // 미작성인 경우
alert("댓글을 작성한 후 버튼을 클릭해주세요.");
replyContent.value = ""; // 띄어쓰기, 개행문자 제거
replyContent.focus();
return;
}
// 3) AJAX를 이용해서 댓글 내용 DB에 저장(INSERT)
$.ajax({
url : contextPath + "/reply/insert",
data : {"replyContent" : replyContent.value,
"memberNo" : loginMemberNo,
"boardNo" : boardNo },
type : "post",
success : function(result){
if(result > 0){ // 등록 성공
alert("댓글이 등록되었습니다.");
replyContent.value = ""; // 작성했던 댓글 삭제
selectReplyList(); // 비동기 댓글 목록 조회 함수 호출
// -> 새로운 댓글이 추가되어짐
} else { // 실패
alert("댓글 등록에 실패했습니다...");
}
},
error : function(req, status, error){
console.log("댓글 등록 실패")
console.log(req.responseText);
}
});
});
// -----------------------------------------------------------------------------------
// 댓글 삭제
function deleteReply(replyNo){
if( confirm("정말로 삭제 하시겠습니까?") ){
// 요청주소 : /community/reply/delete
// 파라미터 : key : "replyNo", value : 매개변수 replyNo
// 전달 방식 : "GET"
// success : 삭제 성공 시 -> "삭제되었습니다" alert로 출력
// 댓글 목록 조회 함수 호출
// 삭제 실패 시 -> "삭제 실패" alert로 출력
// error : 앞 error 코드 참고
// DB에서 댓글 삭제 ==> REPLY_ST = 'Y' 변경
$.ajax({
url : contextPath + "/reply/delete",
data : {"replyNo" : replyNo},
type : "GET",
success: function(result){
if(result > 0){
alert("삭제되었습니다");
selectReplyList(); // 목록을 다시 조회해서 삭제된 글을 제거
}else{
alert("삭제 실패");
}
},
error : function(req, status, error){
console.log("댓글 삭제 실패")
console.log(req.responseText);
}
});
}
}
// ------------------------------------------------------------------------------------------
// 댓글 수정 화면 전환
let beforeReplyRow; // 수정 전 원래 행의 상태를 저장할 변수
function showUpdateReply(replyNo, btn){
// 댓글번호, 이벤트발생요소(수정버튼)
// ** 댓글 수정이 한 개만 열릴 수 있도록 만들기 **
const temp = document.getElementsByClassName("update-textarea");
if(temp.length > 0){ // 수정이 한 개 이상 열려 있는 경우
if(confirm("다른 댓글이 수정 중입니다. 현재 댓글을 수정 하시겠습니까?")){ // 확인
temp[0].parentElement.innerHTML = beforeReplyRow;
// reply-row // 백업한 댓글
// 백업 내용으로 덮어 씌워 지면서 textarea 사라짐
}else{ // 취소
return;
}
}
// 1. 댓글 수정이 클릭된 행을 선택
const replyRow = btn.parentElement.parentElement; // 수정 버튼의 부모의 부모
// 2. 행 내용 삭제 전 현재 상태를 저장(백업) (문자열)
// (전역변수 이용)
beforeReplyRow = replyRow.innerHTML;
// 취소 버튼 동작 코드
//replyRow.innerHTML = beforeReplyRow;
// 3. 댓글에 작성되어 있던 내용만 얻어오기 -> 새롭게 생성된 textarea 추가될 예정
//console.log(replyRow.children[1].innerHTML); // <br> 태그 유지를 위해서 innerHTML 사용
let beforeContent = replyRow.children[1].innerHTML;
// 이것도 가능!
//let beforeContent = btn.parentElement.previousElementSibling.innerHTML;
// 4. 댓글 행 내부 내용을 모두 삭제
replyRow.innerHTML = "";
// 5. textarea 요소 생성 + 클래스 추가 + **내용 추가**
const textarea = document.createElement("textarea");
textarea.classList.add("update-textarea");
// ******************************************
// XSS 방지 처리 해제
beforeContent = beforeContent.replaceAll("&", "&");
beforeContent = beforeContent.replaceAll("<", "<");
beforeContent = beforeContent.replaceAll(">", ">");
beforeContent = beforeContent.replaceAll(""", "\"");
// 개행문자 처리 해제
beforeContent = beforeContent.replaceAll("<br>", "\n");
// ******************************************
textarea.value = beforeContent; // 내용 추가
// 6. replyRow에 생성된 textarea 추가
replyRow.append(textarea);
// 7. 버튼 영역 + 수정/취소 버튼 생성
const replyBtnArea = document.createElement("div");
replyBtnArea.classList.add("reply-btn-area");
const updateBtn = document.createElement("button");
updateBtn.innerText = "수정";
updateBtn.setAttribute("onclick", "updateReply("+replyNo+", this)");
const cancelBtn = document.createElement("button");
cancelBtn.innerText = "취소";
cancelBtn.setAttribute("onclick", "updateCancel(this)");
// 8. 버튼영역에 버튼 추가 후
// replyRow(행)에 버튼영역 추가
replyBtnArea.append(updateBtn, cancelBtn);
replyRow.append(replyBtnArea);
}
// -----------------------------------------------------------------------------------
// 댓글 수정 취소
function updateCancel(btn){
// 매개변수 btn : 클릭된 취소 버튼
// 전역변수 beforeReplyRow : 수정 전 원래 행(댓글)을 저장한 변수
if(confirm("댓글 수정을 취소하시겠습니까?")){
btn.parentElement.parentElement.innerHTML = beforeReplyRow;
}
}
// -----------------------------------------------------------------------------------
// 댓글 수정(AJAX)
function updateReply(replyNo, btn){
// 새로 작성된 댓글 내용 얻어오기
const replyContent = btn.parentElement.previousElementSibling.value;
$.ajax({
url : contextPath + "/reply/update",
data : {"replyNo" : replyNo,
"replyContent" : replyContent},
type : "POST",
success : function(result){
if(result > 0){
alert("댓글이 수정되었습니다.");
selectReplyList();
}else{
alert("댓글 수정 실패");
}
},
error : function(req, status, error){
console.log("댓글 수정 실패");
console.log(req.responseText);
}
});
}
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
// 답글 작성 화면 추가
// -> 답글 작성 화면은 전체 화면에 1개만 존재 해야한다!
function showInsertReply(parentReplyNo, btn){
// 부모 댓글 번호, 클릭한 답글 버튼
const temp = document.getElementsByClassName("replyInsertContent");
if(temp.length > 0){ // 답글 작성 textara가 이미 화면에 존재하는 경우
if(confirm("다른 답글을 작성 중입니다. 현재 댓글에 답글을 작성 하시겠습니까?")){
temp[0].nextElementSibling.remove(); // 버튼 영역부터 삭제
temp[0].remove(); // textara 삭제 (기준점은 마지막에 삭제해야 된다!)
} else{
return; // 함수를 종료시켜 답글이 생성되지 않게함.
}
}
// 답글을 작성할 textarea 요소 생성
const textarea = document.createElement("textarea");
textarea.classList.add("replyInsertContent");
// 답글 버튼의 부모의 뒤쪽에 textarea 추가
// after(요소) : 뒤쪽에 추가
btn.parentElement.after(textarea);
// 답글 버튼 영역 + 등록/취소 버튼 생성 및 추가
const replyBtnArea = document.createElement("div");
replyBtnArea.classList.add("reply-btn-area");
const insertBtn = document.createElement("button");
insertBtn.innerText = "등록";
insertBtn.setAttribute("onclick", "insertChildReply("+parentReplyNo+", this)");
const cancelBtn = document.createElement("button");
cancelBtn.innerText = "취소";
cancelBtn.setAttribute("onclick", "insertCancel(this)");
// 답글 버튼 영역의 자식으로 등록/취소 버튼 추가
replyBtnArea.append(insertBtn, cancelBtn);
// 답글 버튼 영역을 화면에 추가된 textarea 뒤쪽에 추가
textarea.after(replyBtnArea);
}
// 답글 취소
function insertCancel(btn){
// 취소
btn.parentElement.previousElementSibling.remove(); // 취소의 부모의 이전 요소(textarea) 제거
btn.parentElement.remove(); // 취소의 부모 요소(reply-btn-area) 제거
}
// 답글 등록
function insertChildReply(parentReplyNo, btn){
// 부모 댓글 번호, 답글 등록 버튼
// 누가? 로그인한 회원의 memberNo ==> loginMemberNo (전역변수)
// 어떤 내용? textarea에 작성된 내용
// 몇번 게시글? 현재 게시글 boardNo ==> boardNo (전역변수)
// 부모 댓글은 누구? parentReplyNo (매개변수)
// 답글 내용
const replyContent = btn.parentElement.previousElementSibling.value;
// 답글 내용이 작성되지 않은 경우
if(replyContent.trim().length == 0){
alert("답글 작성 후 등록 버튼을 클릭해주세요.");
btn.parentElement.previousElementSibling.value = "";
btn.parentElement.previousElementSibling.focus();
return;
}
// 위 if문이 실행 안됨 == 답글이 작성됨 -> ajax 답글 삽입
// "{K:V, K:V, K:V}" -> JSON
$.ajax({
url : contextPath + "/reply/insert",
data : {"memberNo" : loginMemberNo,
"boardNo" : boardNo,
"parentReplyNo" : parentReplyNo,
"replyContent" : replyContent},
type : "POST",
success : function(r){
if(r > 0){
alert("답글이 등록되었습니다.");
selectReplyList(); // 댓글 목록 조회
}else{
alert("답글 등록 실패");
}
},
error : function(){
console.log("답글 등록 중 오류 발생");
}
});
}
-Reply
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@NoArgsConstructor
@ToString
public class Reply {
private int replyNo;
private String replyContent;
private String createDate;
private int boardNo;
private int memberNo;
private String memberNickname;
private String profileImage;
private int parentReplyNo;
}
댓글 조회
-ReplyController
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.google.gson.Gson;
import edu.kh.comm.board.model.service.ReplyService;
import edu.kh.comm.board.model.vo.Reply;
import lombok.extern.slf4j.Slf4j;
//RestController(Representational state transfer)
//:자원을 이름으로 구분해(Representational) 자원의 상태를(state) 주고받는것(transfer)
//->특정한 이름(주소)로 요청이 오면 값으로 응답하는것
//: 요청에 따른 응답이 모두 데이터(값) 자체인 컨트롤러
//->@Controller + @ResponseBody 합쳐진 의미
@Slf4j
@RestController
@RequestMapping("/reply")
public class ReplyController {
@Autowired
private ReplyService replyservice;
/* url : contextPath + "/reply/selectReplyList",
data : {"boardNo" : boardNo},
type : "GET",
*/
//댓글 목록조회
@GetMapping("/selectReplyList")
public String selectReplyList( int boardNo ) {
log.info("dddddddddddddd111 "+boardNo);
List<Reply> rList = replyservice.selectReplyList(boardNo);
return new Gson().toJson(rList);
}
-ReplyService
import java.util.List;
import java.util.Map;
import edu.kh.comm.board.model.vo.Reply;
public interface ReplyService {
/**댓글조회
* @param boardNo
* @return
*/
List<Reply> selectReplyList(int boardNo);
-ReplyServiceImpl
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import edu.kh.comm.board.model.dao.ReplyDAO;
import edu.kh.comm.board.model.vo.Reply;
import edu.kh.comm.common.Util;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class ReplyServiceImpl implements ReplyService {
@Autowired
private ReplyDAO dao;
//댓글조회
@Override
public List<Reply> selectReplyList(int boardNo) {
return dao.selectReplyList(boardNo);
}
-ReplyDAO
import java.util.List;
import java.util.Map;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import edu.kh.comm.board.model.vo.Reply;
@Repository
public class ReplyDAO {
@Autowired
private SqlSessionTemplate sqlSession;
/**댓글조회
* @param boardNo
* @return
*/
public List<Reply> selectReplyList(int boardNo) {
return sqlSession.selectList("replyMapper.selectReplyList", boardNo);
}
-reply-mapper.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="replyMapper">
<!-- 계층형 구조
ex)회사조직도, 가족조직도, 패키지.클래스
오라클에서만 사용가능
-->
<!--
VO Reply 참고
private int replyNo;
private String replyContent;
private String createDate;
private int boardNo;
private int memberNo;
private String memberNickname;
private String profileImage;
private int parentReplyNo; -->
<resultMap type="reply" id="reply_rm">
<id property="replyNo" column="REPLY_NO"/>
<result property="replyContent" column="REPLY_CONTENT"/>
<result property="createDate" column="CREATE_DT"/>
<result property="boardNo" column="BOARD_NO"/>
<result property="memberNo" column="MEMBER_NO"/>
<result property="memberNickname" column="MEMBER_NICK"/>
<result property="profileImage" column="PROFILE_IMG"/>
<result property="parentReplyNo" column="PARENT_REPLY_NO"/>
</resultMap>
<!-- 댓글조회 -->
<select id="selectReplyList" resultMap="reply_rm">
SELECT LEVEL, R.* FROM
(SELECT REPLY_NO, REPLY_CONTENT,
TO_CHAR(CREATE_DT, 'YYYY"년" MM"월" DD"일" HH24"시" MI"분" SS"초"') CREATE_DT,
BOARD_NO, MEMBER_NO, MEMBER_NICK, PROFILE_IMG, PARENT_REPLY_NO, REPLY_ST
FROM REPLY_S
JOIN MEMBER_S USING(MEMBER_NO)
WHERE BOARD_NO = #{boardNo}) R
WHERE REPLY_ST = 'N'
START WITH PARENT_REPLY_NO IS NULL
CONNECT BY PRIOR REPLY_NO = PARENT_REPLY_NO
ORDER SIBLINGS BY REPLY_NO
</select>
'Spring' 카테고리의 다른 글
spring MVC 요청 처리 과정 (0) | 2023.05.09 |
---|---|
0508spring 게시판(5) 글 삭제 (0) | 2023.05.09 |
0508spring 게시판(4) 글 작성, 수정 (0) | 2023.05.08 |
Mybatis 동적 SQL( if, choose(when,oterwise), trim(where,set) ) (1) | 2023.05.03 |
0428spring 마이페이지(3) 회원탈퇴 (1) | 2023.05.03 |