Continuing from 1017

Comments on Posts

  • 1016~1017에서는 게시물 간의 계층(level)과 순서(step)를 통해 관계를 정립했다면, 여기서는 하나의 게시물상에 표현되는 댓글의 CRUD 구현

DB for Comments

create table reanswer(idx smallint auto_increment primary key,
num smallint,
nickname varchar(30),
pass varchar(20),
content varchar(2000),
writeday datetime,
foreign key(num) references reboard(num) on delete cascade);
  • num은 댓글이 표현되는 원 게시물의 num (foreign key, on delete cascade)

DTO

public class AnswerDto {

	private int idx;
	private int num;
	private String nickname;
	private String pass;
	private String content;
	private Timestamp writeday;

	//getter,setter
}

View_Detail Page_Additional V

  • 1017View_Detail Page에서 댓글 CRUD 기능 추가한 코드
<body>
	<div>
		<table>
			<caption align="top"><b>내용보기</b></caption>
			<tr>
				<td>
					<h2><b>${dto.subject }</b></h2>
					<span style="float: right;margin-right: 20px">
						조회: ${dto.viewcount }&nbsp;&nbsp;&nbsp;
						<fmt:formatDate value="${dto.writeday }" pattern="yyyy-MM-dd HH:mm"/>
					</span><br>
					<h5><b>작성자: ${dto.writer }</b></h5>
				</td>
			</tr>
			<tr>
				<td>
					<pre>${dto.content }</pre><br>
					<!-- 이미지가 있을때만 출력 -->
					<c:if test="${dto.photo!='no' }">
						<c:forTokens var="p" items="${dto.photo }" delims=",">
							<a href="../upload/${p }"><img src="../upload/${p }"></a>
						</c:forTokens>
					</c:if>
				</td>
			</tr>
			
			<!-- 댓글 -->
			<tr>
				<td>
					<div id="answer">
						<b>댓글: ${acount }</b><br>
						<c:forEach var="a" items="${alist }">
							${a.nickname }: ${a.content }&nbsp;&nbsp;
							<span>
								<fmt:formatDate value="${a.writeday }" pattern="yyyy-MM-dd HH:mm"/>&nbsp;
								<i class="bi bi-trash3-fill" style="cursor: pointer" idx="${a.idx }" num="${dto.num }"></i><br>
							</span>
						</c:forEach>
					</div>
					<form action="ainsert" method="post">
						<!-- hidden -->
						<input type="hidden" name="num" value="${dto.num }">
						<input type="hidden" name="currentPage" value="${currentPage }">
						<div class="d-inline-flex">
							<b>닉네임:</b>&nbsp;
							<input type="text" name="nickname" class="form-control" required="required">&nbsp;&nbsp;
							<b>비밀번호:</b>&nbsp;
							<input type="password" name="pass" class="form-control" required="required">
						</div>
						<br>
						<div class="d-inline-flex">
							<input type="text" name="content" class="form-control" required="required" placeholder="댓글내용을 입력해주세요">&nbsp;&nbsp;
							<button type="submit">확인</button>
						</div>
					</form>
				</td>
			</tr>
			
			<!--글쓰기,답글,수정,삭제,목록 버튼 생략-->
		</table>
	</div>
  • 댓글 기능을 담당하는 코드는 크게 표현, 입력 두 부분으로 나뉨
  • 입력은 <form>을 사용하되, 해당 원 게시물과의 연관성을 기억하기 위해 게시물의 primary key(num)을 submit하여야 하며, currentPage 값은 계속 활용하기 위해 전달해야 함
  • 출력은 추후에 설명

Mapper_Control for Comments

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="answer">
	<insert id="insertOfReanswer" parameterType="adto">
		insert into reanswer values(null,#{num},#{nickname},#{pass},#{content},now())
	</insert>

	<select id="selectNumOfReanswer" resultType="adto" parameterType="int">
		select * from reanswer where num=#{num} order by idx
	</select>

	<update id="updateViewcount" parameterType="int">
		update reboard set viewcount=viewcount-1 where num=#{num}
	</update>

	<select id="authentificationOfAnswer" parameterType="HashMap" resultType="int">
		select count(*) from reanswer where idx=#{idx} and pass=#{pass}
	</select>

	<delete id="deleteReanswer" parameterType="int">
		delete from reanswer where idx=#{idx}
	</delete>
</mapper>
  • ‘updateViewcount’ Mapper는 의도하지 않게 증가된 조회 수를 제거하기 위한 sql문 (필요할 때마다 사용)

DAO

@Repository
public class AnswerDao implements AnswerDaoInter{

	@Autowired
	private SqlSession session;

	@Override
	public void insertAnswer(AnswerDto dto) {
		session.insert("insertOfReanswer", dto);
	}

	@Override
	public List<AnswerDto> getAnswerList(int num) {
		return session.selectList("selectNumOfReanswer", num);
	}

	@Override
	public void controlViewcount(int num) {
		session.update("answer.updateViewcount", num);
	}

	@Override
	public int authentification(int idx, int pass) {
		
		Map<String, Integer> map=new HashMap<String, Integer>();
		map.put("idx", idx);
		map.put("pass", pass);
		
		return session.selectOne("authentificationOfAnswer", map);
	}

	@Override
	public void deleteAnswer(int idx) {
		session.delete("deleteReanswer", idx);
	}
}
  • Mapper를 사용, 조작하기 위한 DAO이며 특별한 것은 없음

Controller_Insert

@Controller
public class AnswerController {

	@Autowired
	AnswerDaoInter adao;
	
	@PostMapping("/board/ainsert")
	public String insert(@ModelAttribute AnswerDto dto,@RequestParam int currentPage) {
		
		adao.insertAnswer(dto);
		adao.controlViewcount(dto.getNum());
		
		return "redirect:content?num="+dto.getNum()+"&currentPage="+currentPage;
	}
}
  • currentPage는 기능상 불필요한 데이터지만 다시 Detail Page로 돌아갔을 때 필요하므로 계속 전달
  • 매핑 주소를 redirect로 전달함으로써, 원 매핑 주소(content)에 해당하는 메서드에 전달한 데이터를 추가하여 코드 실행
    • 사실 num, currentPage 데이터를 넘겨주는 이유는, redirect하여 실행되는 경우, 해당 매핑 메서드가 View의 <form>을 통해 데이터를 받지 않고 redirect로부터 GET 방식으로 데이터를 받기 때문
    • 여기서 넘겨주지 않으면 원래 <form>으로부터 받던 데이터가 넘어오지 않으므로 null 값으로 인식

Controller_Redirect

  • redirect 시킨 원 매핑 주소(content)에서 받은 데이터 활용하여 댓글 출력
  • 하나의 매핑 주소에는 하나의 Controller만 접근 가능
  • 다른 매핑 주소에서 데이터를 처리 후 해당 매핑 주소에 도달하기 위해서는 Redirect 사용
  • Redirect를 return하는 Controller는 데이터를 처리 및 전달하여 해당 매핑 주소의 Controller에서 최종적으로 실행
    • 하지만 이 과정에서 해당 매핑 주소를 지향하는 다른 Redirect Controller는 작동하지 않으므로, 다른 Redirect Controller에서 처리 및 전달할 작업은 실행되지 않음
    • 즉 실행한 하나의 Redirect Controller와 해당 매핑 주소의 Controller 두 개의 Controller만 실행 (직렬 실행은 누적이 가능하지만, 병렬 실행은 불가)
@Controller
public class BoardContentController {
	
	@Autowired
	BoardDaoInter dao;
	
	@Autowired
	AnswerDaoInter adao;

	@GetMapping("/board/content")
	public String content(Model model,@RequestParam int num,int currentPage) {
		
		dao.updateViewcount(num);
		
		BoardDto dto=dao.getData(num);
		
		//num에 해당하는 댓글을 db에서 가져와서 보낸다
		List<AnswerDto> alist=adao.getAnswerList(num);
		
		//값이 있을때만 넘겨야 하므로
		model.addAttribute("acount", alist.size());
		model.addAttribute("alist", alist);
		
		model.addAttribute("dto", dto);
		model.addAttribute("currentPage", currentPage);

		return "reboard/content";
	}
}
  • 1017의 동일 Controller에 추가한 코드임
  • 댓글 데이터를 처리하는 DAO(AnswerDaoInter)를 통해 댓글 데이터 List를 호출 → 이로부터 .size() 객체를 통해 데이터의 개수를 구하여 전달 (acount)
  • View에서 위의 데이터를 받아 출력

View_Detail Page_Delete Comments

  • 위에서 작성한 View_Detail Page 중 댓글 출력 부분만 발췌
<div id="answer">
	<b>댓글: ${acount }</b><br>
	<c:forEach var="a" items="${alist }">
		${a.nickname }: ${a.content }&nbsp;&nbsp;
		<span>
			<fmt:formatDate value="${a.writeday }" pattern="yyyy-MM-dd HH:mm"/>&nbsp;
			<i class="bi bi-trash3-fill" style="cursor: pointer" idx="${a.idx }" num="${dto.num }"></i><br>
		</span>
	</c:forEach>
</div>
  • 댓글 출력란 옆에 작성일 정보와 아이콘 추가 (삭제 위해)
  • 삭제 시 필요한 idx 값과, 조회 수 증가 방지를 위한 num 값을 attr 삽입
<script>
	$("i").on("click",function(){
		var idx=$(this).attr("idx");
		var num=$(this).attr("num");
		
		var pass=prompt("비밀번호를 입력해주세요");
		
		if(pass==null)
			return;
		else if(pass==""){
			alert("비밀번호를 입력해주세요");
			return;
		}
		
		$.ajax({
			data:{"idx":idx,"pass":pass,"num":num},
			type:"get",
			dataType:"json",
			url:"delete",
			success:function(res){
				
				if(res.no==1){
					alert("삭제 완료");
					location.reload();
				}
				else{alert("비밀번호가 틀렸습니다");}
			}
		});
	});
</script>
  • 삭제 아이콘을 클릭 시, 해당 아이콘에 삽입한 idx, num 호출
  • prompt를 통해 비밀번호 입력하여 변수로 저장
    • prompt의 ‘취소’ 버튼 클릭 시(null) 혹은 미입력 후 ‘확인’ 클릭 시(””) 예외 처리 (함수 종료 : return;)
    • 입력 시 ajax로 data 전송 (url의 주소를 통해 json 형태의 데이터 처리 후 반환)

Controller_Ajax_JSON

@RestController
public class AnswerRestController {

	@Autowired
	BoardDaoInter bdao;
	
	@Autowired
	AnswerDaoInter adao;
	
	@GetMapping("/board/delete")
	public Map<String, Integer> delete(@RequestParam int idx,int pass,int num) {
		
		int flag=adao.authentification(idx, pass);
		int val=0;
		
		if(flag!=0) {
			adao.deleteAnswer(idx);
			val=1;
			adao.controlViewcount(num);
		}
		
		Map<String, Integer> map=new HashMap<String, Integer>();
		map.put("no", val);
			
		return map;
	}
}
  • RestController로 지정했으므로 위 클래스에 속한 모든 메서드는 return 값의 데이터를 JSON 형식으로 변환 (Key, Value 쌍의 데이터를 return할 경우를 전제)
    • 단순 데이터를 전달할 시 변환되지 않고 그대로 출력
    • 일반 Controller 지정 시 @ResponseBody 어노테이션으로 각 메서드마다 기능 지정 가능
  • Ajax url을 통해 받은 데이터를 처리
    • 비밀번호 인증
    • 맞을 시 삭제 처리, 처리 완료 신호 작성, 조회 수 제어
  • Map에 Key, Value 쌍의 데이터로 Ajax에 전달

Mark Comment Data on Whole List

  • 전체 목록 출력 시 각 게시물의 제목 옆에 첨부된 댓글 개수 출력
  • 댓글 개수 클릭 시 댓글 창으로 이동 후 Focus On

Controller_Whole List

@Controller
public class BoardListController {

	@Autowired
	BoardDaoInter dao;
	
	@Autowired
	AnswerDaoInter adao;
	
	@GetMapping("/board/list")
	public String list(Model model,
			@RequestParam(value = "currentPage",defaultValue = "1") int currentPage) {
		
		//페이징처리에 필요한 변수선언
		int totalCount=dao.getTotalCount();
		int totalPage; //총 페이지수
		int startPage; //각블럭에서 보여질 시작페이지
		int endPage; //각블럭에서 보여질 끝페이지
		int startNum; //db에서 가져올 글의 시작번호(mysql은 첫글이 0,오라클은 1)
		int perPage=10; //한페이지당 보여질 글의 갯수
		int perBlock=5; //한블럭당 보여질 페이지 개수
		
		totalPage=totalCount/perPage+(totalCount%perPage==0?0:1);

		startPage=(currentPage-1)/perBlock*perBlock+1;
		     
		endPage=startPage+perBlock-1;
 
		 if(endPage>totalPage)
			 endPage=totalPage;

		startNum=(currentPage-1)*perPage;

		//각페이지에서 필요한 게시글 가져오기
		List<BoardDto> list=dao.getPagingList(startNum, perPage);
		
////////////////////////////////////추가부분
		//list의 각글에 댓글개수 표시
		for(BoardDto d:list) {
			
			d.setAcount(adao.getAnswerList(d.getNum()).size());
		}
///////////////////////////////////
		
		//각 페이지에 출력할 시작번호
		int no=totalCount-startNum;
		
		model.addAttribute("totalCount", totalCount);
		model.addAttribute("list", list); //댓글포함후 전달
		model.addAttribute("startPage", startPage);
		model.addAttribute("endPage", endPage);
		model.addAttribute("totalPage", totalPage);
		model.addAttribute("no", no);
		model.addAttribute("currentPage", currentPage);
		
		return "reboard/boardList";
	}
}
  • 1017에서 작성한 전체 리스트 출력 Controller에 추가한 코드
  • 게시물에 첨부된 댓글의 List 호출 후 size() 객체를 통해 데이터 개수 호출
    • 따로 설명하지 않았지만 BoardDto에 int acount 변수와 getter, setter 추가
    • DTO 추가하지 않고도 Model의 addAttribute() 객체로 단순히 전달해도 무방
  • setter를 통해 List의 각 DTO 내 acount 변수를 setting했으므로 List 전달 시 변경된 데이터 전달됨
  • 해당 View에서 데이터 받아서 적절히 사용
profile
초보개발자

0개의 댓글