삭제 쿼리부터 보자
생각할 사항
만약 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
트랜잭션 관리
만약
레파지토리에 트랜잭션을 안 하고 컨트롤러한테 트랜잭션 걸면 어떻게 될까
문제
- 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());
}

아래 것이 검증할 때 기능 더 많음
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이 나왔어

@Test
public void deleteById_test() {
//given
int id = 1;
//when
boardRepository.deleteById(id);
//findById에서 터지는지 확인 어제 익셉션 해서 터진거 컨트롤 가능
//e.getMessage가 글 하고 같으면 기대한거 하고 맞음 (이거는 스태틱으로 띄워두는 게 좋음 잘못적을 수도 있기 때문에)
boardRepository.findById(id);
//eye
}

@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님 큰일났어요. 당신 계좌가 다 털렸어요” 라고 남기고
http://localhost:8080/board/1/delete 링크를 남겼다.
1번유저가 로그인해서 게시글 보고 이 링크를 클릭하면 삭제가 된다!
get은 하이퍼 링크로 해서 삭제 가능해서 위험함!!
삭제 결과

삭제 잘됨
게시글 업데이트
2가지 로직이 필요함
1. findById 업데이트 화면으로 가기
- 업데이트 쿼리 필요
게시글 수정
- 게시글 수정화면 가기
- 게시글 수정하기
잘게 쪼개서 1번이 실행되면 2번은 또 되니까
왜 쪼개 봐야하는가?
만약 레이더 통신하면 싸인 코싸인 이런게 무한대로 흘러감
이거를 분석할 때 엄청 긴 것을 무조건 잘라서 해석함(자른 것을 해석 할 수 있으면 다른 것 할 수 있다.)
→ 한번에 만드는 게 아닌 쪼개야 한다
클릭 시 업데이트 화면으로 이동
일단 클릭했을 때 업데이트 화면으로 가는 거를 먼저 할거임
이 화면으로 갈 수 있게

1. 컨트롤러 이동
기존 코드
@GetMapping("/board/{id}/update-form")
public String updateForm(@PathVariable("id") int id) {
return "board/update-form";
}
// PathVariable 이게 정규 표현식을 해줌 숫자로 받을 수 있게 해준다
- 조회하기
@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}}

잘 작동함!
히든으로 사용하기도 한다
하지만
주소 뒤에 있는거는 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();
}

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해줘야 한다!!
}
참고 사진

파라미터스 플래그 오류

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";
}
파라미터 오류 안 뜨려면


인증 맛보기
인증 맛보기Share article