스프링부트

8장 게시글 삭제하기: Delete

hpehpeyy 2025. 1. 23. 15:56

8.1 데이터 삭제 과정

게시판에서 글을 삭제하는 과정은 다음 순서로 이루어집니다.

 

1. 클라이언트가 HTTP 메서드로 특정 게시글의 삭제를 요청합니다.

2. 삭제 요청을 받은 컨트롤러는 리파지터리를 통해 DB에 저장된 데이터를 찾아 삭제합니다. 이 작업은 기존 데이터가 있는 경우에만 수행됩니다.

3. 삭제가 완료됐다면 클라이언트를 결과 페이지로 리다이렉트합니다.

 

데이터 삭제 과정

그런데 결과 페이지로 리다이렉트할 때 클라이언트에 삭제 완료 메시지도 같이 띄워 주면 더 좋겠죠? 이를 위한 클래스가 바로 RedirectAttributes입니다. RedirectAttributes 객체의 addFlashAttribute()라는 메서드는 리다이렉트된 페이지에서 사용할 일회성 데이터를 등록할 수 있습니다.


8.2 데이터 삭제하기

서버를 실행하고 localhost:8080/articles에 접속하면 더미 데이터 3개가 준비돼 있습니다. 세 번째 게시글을 클릭해 상세 페이지로 갑니다. 여기서 [Delete] 버튼을 클릭해 해당 글을 지워 보겠습니다.

 

8.2.1 Delete 버튼 추가하기

1. [Delete] 버튼을 상세 페이지에 추가하기 위해 show.mustache 파일을 엽니다. 코드를 보면 [Edit] 버튼이 만들어져 있는데 이를 복사해 [Delete] 버튼을 만들겠습니다.

 

 (1) <a href=...>Edit</a> 부분을 복사한 후 다음 줄에 붙여 넣습니다.

 (2) 버튼 텍스트를 Delete로 수정합니다.

 (3) 서버가 삭제와 수정 요청을 구분해서 동작할 수 있도록 삭제 요청 URL을 "/articles/{{article.id}}/delete"로 수정합니다.

 (4) [Edit] 버튼과 구분하기 위해 버튼 색상을 빨간색으로 수정합니다. class의 속성 값을 "btn btn-danger"로 수정합니다.

<a href="/articles/{{article.id}}/delete" class="btn btn-danger">Delete</a>
더보기

부트스트랩을 이용하면 버튼 색상을 원하는대로 수정할 수 있습니다. 예를 들어, btn-success는 초록색, btn-warning은 노란색으로 나옵니다. 자세한 색상별 class 속성 값은 부트스트랩 사이트에서 [Docs] 메뉴를 클릭한 다음 'button'으로 검색해 보세요.

2. 버튼이 잘 삽입됐는지 확인해 볼까요? 망치 아이콘을 클릭하고 웹 브라우저에서 새로 고침을 하면 [Delete] 버튼이 나타납니다.

 

3. [Delete] 버튼을 클릭해 보면 에러 페이지가 나옵니다. 아직 Delete 요청을 받아 줄 컨트롤러를 만들지 않았기 때문입니다. 해당 컨트롤러를 만들어 봅시다.

 

8.2.2 Delete 요청을 받아 데이터 삭제하기

본격적으로 코드를 작성하기 전에 한 가지 알아야 할 개념이 있습니다. 클라이언트에서 서버로 요청을 보낼 때 크게 4가지 HTTP 메서드를 활용한다고 했습니다. 데이터를 생성할 때는 POST, 조회할 때는 GET, 수정할 때는 PATCH(PUT), 삭제할 때는 DELETE입니다. 여기서는 데이터를 삭제하므로 DELETE를 써야 합니다.

 

그런데 HTML에서는 POST와 GET을 제외한 다른 메서드를 제공하지 않습니다. 그래서 여기에서는 GET 방식으로 삭제 요청을 받아 처리하겠습니다. 

 

- delete() 메서드 기본 틀 만들기

1. ArticleController 의 update() 메서드 아래에 삭제 요청을 받아 처리할 delete() 메서드를 추가합니다.

 (1) delete() 메서드의 기본 틀을 입력하고 return 값으로 null을 반환합니다.

 (2) 삭제 요청을 받아 오기 위해 매핑 어노테이션을 작성합니다. 원래대로 하면 @DeleteMapping("/articles/{id}/delete")로 작성하면 됩니다. id가 1번인 글의 삭제 요청은 /articles/1/delete 페이지로, 2번 글의 삭제 요청은 /articles/2/delete 페이지로 연결하는 거죠. 그런데 HTML에서 DELETE 메서드를 지원하지 않으니 @GetMapping("/articles/{id}/delete")로 작성합니다.

@GetMapping("/articles/{id}/delete") //2.URL 요청 접수
public String delete() { //1.메서드 생성 및 null 값 반환
    return null;
}

 

2. delete() 메서드가 잘 동작하는지 확인하는 로그도 추가합니다.

@GetMapping("/articles/{id}/delete") //2.URL 요청 접수
public String delete() { //1.메서드 생성 및 null 값 반환
	log.info("삭제 요청이 들어왔습니다!!");
	return null;
}

 

3. 중간 점검해 봅시다. 서버를 재시작하고 localhost:8080/articles/3 페이지로 접속한 후 [Delete] 버튼을 클릭합니다. 인텔리제이의 실행창을 보면 "삭제 요청이 들어왔습니다!!" 로그가 잘 찍혔습니다.

 

4. 그다음은 세 부분으로 나누어 처리합니다.

 (1) 삭제할 대상 가져오기

 (2) 대상 엔티티 삭제하기

 (3) 결과 페이지로 리다이렉트하기

public String delete() { //1.메서드 생성 및 null 값 반환
    log.info("삭제 요청이 들어왔습니다!!");
    //(1) 삭제할 대상 가져오기

    //(2) 대상 엔티티 삭제하기

    //(3) 결과 페이지로 리다이렉트하기
    return null;
}

 

- 삭제할 대상 가져오기

1. DB에 접근해 데이터를 처리할 때는 JPA의 리파지터리를 이용합니다.

 (1) articleRepository.findById(id) 메서드로 DB에 해당 id를 가진 데이터가 있는지 찾습니닫. 만약 찾으면 Article 타입의 target 변수에 저장하고, 찾지 못하면 null을 반환합니다.

 

 (2) findById(id)를 호출할 때 사용한 id 변수는 delete() 메서드에 선언되지 않았습니다. 이 id는 @GetMapping("/articles/{id}/delete")의 URL 주소에서 가져오므로 delete() 메서드의 매개변수로 @PathVariable Long id를 써 줍니다. 

@GetMapping("/articles/{id}/delete") //2.URL 요청 접수
public String delete(@PathVariable Long id) { //2.id를 매개변수로 가져오기
    log.info("삭제 요청이 들어왔습니다!!");
    //(1) 삭제할 대상 가져오기
    Article target = articleRepository.findById(id).orElse(null); //1.데이터 찾기

 

이제 클라이언트에서 1번을 삭제하겠다고 요청할 경우 DB에서 1번 데이터를 찾아 target에 저장합니다. 2번, 3번을 삭제하겠다고 요청한 경우도 마찬가지입니다.

 

2. target에 데이터가 있는지 없는지 확인하는 로그도 찍어 봅니다.

//(1) 삭제할 대상 가져오기
Article target = articleRepository.findById(id).orElse(null); //1.데이터 찾기
log.info(target.toString());

 

- 대상 엔티티 삭제하기

1. target에 무언가 저장됐다면 삭제를 수행합니다.

 (1) if() 조건문으로 target이 null이 아닌지 확인하고 참이면 내부 코드를 수행합니다.

 

 (2) 삭제는 articleRepository를 작성하고 점(.)을 찍으면 활용할 수 있는 메서드가 뜨는데 delete()를 선택하면 됩니다. 이때 전달값으로 삭제할 대상인 target을 넣습니다. 이렇게 하면 리파지터리가 DB에서 target을 삭제합니다.

//(2) 대상 엔티티 삭제하기
if(target != null){ //1.삭제할 대상이 있는지 확인
    articleRepository.delete(target); //2.delete() 메서드로 대상 삭제
}

 

2. 중간 점검해 보겠습니다. 서버를 재시작하고 localhost:8080/articles/1에 접속한 후 [Delete] 버튼을 클릭합니다. 아직 결과 페이지로 리다이렉트하지 않았기 때문에 에러 페이지가 뜹니다.

 

인텔리제이 실행창에서 로그를 확인해 보면 삭제 요청이 들어왔다는 메시지를 볼 수 있고, DB에서 해당 데이터를 찾은 것까지 확인할 수 있습니다. target이 null이 아니므로 이와 같은 로그가 찍힙니다. 삭제도 당연히 수행했을 거고요,

 

3. 실제로 그런지 확인해 보겠습니다. 웹 브라우저에서 새 탭을 열고 localhost:8080/h2-console에 접속합니다. 인텔리제이 실행창에서 검색한 JDBC URL을 새로 붙여 넣고 접속합니다.

 

4. ARTICLE 테이블을 선택하고 [Run]을 클릭합니다. 1번 데이터가 삭제되고 2번과 3번 데이터만 남은 것을 확인할 수 있습니다.

 

- 결과 페이지로 리다이렉트하기

1. 리다이렉트는 return 문에 작성합니다. 게시글을 삭제하면 목록 페이지로 돌아가야 합니다. 따라서 null을 지우고 "redirect:/articles"를 작성합니다.

//(3) 결과 페이지로 리다이렉트하기
return "redirect:/articles";

 

2. 서버를 재시작 하고 loaclhost:8080/articles 페이지에 접속합니다. 2번 데이터의 상세 페이지로 가서 [Delete] 버튼을 클릭합니다. 목록 페이지로 리다이렉트되면서 2번이 삭제된 것을 확인할 수 있습니다.

 

 

8.2.3 삭제 완료 메시지 남기기

1. RedirectAttributes 객체로 리다이렉트 페이지에서 사용할 데이터를 남길 수 있습니다. RedirectAttributes를 활용하려면 delete() 메서드의 매개변수로 받아 와야 합니다. 객체 이름은 rttr로 정하겠습니다.

public String delete(@PathVariable Long id, RedirectAttributes rttr) {
	(중략)
}

 

2. RedirectAttributes 객체의 addFlashAttribute() 메서드를 활용하면 리다이렉트 시점에 한 번만 사용할 데이터를 등록할 수 있습니다. 한 번 쓰고 사라지는 휘발성 데이터를 등록하는 것입니다. 형식은 다음과 같습니다.

형식 |  객체명.addFlashAttribute(넘겨_주려는_키_문자열, 넘겨_주려는_값_객체);

 

if() 문에서 삭제를 수행했는데요. 바로 다음에 msg라는 키 문자열에 "삭제됐습니다!"라는 값을 메시지로 넣습니다.

if(target != null){ //1.삭제할 대상이 있는지 확인
    articleRepository.delete(target); //2.delete() 메서드로 대상 삭제
    rttr.addFlashAttribute("msg","삭제됐습니다!");
}

 

3. msg 키 값에 담긴 메시지는 어디에서 사용할까요? 리다이렉트된 페이지가 어딘지는 return문을 보면 알 수 있습니다. 바로 /articles, 즉 목록 페이지에 삭제 메시지를 보여 줘야 합니다. 따라서 해당 뷰 파일인 index.mustache를 엽니다.

 

4. index.mustache 코드를 보니 헤더 안에 삭제 메시지를 출력하는 게 좋겠습니다. resources > templates > layouts 디렉터리에서 header.mustahce 파일을 엽니다.

 

 (1) 코드 맨 아래에 {{#msg}}{{/msg}}를 써서 msg 키를 사용할 범위를 잡아 줍니다.

 (2) 메시지 창(alert)으로 msg에 담긴 값을 출력합니다. 그리도 닫기(x) 버튼도 추가합니다.

{{#msg}} <!-- 1.msg 사용 범위 설정 -->
<div class="alert alert-primary alert-dismissible"> <!-- 2.메시지 창 작성 -->
    {{msg}}
    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{{/msg}}

 

5. 삭제 메시지가 잘 나오는지 확인해 보겠습니다. 서버를 재시작하고 localhost:8080/articles 페이지에 접속합니다. 1번 데이터의 상세 페이지로 가서 [Delete] 버튼을 클릭합니다. 페이지 상단에 "삭제됐습니다!"라는 메시지 창이 뜨는 것을 확인할 수 있습니다. [x] 버튼을 클릭하면 창이 사라집니다.

 

 

8.2.4 SQL 문으로 직접 DB 삭제하기

1. 서버를 재시작 했으니 JDBC URL을 다시 입력한 후 접속합니다. ARTICLE 테이블을 선택하고 [Run]을 클릭해 보면 2번, 3번 데이터만 남아 있습니다. 2번 데이터를 SQL문으로 삭제해 봅시다.

 

2. 데이터를 삭제할 때 사용하는 SQL 문은 DELETE 문입니다. 형식은 다음과 같은데, FROM은 생략할 수 있습니다.

더보기

형식 |  DELETE [FROM] 테이블명 WHERE 조건;

id가 2인 데이터를 삭제하므로 다음과 같이 작성하고 [Run]을 클릭합니다.

DELETE article WHERE id = 2;

 

3. DELETE 문을 실행한 후 결과를 확인하려면 다시 SELECT 문으로 조회해야 합니다. 다음과 같이 작성한 후 [Run]을 클릭합니다.

SELECT * FROM article;

결과를 보면 2번 데이터가 지워지고 3번 데이터만 남아있는 것을 확인할 수 있습니다.

 

8.2.5 최종 정리

8장에서는 게시글을 삭제하는 방법을 실습했습니다. 클라이언트가 특정 게시글의 삭제 요청을 하면 컨트롤러의 delete() 메서드에서 @GetMapping으로 받습니다. delete() 메서드가 삭제 대상을 찾으려면 대푯값인 id가 필요한데, 이때 @PathVariable 어노테이션을 사용합니다. @PathVariable은 @GetMapping의 URL에서 중괄호에 둘러싸인 값을 매개변수로 가져옵니다. 리파지터리는 이렇게 가져온 id로 DB에서 삭제 대상을 찾고 리파지터리가 제공하는 delete() 메서드로 데이터를 삭제합니다. 이때 DB 내부에서는 DELETE라는 SQL 문이 자동으로 수행됩니다.

 

삭제 작업이 끝나면 결과 페이지로 리다이렉트합니다. 삭제됐다는 메시지도 함께 출력하는데, 이를 위해 RedirectAttributes 객체의 addFlashAttribute() 메서드를 이용합니다. addFlashAttribute() 메서드는 리다이렉트되는 시점에 사용할 휘발성 데이터를 등록합니다.

'스프링부트' 카테고리의 다른 글

10장 REST API와 JSON  (3) 2025.02.04
9장 CRUD와 SQL 쿼리 종합  (2) 2025.01.23
7장 게시글 수정하기: Update  (0) 2025.01.22
6장 게시판 내 페이지 이동하기  (2) 2025.01.20
5장 게시글 읽기: Read  (1) 2025.01.20