[Spring Boot] 게시판 프로젝트 - 4

Hyeonseok Jeong·2023년 11월 29일
0

사용한 기술 스택

  • Spring Boot 3
  • Spring Security
  • Spring JPA
  • Java
  • MySQL
  • Thymeleaf
  • LomBok
  • HTML
  • CSS
  • Javascript

구현한 기능

  • 스프링 시큐리티를 이용한 로그인 기능 (작성 완료)
  • 스프링 시큐리티를 이용한 회원가입 기능 (작성 완료)
  • 게시판 리스트 (작성 완료)
  • 게시판 디테일 (다음글, 이전글 링크 구현) (작성 완료)
  • 게시글 생성 & 다중 파일 업로드 기능 구현 (작성 완료)
  • 파일 다운로드 기능 (작성 완료)
  • 게시글 수정 (작성 완료)
  • 검색 기능 (작성 완료)
  • 페이지 네이션 기능 (작성 완료)
  • 댓글 생성 (작성 완료)
  • 댓글 삭제 (작성 완료)

오늘의 구현

  • 게시글 수정
  • 검색 기능

게시글 수정과 검색 기능..!
사실 앞으로 남은 부분들은 댓글 생성과 댓글 삭제그리고 오늘할 게시글 수정부분이 끝일것이다.
왜냐 이전 게시판 리스트 부분에서 검색기능, 페이지전환 기능, 검색기능을 이미 한차례 설명했기 때문!
(여기까지 쓰고 잠시 두절이 되어버린...)

게시글 수정

빠르게 쓰고 다음 프로젝트를 적어보잣

먼저 코드이다

  • controller
   @PreAuthorize("isAuthenticated()")
    @GetMapping("/modify/{id}")
    public String modify (@PathVariable("id")Integer id, BoardForm boardForm, Principal principal) {
        Board board = this.boardService.detail(id);
        if(!board.getSiteUser().getUsername().equals(principal.getName())) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
        }
        boardForm.setSubject(board.getSubject());
        boardForm.setContent(board.getContent());
        return "board_form";
    }

    @PreAuthorize("isAuthenticated()")
    @PostMapping("/modify/{id}")
    public String modifyP (@PathVariable("id") Integer id, BoardForm boardForm, Principal principal) {
        Board board = this.boardService.detail(id);
        if (!board.getSiteUser().getUsername().equals(principal.getName())) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
        }
        this.boardService.modify(board, boardForm.getSubject(), boardForm.getContent());
        return "redirect:/board/list";
    }
  • Thymeleaf
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" th:href="@{/board_form.css}" />
    <title>Document</title>
  </head>
  <body>
    <!-- layout tag : <main> tag  -->
    <main class="contain">
      <div th:replace="~{nav :: navigation}"></div>

      <!-- board_form page start -->
      <form class="board_form_contain" th:object="${boardForm}" enctype="multipart/form-data" method="post">
<!--   csrf 공격 방어 설정시 action 속성이 없을경우 자동으로 생성되지 않으므로 수동으로 적어주어야 한다.     -->
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
        <table class="detail-post-content-box">
          <tr class="detail-post-tr">
            <th class="detail-post-th">제목</th>
            <td class="detail-post-td">
              <input
                type="text"
                class="subject_input_style"
                th:field="*{subject}"
                placeholder="제목을 작성해주세요"
              />
            </td>
          </tr>

          <tr class="detail-post-tr">
            <th class="detail-post-th">첨부파일</th>
            <td class="detail-post-td">
              <input type="file" name="files" id="files" multiple="multiple" />
            </td>
          </tr>
        </table>

        <div class="detail-post-content">
          <textarea
            class="content_input_style"
            th:field="*{content}"
            placeholder="내용을 작성해주세요"
          ></textarea>
        </div>
        <div class="board_form_confirm_box">
          <input class="confirm_btn btn" type="submit" value="확인" />
          <a class="cancle_btn btn" th:href="@{/}">취소</a>
        </div>
      </form>
      <!-- board_form page end -->
    </main>
    <script th:src="@{/board_form.js}"></script>
  </body>
</html>

수정부분은 게실글 생성에서 추가로 수정을 한것이다.
기존에 존재하는 게시글을 수정버튼을 통해서 게시글 생성 폼으로 이동되는데 게시글ID가 존재하게 되면 해당 제목, 내용 등과같은 데이터가 폼에 입력이 된다 (th:field를 통해서 기존에 존재하는 게시글 데이터가 입력이 되는것이다.)

검색 기능

  • 코드
  • Controller
@GetMapping("/list")
    public String list (Model model,@RequestParam(value= "query", defaultValue = "subject") String query,@RequestParam(value = "kw", defaultValue = "") String kw , @RequestParam(value = "page", defaultValue = "0") int page) {
        Page<Board> boardList = this.boardService.boardList(query, kw, page);
        model.addAttribute("list", boardList);
        model.addAttribute("kw", kw);
        model.addAttribute("query", query);
        return "list";
    }

검색 부분은 path를 통해서 사용자가 원하는 필터와 검색내용을 가지고와 리스트를 작성해 주는것으로 그렇게 어려울건 없었다 오히려 처음 JPA를 통해 페이지네이션 하는 부분이 Page 클래스를 통해 작업하는게 더 어려웠던것 같다.

  • Repository
package com.mysite.board.siteBoard;

import com.mysite.board.siteUser.SiteUser;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.repository.query.Param;

import javax.swing.text.html.Option;
import java.util.List;
import java.util.Optional;



public interface BoardRepository extends JpaRepository<Board, Integer> {
    @Query(value = "SELECT * FROM BOARD " +
            "WHERE REG_DATE > (SELECT REG_DATE FROM BOARD WHERE ID = :id) " +
            "LIMIT 1", nativeQuery = true)
    Optional<Board> findByNextBoard(Integer id);

    @Query(value = "select * from board " +
            "where reg_date < (select reg_date from board where id = :id) order by reg_date desc " +
            "LIMIT 1", nativeQuery = true)
    Optional<Board> findByPrevBoard(Integer id);

    Page<Board> findAll(Pageable pageable);

    Page<Board> findAll(Specification<Board> spec, Pageable pageable);

    Page<Board> findBySubjectContaining(String keyword, Pageable pageable);
    Page<Board> findByContentContaining(String keyword, Pageable pageable);

    Page<Board> findBySubjectOrContentContaining(String keyword1, String keyword2, Pageable pageable);

}
  • Service

    private final BoardRepository boardRepository;
    private final FileService fileService;


    private Specification<Board> search(String kw) {
        return new Specification<>() {
            private static final long serialVersionUID = 1L;
            @Override
            public Predicate toPredicate(Root<Board> b, CriteriaQuery<?> query, CriteriaBuilder cb) {
                query.distinct(true);  // 중복을 제거
                Join<Board, SiteUser> u1 = b.join("siteUser", JoinType.LEFT);
                return cb.or(cb.like(u1.get("username"), "%" + kw + "%"));
            }
        };
    }


    public Page<Board> boardList (String query, String kw, int page) {
        List<Sort.Order> sorts = new ArrayList<>();
        sorts.add(Sort.Order.desc("regDate"));
        Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));
        if(query.equals("subject")) {
            return this.boardRepository.findBySubjectContaining(kw, pageable);
        } else if (query.equals("content")) {
            return this.boardRepository.findByContentContaining(kw, pageable);
        } else if (query.equals("user")) {
            Specification<Board> spec = search(kw);
            return this.boardRepository.findAll(spec, pageable);
        } else if (query.equals("subject+content")) {
            return this.boardRepository.findBySubjectOrContentContaining(kw, kw, pageable);
        }
        else {
            return this.boardRepository.findAll(pageable);
        }
    }
java
private Specification<Board> search(String kw) {
        return new Specification<>() {
            private static final long serialVersionUID = 1L;
            @Override
            public Predicate toPredicate(Root<Board> b, CriteriaQuery<?> query, CriteriaBuilder cb) {
                query.distinct(true);  // 중복을 제거
                Join<Board, SiteUser> u1 = b.join("siteUser", JoinType.LEFT);
                return cb.or(cb.like(u1.get("username"), "%" + kw + "%"));
            }
        };
    }

부분이 검색기능을 만들기 위한 쿼리문이며 이상하게 어노테이션을 통해서 작성하게 되면 오류가 나서 이와같이 Specification을 통해서 쿼르문을 만들어 검색 기능을 구현 하였다.

사진

  • 게시글 수정



  • 검색 기능

마무리

사실 시간이 좀 지나서 이렇게 급하게 블로그 글을 마무리 하면 안됬지만
팀프로젝트를 하며 빠르게 스프링에 대해서 익숙해지고싶은 마음에 학사관리 시스템에 집중하는라 그랬다는 변명 아닌 변명을 적어보며 블로그 글을 마치고

다음에 할 개인 프로젝트인 즐거운설문 (가제) 프로젝트를 진행한 후에는 좀더 나중에 알아보기 쉽게 작성하고 이렇게 어중간한 작성은 피해서 작업해야할것 같다.

profile
풀스텍 개발자

0개의 댓글