본문 바로가기
SpringBoot

Spring-Boot Blog Project 9. 상세페이지 구현, 수정 삭제 구현

by 김마리님 2020. 7. 27.

이전 포스트 >>

Spring-Boot Blog Project 8. 메인페이지에 글 노출 구현하기

 

Spring-Boot Blog Project 8. 메인페이지에 글 노출 구현하기

이전 페이지 >> Spring-Boot Blog Project 7. 글쓰기 구현하기 이제 홈페이지에 접속할때는 세션을 통해 인증이 없으면 로그인을 반드시 하도록 유도할 것이다. 옵션을 통해 /post나 /user 등의 주소를 가지

itstudy-mary.tistory.com

 

앞서 포스트에서 index에 a태그를 걸었었다.

--중략--
<td><a href="/post/${post.id}">${post.title }</a></td>
--하략--

이 uri를 통해 이 id값을 가지는 게시글의 세부사항을 도출한다.

 

먼저, 세부사항을 도출할 페이지를 생성한다.

 

../views/post/detail.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@include file="../layout/header.jsp" %>

<div class="container">
      
  <table class="table table-striped">
    <thead>
      <tr>
        <th>번호</th>
        <th>제목</th>
        <th>내용</th>
        <th>작성자</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><input id="id" type="text" value="${postDetailRespDto.id }" readonly="readonly"></td>
        <td><input id="title" type="text" value="${postDetailRespDto.title }" readonly="readonly"></td>
        <td><input id="content" type="text" value="${postDetailRespDto.content }" readonly="readonly"></td>
        <td><input id="username" type="text" value="${postDetailRespDto.username }" readonly="readonly"></td>
      </tr>
      <tr>
    </tbody>
  </table>
</div>

<script src="/js/post.js"></script>
<%@include file="../layout/footer.jsp" %>

이 때, 현재 페이지에서는 값을 확인만 할 것이므로 readonly 속성을 전 태그에 걸어둔다.

 

이제 값을 가져오자.

 

/controller/PostController.java

--중략--
		@GetMapping("/post/{id}")
		//?주소 -> 쿼리스트링 받는 것
		// /post/{id} -> 파라메터를 받는 것
		public String getPost(@PathVariable int id, Model model) {
			model.addAttribute("postDetailRespDto", postService.상세보기(id));
			return "post/detail";
		}
--하략--

주소를 보면 id를 {}를 통해 동적으로 받고 있다.

따라서, @PathVariable 어노테이션을 통해 동적으로 받아오는 주소값을 변수로써 받아온다.

이 동적으로 받아온 id값을 통해 상세보기를 유도한다.

 

서비스에서 이 값을 도출시킬 트랜젝션을 생성한다.

/service/PostService.java

//중략

	@Transactional(readOnly = true)
	public PostDetailRespDto 상세보기(int id) {
		 return postRepository.findById(id);
	}
    
//하략    

 

이 때 상세보기를 할때 글의 제목과 내용뿐만 아니라 작성자도 호출해야하므로, 하나의 테이블로 처리할 수 없다. 따라서 이걸 합쳐줄 DTO를 제작한다.

 

/controller/dto/PostDetailRespDto.java

package com.mary.blog.controller.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

//웬만하면 컴포지션 하지 말고

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder

public class PostDetailRespDto {
	private int id;
	private String title;
	private String content;
	private String username;
	
}

 

서비스가 값을 가져올 저장소를 생성한다.

 

/repository/PostRepository.java

package com.mary.blog.repository;

import java.util.List;

import com.mary.blog.controller.dto.PostDetailRespDto;
import com.mary.blog.model.Post;

public interface PostRepository {
	public void save(Post post);
	public List<Post> findAll();
	public PostDetailRespDto findById(int id);
}

 

findById를 실행해줄 mapper xml 을 생성한다.

	<select id="findById" resultType="com.mary.blog.controller.dto.PostDetailRespDto">
		select p.id, p.title, p.content, u.username 
		from post p inner join user u 
		on p.userId=u.id 
		where p.id=#{id}
	</select>

 

이제 이 값을 가져와서 jsp에서 도출하면 결과를 얻을 수 있다.

 

화면에 수정 삭제가 보일 것이다. 그렇다, 수정 삭제도 구현해보자.

버튼을 jQuery를 통해 동적으로 구현할텐데, 일단 어떻게 구현하는지 보자.

 

버튼이 동적으로 구현되는게 보이는가?

 

버튼을 먼저 생성해보자.

 

/post/detail.jsp

//중략

    </tbody>
  </table>
  <button id="btn-update" class="btn btn-primary">수정하기</button>
  <button id="btn-update-mode" class="btn btn-warning">수정</button>
  <button id="btn-delete" class="btn btn-danger">삭제</button>
</div>

<script src="/js/post.js"></script>
<%@include file="../layout/footer.jsp" %>

 

보면 버튼이 3개인데, 버튼을 보였다가, 말았다가 할 것이다.

 

이제 이 값을 js 파일을 통해 동적으로 구현한다.

let index = {
		
	init: function(){
		// let _this = this; //this 바인딩
		// $("#btn-save").on("click", function (){
		
		// 1. 리스너
		$("#btn-save").on("click", ()=>{
			
			// 콜백 스택
			this.save();
		});
		
		// 1. 리스너
		$("#btn-delete").on("click", ()=>{

			// 콜백 스택
			this.deleteContent();
		});
		
		// 1. 리스너
		$("#btn-update-mode").on("click", ()=>{
			console.log(this);
			// 콜백 스택
			this.updateMode();
		});
		$("#btn-update").on("click", ()=>{

			// 콜백 스택
			console.log("클릭됨");
			this.update();
		});
		$("#btn-update").hide();
	},
	
	update: function(){
		console.log("업데이트 요청");
		let data = {
				id: $("#id").val(),
				title: $("#title").val(),
				content: $("#content").val()	
		};
		
		$.ajax({
			type: "PUT",
			url: "/post/"+data.id,
			data: JSON.stringify(data), // json 으로 바꿔줌
			contentType: "application/json; charset=utf-8", 
			dataType: "json"
			
		}).done(function(resp){
			// console.log(JSON.parse(resp)); // text 타입으로 데이터를 받았을때 json 오브젝트로
			// 바꿔줌

				alert("수정 성공");
				location.href="/post/"+data.id;
		}).fail(function(error){
			alert("수정 실패");
			console.log(error);
		})
	},
	
	save: function(){
		// alert("btn-save 로직 실행");
		let data = {
				title: $("#title").val(),
				content: $("#content").val(),
				userId: $("#userId").val()
		};
		
		$.ajax({
			type: "POST",
			url: "/post",
			data: JSON.stringify(data), // json 으로 바꿔줌
			contentType: "application/json; charset=utf-8", 
			dataType: "json"
			
		}).done(function(resp){
			// console.log(JSON.parse(resp)); // text 타입으로 데이터를 받았을때 json 오브젝트로
			// 바꿔줌

				alert("글쓰기 성공");
				location.href="/";
		}).fail(function(error){
			alert("글쓰기 실패");
			console.log(error);
		})
	},
	
	deleteContent: function(){
		// alert("btn-save 로직 실행");
		let data = {
				Id: $("#id").val()
		};
		
		console.log(data.Id);
		
		$.ajax({
			type: "DELETE",
			url: "/post/"+data.Id,
			dataType: "json"
			
		}).done(function(resp){
			alert("삭제 성공");
			location.href="/";
		}).fail(function(error){
			alert("삭제 실패");
			console.log(error);
		})
	},
	
	updateMode: function(){
		// alert("btn-save 로직 실행");
		
		$("#title").attr("readOnly",false);
		$("#content").attr("readOnly",false);
		
		$("#btn-update").show();
		$("#btn-update-mode").hide();

	}
	
}

index.init();

 

버튼을 누르면 updateMode 함수가 작동하며 수정 버튼을 숨기고 수정하기 버튼을 출력 시키고, readOnly 속성을 제거한다.

 

왜 굳이 id를 바꾸지 않냐면, 

리스너가 로드 될때 같은 id를 가지고 있는 값이 없으면 리스너가 바인딩 되지 않기 때문이다. 따라서, element에 동적으로 id를 바꾸어 하는 방식은 선택할 수 없다.

 

동적으로 나타난 update 버튼을 누르면 update함수가 실행되면서 수정된 제목과 타이틀 값이 컨트롤러로 날아간다.

 

PostController.java

//중략
		@PutMapping("/post/{id}")
		public @ResponseBody CommonRespDto<?> update(@RequestBody Post post){
			System.out.println("수정하기");
			postService.수정하기(post);
			return new CommonRespDto<String>(1,"수정 성공");
			
		}

//하략

들어온 값이 json 형태이므로 RequestBody를 통해 값을 받는다.

 

 

PostService.java

//중략
	@Transactional
	public void 수정하기(Post post) {
		 postRepository.update(post);
	}
//하략    

 

PostRepository.java

package com.mary.blog.repository;

import java.util.List;

import com.mary.blog.controller.dto.PostDetailRespDto;
import com.mary.blog.model.Post;

public interface PostRepository {
	public void save(Post post);
	public List<Post> findAll();
	public PostDetailRespDto findById(int id);
	public void update(Post post);
}

 

이제 이 값을 받아올 xml mapper을 생성한다.

 

post.xml

	<update id="update">
		update post set title=#{title}, content=#{content} 
		where id=#{id}
	</update>

여기서 성공한다면 값을 1로 도출하며 성공을 표기하고, 아니면 중간에서 error을 도출하며 삭제 실패를 도출한다.

 

삭제도 마찬가지로 구현한다.

 

PostController.java

		@DeleteMapping("/post/{id}")
		public @ResponseBody CommonRespDto<?> deletePost(@PathVariable int id){

			if(principal.getId()!=postEntity.getUserId()) {
				throw new MyRoleException();
			}
			
			postService.삭제하기(id);
			return new CommonRespDto<String>(1,"삭제 성공");
			
		}

 

PostService.java

//중략
	
	@Transactional
	public void 삭제하기(int id) {
		System.out.println("삭제하기 : "+id);
		 postRepository.deleteContentById(id);
	}
	
//하략

 

PostRepository.java

package com.mary.blog.repository;

import java.util.List;

import com.mary.blog.controller.dto.PostDetailRespDto;
import com.mary.blog.model.Post;

public interface PostRepository {
	public void save(Post post);
	public List<Post> findAll();
	public PostDetailRespDto findById(int id);
	public void deleteContentById(int id);
	public void update(Post post);
}

 

Post.xml

	<delete id="deleteContentById">
		delete from post where id=#{id}
	</delete>

 

반응형