Spring boot
(스프링 부트 입문) 버전 1 - 스프링 부트 익명 블로그 만들어 보기) 4.게시글 삭제하기
mynote6676
2025. 6. 22. 01:10
|BoardRepository 코드 추가
package com.tenco.blog.repository;
import com.tenco.blog.model.Board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Repository // IoC 대상
public class BoardNativeRepository {
// JPA의 핵심 인터페이스
// 데이터베이스와의 모든 작업을 담당
private EntityManager em;
// 생성자를 확인해서 자동으로 EntityManager 객체를 주입 시킨다.
// DI 처리
public BoardNativeRepository(EntityManager em) {
this.em = em;
}
// 특정 게시글을 삭제하는 메서드
@Transactional
public void deleteById(Long id) {
Query query = em.createNativeQuery("delete from board_tb where id = ? ");
query.setParameter(1, id);
// Insert, Update, Delete 실행 시킬 때
query.executeUpdate();
}
public Board findById(Long id) {
// WHERE 조건절을 활용해서 단건에 데이터를 조회
String sqlStr = "select * from board_tb where id = ? ";
// 4 or 1=1
Query query = em.createNativeQuery(sqlStr, Board.class);
// SQL Injection 방지 - 파라미터 바인딩
// 직접 문자열을 연결하지 않고 ? 를 사용해서 안전하게 값 전달
query.setParameter(1, id);
// query.getSingleResult() -> 단일 결과만 반환하는 메서드
// 주의 : null 리턴된다면 예외 발생 --> try-catch 처리를 해야 한다.
// 주의 : 혹시 결과가 2개 행의 리턴이 된다면 예외가 발생하게 된다.
return (Board) query.getSingleResult();
}
// 게시글 목록 조회
public List<Board> findAll() {
// 쿼리 기술 --> 네이티브 쿼리
// Board.class <-- 형변환을 직접적으로 명시하는 것이 좋다.
Query query = em.
createNativeQuery("select * from board_tb order by id desc ", Board.class);
// List<Board> <---
// Object[] <---
// query.getResultList() : 여러 행의 결과를 List 객체로 반환
// query.getSingleResult(); 단일 결과만 반환 (한 개의 row 데이터만 있을 때)
// List<Board> list = query.getResultList();
return query.getResultList();
}
// 트랜잭션 처리
@Transactional
public void save(String title, String content, String username) {
Query query = em.createNativeQuery("insert into board_tb(title, content, username, created_at) " +
" values(?, ?, ?, now())");
query.setParameter(1, title);
query.setParameter(2, content);
query.setParameter(3, username);
query.executeUpdate();
}
}
| 단위 테스트 코드 작성
package com.tenco.blog.repository;
import com.tenco.blog.model.Board;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import java.util.List;
// @Import - 테스트에 사용할 수 있도록 해당 클래스 로드 한다.
@Import(BoardNativeRepository.class)
@DataJpaTest // JPA 관련 테스트만 로드하는 테스트
public class BoardNativeRepositoryTest {
@Autowired // DI 처리(의존성 주입)
private BoardNativeRepository br;
// DI - 의존성 주입
// public BoardNativeRepositoryTest(BoardNativeRepository br) {
// this.br = br;
// }
@Test
public void deleteById_test() {
// given
Long id = 4L;
// when
br.deleteById(id);
// then
// 조회했을 때 3개 나와야 함
List<Board> boardList = br.findAll();
Assertions.assertThat(boardList.size()).isEqualTo(3);
}
@Test
public void findById_test() {
// given
Long id = 1L;
// when
Board board = br.findById(id);
// then
Assertions.assertThat(board.getTitle()).isEqualTo("제목1");
Assertions.assertThat(board.getUsername()).isEqualTo("ssar");
// 객체가 null 아닌지 확인
Assertions.assertThat(board).isNotNull();
}
@Test
public void findAll_test() {
// given - 테스트를 위한 준비 단계
// 게시글 목록 조회 정상 작동 하는지 확인 --> data.sql 파일에 데이터 이미 준비
// when - 실제 테스트를 하는 행위 (전체 게시글 목록 조회)
List<Board> boardList = br.findAll();
// then : 결과 검증 (예상하는대로 동작하는지 검증)
Assertions.assertThat(boardList.size()).isEqualTo(4);
Assertions.assertThat(boardList.get(3).getUsername()).isEqualTo("ssar");
}
}
| BoardController 코드 추가
package com.tenco.blog.controller;
import com.tenco.blog.model.Board;
import com.tenco.blog.repository.BoardNativeRepository;
import com.tenco.blog.utils.MyDateUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@Controller // IoC 대상 - 싱글톤 패턴으로 관리 됨
public class BoardController {
private BoardNativeRepository boardNativeRepository;
// DI: 의존성 주입 : 스프링이 자동으로 객체를 주입
public BoardController(BoardNativeRepository boardNativeRepository) {
this.boardNativeRepository = boardNativeRepository;
}
@PostMapping("/board/{id}/delete")
public String delete(@PathVariable(name = "id") Long id) {
// 클라이언트 --> 삭제요청 처리 ---> 응답 : 리다이렉트 -- 클라이언트 --> / --> 응답
boardNativeRepository.deleteById(id);
// PRG 패턴 (Post-Redirect-Get) 적용
// 삭제 후 메인 페이지로 리다이렉트 하여 중복 삭제 방지
// 새로 고짐을 해도 삭제가 다시 실행되지 않음
return "redirect:/";
}
/**
* 상세보기 화면 요청
* board/1
*/
@GetMapping("/board/{id}")
public String detail(@PathVariable(name = "id") Long id,
HttpServletRequest request) {
Board board = boardNativeRepository.findById(id);
request.setAttribute("board", board);
return "board/detail";
}
// username, title, content <--- DTO 받는 방법, 기본 데이터 타입 설정
// form 태그에서 넘어오는 데이터 받기
// form 태그에 name 속성에에 key 값 동일해야 함
// http://localhost:8080/board/save
// 스프링 부트 기본 파싱 전략 - key=value (form)
@PostMapping("/board/save")
public String save(@RequestParam("username") String username,
@RequestParam("title") String title,
@RequestParam("content") String content) {
System.out.println("title : " + title);
System.out.println("content : " + content);
System.out.println("username : " + username);
boardNativeRepository.save(title, content, username);
return "redirect:/";
}
@GetMapping({"/", "/index"})
// public String index(Model model) {
public String index(HttpServletRequest request) {
// DB 접근해서 select 결과값을 받아서 머스태치 파일에 데이터 바인딩 처리
List<Board> boardList = boardNativeRepository.findAll();
// 뷰에 데이터를 전달 -> Model 사용가능
request.setAttribute("boardList", boardList);
return "index";
}
@GetMapping("/board/save-form")
public String saveFrom() {
// /templates//board
// /templates/board/
return "board/save-form";
}
}
<!-- 수정삭제버튼 -->
<div class="d-flex justify-content-end">
<!-- 페이지 이동 요청 -->
<a href="/board/{{board.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/board/{{board.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
728x90