📌 지난 내용 요약
1편에서는 CRUD 기능을 중심으로 게시글을 작성하고, 수정하고, 삭제하는 기본 게시판을 만들어봤다.
이번 편에서는 아래 기능들을 추가로 구현하면서 실제 서비스처럼 다듬어 보기 위한 작업을 진행했다.
✅ 검색 기능
✅ 페이징 처리
✅ 파일 업로드 기능
⚠️ 그리고 구현하면서 혼동 되었던 URL 매핑 방식도 정리해보았다.
✔️ 목적
사용자가 입력한 키워드(예: 제목)에 해당하는 게시글만 보여주도록 만들기
📁 BoardController.java
@GetMapping("/board/list")
public String boardList(Model model, @RequestParam(required = false) String searchKeyword) {
    List<Board> list;
    if (searchKeyword == null) {
        // 검색어 없으면 전체 목록
        list = boardService.boardList();
    } else {
        // 검색어 있으면 해당 키워드 포함 게시글만
        list = boardService.boardSearchList(searchKeyword);
    }
    model.addAttribute("list", list);
    return "boardlist";
}
@RequestParam으로 쿼리 파라미터 받음null체크로 검색어 여부에 따라 로직 분기
searchKeyword가 null인 경우 -> 전체 게시글searchKeyword가 null이 아닌 경우 -> 해당 키워드가 포함된 게시글
📁 BoardService.java
public List<Board> boardSearchList(String searchKeyword) {
    return boardRepository.findByTitleContaining(searchKeyword);
}
📁 BoardRepository.java
List<Board> findByTitleContaining(String title);
Containing은 SQL에서LIKE '%keyword%'와 같은 의미로, 제목 안에 검색어가 포함된 글을 자동으로 찾아준다.
✔️ 목적
게시글이 많아졌을 때, 한 번에 모두 출력하지 않고 페이지 단위로 나누기
📁 BoardController.java
@GetMapping("/board/list")
public String boardList(Model model,
    @PageableDefault(page = 0, size = 10, sort = "id", direction = Sort.Direction.DESC)
    Pageable pageable) {
    Page<Board> list = boardService.boardList(pageable);
    // 현재 페이지, 시작/끝 페이지 계산 (페이지 버튼 만들 때 사용)
    int nowPage = list.getPageable().getPageNumber() + 1;
    int startPage = Math.max(nowPage - 4, 1);
    int endPage = Math.min(nowPage + 5, list.getTotalPages());
    model.addAttribute("list", list);
    model.addAttribute("nowPage", nowPage);
    model.addAttribute("startPage", startPage);
    model.addAttribute("endPage", endPage);
    return "boardlist";
}
📁 BoardService.java
public Page<Board> boardList(Pageable pageable) {
    return boardRepository.findAll(pageable);
}
📁 BoardService.java
Page<Board> findAll(Pageable pageable);
Pageable은 Spring에서 페이징을 쉽게 도와주는 인터페이스
📁 BoardService.java
public void write(Board board, MultipartFile file) throws Exception {
    // 저장 경로: 프로젝트 내부 static/files 폴더
    String projectPath = System.getProperty("user.dir") + "/src/main/resources/static/files";
    // UUID로 파일명 중복 방지
    UUID uuid = UUID.randomUUID();
    String fileName = uuid + "_" + file.getOriginalFilename();
    // 실제 경로에 파일 저장
    File saveFile = new File(projectPath, fileName);
    file.transferTo(saveFile);
    // DB에 저장할 파일 정보 설정
    board.setFilename(fileName);
    board.setFilepath("/files/" + fileName);
    boardRepository.save(board);
}
- 실제 파일은
 static/files에 저장되고, 그 경로를 DB에 저장해서 이미지로 보여준다.
게시글 상세보기 기능을 만들던 중, @RequestParam 으로 id를 받아오는 과정에서 오류가 났다.
오류 메세지는 아래와 같았다.
Required request parameter 'id' for method parameter type Long is not presen
다음과 같이 컨트롤러를 작성했다.
📁 java
@GetMapping("/board/view") public String boardView(@RequestParam("id") Integer id, Model model) { model.addAttribute("board", boardService.boardView(id)); return "boardview"; }📁 html
<a th:href="@{/board/view(id=${board.id})}">상세보기</a>->
/board/view?id=1형식으로 호출되게 했다.
똑같이 썼음에도 계속 오류가 나서 @PathVariable 로 바꿨더니 오류가 해결 되었다.
📁 java
@GetMapping("/board/view") public String boardView(@PathVariable("id") Integer id, Model model) { model.addAttribute("board", boardService.boardView(id)); return "boardview"; }📁 html
<a th:href="@{'/board/view/' + ${board.id}}">상세보기</a>->
/board/view/1형식으로 바꾸니깐 해결 되었다.
📌 정리
| 구조 | URL 예시 | Annotation | 
|---|---|---|
| 쿼리스트링 방식 | /board/view?id=1 | @RequestParam("id") | 
| 경로 변수 방식 | /board/view/1 | @PathVariable("id") | 
결국 상세 페이지를 구현하는 과정에서, 처음엔 @RequestParm을 사용해서 값을 받아오려 했지만 오류가 났었고, 구조가 잘못된 줄 알고 @PathVariable로 바꿔서 해결했었다.
그런데 나중에 보니 @RequestParam도 맞는 방식이었고, 내가 놓친 건 특정 상황에서 파라미터가 실제로 안 넘어왔던 부분이었다.
두 방식 모두 사용 가능하지만, 그 과정에서 다른 방법을 시도해본 것도 도움이 되었던 것 같다.
단지 강의를 보며 따라치는 프로젝트였지만, 생각보다 오류도 많이 나고 쉽지만은 않았지만, 그만큼 잘 작동했을 때는 뿌듯함이 컸다. 끗!
애노테이션 마다 요구하는게 달라서 저도 애를 많이 먹었던 것 같아요 !! 정말 헷갈려요 … 그래도 잘 해결하셔서 다행입니다 ! 덕분에 저도 RequestParam과 PathVariable 둘 다 사용할 수 있다는 것을 알 수 있었던 것 같습니다! 고생 많으셨습니다