7. 스프링부트(블로그만들기) 게시글 삭제, 수정

윤주헌's avatar
Aug 21, 2024
7.  스프링부트(블로그만들기) 게시글 삭제, 수정

삭제 쿼리부터 보자

생각할 사항

만약 5번까지 데이터 있는데 없는 6번 데이터를 삭제를 하려고 한다면 쓸데없이 6번이 없는데 트랜잭션 걸고 다른 애들이 일 못하게 wait만든다.
그래서 먼저 6번이 있는지 확인하고 없으면 행동 안 하게 해야 한다

메서드는 직관적으로!

레파지토리 기능명(메서드)을 직관적으로 적어야 한다!!

예시

만약 계좌 이체를 하려고 한다 1번 계좌 5000, 2번계좌 2000
1번을 2번계좌에 2천원 이체할 거야 → 1번 계좌는 3000, 2번 계좌는 4000
업데이트가 2번 일어난다
 
레파지토리에 기능명을 이체하기로 하면 안되고 업데이트로 해야한다
업데이트가 2번 일어나야 하는데 여기에 트랜잭션 하면
외부 현재 controller있는데 controller가 u1 u2 두 번 때린다
만약 update 먼저 수행 commit 되서 반영 3000원 남음
 
u2 실행하다 실패하면 롤백되고 4천원으로 변경 돼야 하는데 2천원 그대로 있다.
이 때 필요한 것이
트랜잭션 관리해주는 레이어가 필요하다 → service

책임들

  • 컨트롤러
💡
요청, 응답
  • 레파지토리
💡
db연결
  • service
💡
트랜잭션 관리

만약

레파지토리에 트랜잭션을 안 하고 컨트롤러한테 트랜잭션 걸면 어떻게 될까
 
문제
  1. request 돼서 처리되는 시간이 오래되는 게 아니고 트랜잭션의 시간이 오래걸림 오래걸린다는 말은 책을 뽑을 건데 집에서 양치할 때 부터 트랜잭션 걸어두고 다른 애는 이미 책 앞에 있는데 못 뽑고 기다리고 있기 때문이다.
 
서비스로 하면 딱 필요한 시간에만 service해주면 된다
서비스의 역할 다른 것은 버전 2 때 알려주신다 함

시작

1. 보드 레파지토리에서 deleteById 만들기

@Transactional public void deleteById(int id) { Query query = em.createNativeQuery("delete from board_tb where id =?"); query.setParameter(1, id); query.executeUpdate(); }

2. 테스트로 가서

@Test public void deleteById_test() { //given int id = 1; //when boardRepository.deleteById(id); //eye }
쿼리는 이상 없음

then 검증 방법

findById로 감
@Test public void findById_test() { //given int id = 1; //when Board board = boardRepository.findById(id); //eye 사실은 then 원래는 메서드로 해야 하는데 눈으로 해서 (어썰션으로 한다)(통합테스트 할 때 본다) System.out.println(board.getId()); System.out.println(board.getTitle()); System.out.println(board.getContent()); }
notion image
아래 것이 검증할 때 기능 더 많음
 
then = 코드로 검증하는 것
@Test public void findById_test() { //given int id = 1; //when Board board = boardRepository.findById(id); //eye 사실은 then 원래는 메서드로 해야 하는데 눈으로 해서 (어썰션으로 한다)(통합테스트 할 때 본다) 검증(눈으로) System.out.println(board.getId()); System.out.println(board.getTitle()); System.out.println(board.getContent()); //then 검증(코드로) 제목1이길 기대함 Assertions.assertThat(board.getTitle()).isEqualTo("제목1"); }
 
만약 제목1을 제목 2로 바꾸면 오류남!
기대한 거는 제목2인데 실제는 제목1이 나왔어
notion image
@Test public void deleteById_test() { //given int id = 1; //when boardRepository.deleteById(id); //findById에서 터지는지 확인 어제 익셉션 해서 터진거 컨트롤 가능 //e.getMessage가 글 하고 같으면 기대한거 하고 맞음 (이거는 스태틱으로 띄워두는 게 좋음 잘못적을 수도 있기 때문에) boardRepository.findById(id); //eye }
notion image
@Test public void deleteById_test() { //given int id = 1; //when boardRepository.deleteById(id); //findById에서 터지는지 확인 어제 익셉션 해서 터진거 컨트롤 가능 //e.getMessage가 글 하고 같으면 기대한거 하고 맞음 (이거는 스태틱으로 띄워두는 게 좋음 잘못적을 수도 있기 때문에) try { boardRepository.findById(id); } catch (Exception e) { Assertions.assertThat(e.getMessage()).isEqualTo("게시글 id를 찾을 수 없습니다"); } //eye }
 

3. 보드 컨트롤러로 이동

@PostMapping("/board/{id}/delete") public String delete(@PathVariable("id") int id) { //원래는 조회를 하고 삭제해야하는데 V1이라 그냥 함 boardRepository.deleteById(id); return "redirect:/board"; }

주의 위험!

post를 get으로 하면 왜 안될까?
게시글이 1~5번까지 있는데 1번글 2번글을 각기 다른 유저가 적음
 
  • get으로 했을 시 위험
2번유저가 1번글 지울 수 있다
 
  • 어떻게?
2번유저가 블로그에 “1님 큰일났어요. 당신 계좌가 다 털렸어요” 라고 남기고
1번유저가 로그인해서 게시글 보고 이 링크를 클릭하면 삭제가 된다!
💡
get은 하이퍼 링크로 해서 삭제 가능해서 위험함!!
 

삭제 결과

notion image
삭제 잘됨

게시글 업데이트

2가지 로직이 필요함
1. findById 업데이트 화면으로 가기
  1. 업데이트 쿼리 필요
 
게시글 수정
  1. 게시글 수정화면 가기
  1. 게시글 수정하기
잘게 쪼개서 1번이 실행되면 2번은 또 되니까

왜 쪼개 봐야하는가?

만약 레이더 통신하면 싸인 코싸인 이런게 무한대로 흘러감
이거를 분석할 때 엄청 긴 것을 무조건 잘라서 해석함(자른 것을 해석 할 수 있으면 다른 것 할 수 있다.)
→ 한번에 만드는 게 아닌 쪼개야 한다
 
 

클릭 시 업데이트 화면으로 이동

일단 클릭했을 때 업데이트 화면으로 가는 거를 먼저 할거임
이 화면으로 갈 수 있게
notion image

1. 컨트롤러 이동

기존 코드
@GetMapping("/board/{id}/update-form") public String updateForm(@PathVariable("id") int id) { return "board/update-form"; } // PathVariable 이게 정규 표현식을 해줌 숫자로 받을 수 있게 해준다
  1. 조회하기
@GetMapping("/board/{id}/update-form") public String updateForm(@PathVariable("id") int id, HttpServletRequest request) { //못찾으면 터진다! null 안들어옴 우리가 설정해서 Board board = boardRepository.findById(id); //리퀘스트에 담는다 request.setAttribute("model", board); return "board/update-form"; }
헬스장 가방에 담음
→ 가방에 들은거 뿌리면 끝
 

2. update-form 머스테치이동 게시글 수정화면 가기

기존 것
{{>layout/header}} <div class="container p-5"> <div class="card"> <div class="card-header"><b>글수정하기 화면입니다</b></div> <div class="card-body"> <form> <div class="mb-3"> <input type="text" class="form-control" placeholder="Enter title" name="title" value="{{model.title}}"> </div> <div class="mb-3"> <textarea class="form-control" rows="5" name="content">{{model.content}}</textarea> </div> <button class="btn btn-primary form-control">글수정하기완료</button> </form> </div> </div> </div> {{>layout/footer}}
 
notion image
잘 작동함!
 
 

히든으로 사용하기도 한다

하지만
주소 뒤에 있는거는 where절에 쓰인다 where은 바디에 담지 않는다 약속이다!
{{>layout/header}} <div class="container p-5"> <div class="card"> <div class="card-header"><b>글수정하기 화면입니다</b></div> <div class="card-body"> <form action="/board/{{model.id}}/update"> <input type="hidden" name="id" value="{{model.id}}"> <div class="mb-3"> <input type="text" class="form-control" placeholder="Enter title" name="title" value="{{model.title}}"> </div> <div class="mb-3"> <textarea class="form-control" rows="5" name="content">{{model.content}}</textarea> </div> <button class="btn btn-primary form-control">글수정하기완료</button> </form> </div> </div> </div> {{>layout/footer}}
이거 보다는 주소에 거는게 맞기에

실제 사용

이걸로 사용
{{>layout/header}} <div class="container p-5"> <div class="card"> <div class="card-header"><b>글수정하기 화면입니다</b></div> <div class="card-body"> <form action="/board/{{model.id}}/update" method="post"> <div class="mb-3"> <input type="text" class="form-control" placeholder="Enter title" name="title" value="{{model.title}}"> </div> <div class="mb-3"> <textarea class="form-control" rows="5" name="content">{{model.content}}</textarea> </div> <button class="btn btn-primary form-control">글수정하기완료</button> </form> </div> </div> </div> {{>layout/footer}}
만약 메서드를 put지원하면 form에 /update 안 써도 됨
원래는 주소에 동사를 걸지 않는다
 

3. 레파지토리 이동

@Transactional public void updateById(String title, String content, int id) { Query query = em.createNativeQuery("update board_tb set title = ?, content = ? where id = ?"); query.setParameter(1, title); query.setParameter(2, content); query.setParameter(3, id); query.executeUpdate(); }
notion image
 

4. 테스트하기

실행해서 쿼리 잘 작동하는지 확인
@Test public void updateById_test() { //given int id = 1; String title = "제목1변경"; String content = "내용1변경"; //when boardRepository.updateById(title, content, id); //eye }
@Test public void updateById_test() { //given int id = 1; String title = "제목1변경"; String content = "내용1변경"; //when boardRepository.updateById(title, content, id); //eye Board board = boardRepository.findById(id); //then Assertions.assertThat(board.getTitle()).isEqualTo("제목1변경"); }
테스트 통과
 

5. 컨트롤러로 이동

 

옛 방식 및 예시

통신을 할 때 app에서 os로 갈 때 바이트 스트림이 있다
원래는 직접 하나 하나 바이트스트림을 통해서 넘겨줘야 한다
  • 문제
만약 아래서 받다가 잠시 멈추게 된다면 위에서 주는 사람도 멈추게 되어 답답하게 된다.
그래서 app에서 바이트라는 통에 한번에 담고 알아서 os 바이트 통으로 이동한다
 
나는 내 박스에 넣어 알아서 던지고 내 박스에 담김 → 통신하면 버퍼 2개 내 버퍼(write버퍼),
상대 버퍼(버퍼드read) (편하게 하려고 printwriter사용함)
브라우저라는 app에서 버퍼를 던진다(글쓰기 수정완료) 이게 버퍼에 쌓임 스프링이 버퍼드 리더가 필요해서 이걸로 받음 rquest로 버퍼드 리더에 접근할 수 있다.
request는 디스페처 서블릿한테 받음
방식 →
@PostMapping("/board/{id}/update") public String update(@PathVariable("id") int id, HttpServletRequest request) { BufferedReader br = request.getReader(); String body = br.readLine(); //title=제목1변경&content=내용1변경 //내가 직접 파싱해야함 split(쪼갠다) 1번지 String title =body.split("&")[0].split("=")[1]; String title =body.split("&")[1].split("=")[1]; }
버퍼 → 보조 스트림(버퍼에서 원할 때 하나씩 빼다 먹을 수 있다)
기본 1
@PostMapping("/board/{id}/update") public String update(@PathVariable("id") int id, HttpServletRequest request) { String title = request.getParameter("title"); String content = request.getParameter("content"); }
 

update 최종

@PostMapping("/board/{id}/update") public String update(@PathVariable("id") int id, @RequestParam("title") String title, @RequestParam("content") String content) { boardRepository.updateById(title, content, id); //상세보기로 가야함 return "redirect:/board/" + id; }
 
  • 왜 주소에 id넣냐? where절에 걸리기 때문에
 
  • board/detail하면 안됨? 안된다!
@PostMapping("/board/{id}/update") public String update(@PathVariable("id") int id, @RequestParam("title") String title, @RequestParam("content") String content) { boardRepository.updateById(title, content, id); //상세보기로 가야함 return "board/detail"; 이러면 못 뿌린다 만들었으면 redirect해줘야 한다!! }
참고 사진
notion image
 

파라미터스 플래그 오류

notion image

save도 수정 직접 지정해줘야 한다

@PostMapping("/board/save") public String save(@RequestParam("title") String title, @RequestParam("content") String content) { //스프링 기본 견략 = x-www-form-urlencoded 파싱 매개변수만 같으면 됨 화면에 폼테그 name 과 반드시 같아야 한다!! boardRepository.save(title, content); //보드 레파지토리 객체가 어디있나? Ioc에 있다 autoWrid해서 가져옴 return "redirect:/board"; }
 
파라미터 오류 안 뜨려면
notion image
notion image

인증 맛보기

🔐
인증 맛보기
Share article

code-sudal