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