Spring boot

블로그2 - 익명 게시판 만들기

ryeonng 2024. 10. 8. 12:39

 

엔티티 클래스 만들기 (Board.java)
package com.tenco.blog_v1.board;

import jakarta.persistence.*;
import lombok.Data;

import java.sql.Timestamp;

@Entity
@Table(name = "board_tb")
@Data
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 기본 키 전략 db 위임
    private Integer id;
    private String title;
    private String content;

    // created_at 컬럼과 매핑하며, 이 필드는 데이터 저장 시 자동으로 설정 됨
    @Column(name = "created_at", insertable = false, updatable = false)
    private Timestamp createdAt;
}

 

레포지토리 클래스 작성 (BoardNativeRepository.java) - Native 쿼리 연습
  • @Repository 애노테이션으로 스프링에게 이 클래스가 레포지토리임을 알린다.
  • EntityManager를 주입받아 Native Query를 사용하여 CRUD 메서드를 구현한다.
  • @Transactional 애노테이션을 사용하여 데이터 변경 메서드에 트랜잭션을 관리한다.
package com.tenco.blog_v1;

import com.tenco.blog_v1.board.Board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Repository // IOC
@RequiredArgsConstructor
public class BoardNativeRepository  {
    // DI 처리
    private final EntityManager em;
    @Transactional
    public void save(String title , String content){
        Query query = em.createNativeQuery(

                "INSERT INTO board_tb(title, content , created_at) VALUES (?,?,NOW())"
        );
        query.setParameter(1,title);
        query.setParameter(2,content);

        // 실행
        query.executeUpdate();
    }

    /**
     * 특정 Id 의 게시글을 조회합니다.
     * @param id
     * @return
     */
    public Board findById(int id){
        Query query = em.createNativeQuery("SELECT * FROM board_tb WHERE id = ? " , Board.class);
        query.setParameter(1, id);
        return (Board)query.getSingleResult();
    }

    /**
     * 모든 게시글 조회
     *
     * @return
     */

    public List findAll() {
        Query query = em.createNativeQuery("SELECT * FROM board_tb ORDER By id DESC ", Board.class);
        return query.getResultList();
    }

    /**
     * 특정 Id 로 게시글을 수정하는 기능
     * @param id
     * @param title
     * @param content
     */
    @Transactional
    public void  updateById(int id, String title , String content){
        Query query = em.createNativeQuery(" UPDATE board_tb SET title = ?,content = ? WHERE id = ? ");
        query.setParameter(1,title);
        query.setParameter(2,content);
        query.setParameter(3,id);

        query.executeUpdate();
    }

    /**
     * 특정 ID의 게시글을 삭제 합니다.
     * @param id
     */
    @Transactional
    public void deleteById(int id) {
        Query query
                = em.createNativeQuery("DELETE FROM board_tb WHERE id = ?");
        query.setParameter(1, id);
        query.executeUpdate();
    }
}

 

컨트롤러 클래스 작성 (BoardController.java)
  • @Controller 애노테이션을 스프링에게 이 클래스가 컨트롤러임을 알린다.
  • @RequiredArgsConstructor를 사용하여 레포지토리를 주입받는다.
  • 각 HTTP 요청에 대해 적절한 핸들러 메서드를 작성한다.
    • GET 요청 : 데이터 조회 및 뷰 반환
    • POST 요청 : 데이터 변경 및 리다이렉트

 

index.mustache
{{> layout/header}}

<div class="container p-5">
    <!-- 게시글 목록을 반복 출력 (boardList가 null이 아니고 비어 있지 않다면 출력) -->
    {{#boardList}}
        <div class="card mb-3">
            <div class="card-body">
                <h4 class="card-title mb-3">{{title}}</h4>
                <a href="/board/{{id}}" class="btn btn-primary">상세보기</a>
            </div>
        </div>
    {{/boardList}} <!-- 반드시 섹션을 닫는 태그가 필요 -->
    
    <!-- 게시글이 없을 경우 출력할 내용 -->
    {{^boardList}}
        <p>게시글이 없습니다.</p>
    {{/boardList}}

    <ul class="pagination d-flex justify-content-center">
        <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
        <li class="page-item"><a class="page-link" href="#">Next</a></li>
    </ul>
</div>

{{> layout/footer}}

 

static/styles.css
body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

.content {
    flex: 1;
}

footer {
    position: relative;
    bottom: 0;
    width: 100%;
    background-color: #f8f9fa;
    text-align: center;
    padding: 20px;
}

 

save-form.mustache
{{> layout/header}} {{!    Partial 태그 (부분 템플릿 태그) }}

  <main class="container p-5 content">
    <article>
      <div class="card">
        <div class="card-header"><b>글쓰기 화면입니다</b></div>
        <div class="card-body">
          <form action="/board/save" method="post">
            <div class="mb-3">
              <input type="text" class="form-control" placeholder="Enter title" name="title">
            </div>
            <div class="mb-3">
              <textarea class="form-control" rows="5" name="content"></textarea>
            </div>
            <button class="btn btn-primary form-control">글쓰기완료</button>
          </form>
        </div>
      </div>
    </article>
  </main>

{{> layout/footer}}

 

BoardController
package com.tenco.blog_v1.board;

import com.tenco.blog_v1.BoardNativeRepository;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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;

@Slf4j
@RequiredArgsConstructor
@Controller
public class BoardController {

    // DI
    // @Autowired
    private final BoardNativeRepository boardNativeRepository;

    @GetMapping("/")
    public String index(Model model) {

        List<Board> boardList = boardNativeRepository.findAll();
        model.addAttribute("boardList", boardList);
        log.warn("여기까지 오니");
        return "index";
    }

    // 주소설계 http://localhost:8080/board/save-form
    // 게시글 작성 화면
    @GetMapping("/board/save-form")
    public String saveForm() {
        return "board/save-form";
    }

    // 게시글 저장
    // 주소설계 http://localhost:8080/board/save-form
    @PostMapping("/board/save")
    public String save(@RequestParam(name = "title") String title, @RequestParam(name = "content") String content) {
        // 파라미터가 올바르게 전달 되었는지 확인
        log.warn("save 실행: 제목={}, 내용{}", title, content);
        boardNativeRepository.save(title, content);
        return "redirect:/";
    }


    // 특정 게시글 요청 화면
    // 주소설계 http://localhost:8080/board/10
    @GetMapping("/board/{id}")
    public String detail(@PathVariable(name = "id") Integer id, HttpServletRequest request) {
        Board board = boardNativeRepository.findById(id);
        request.setAttribute("board", board);
        return "board/detail";
    }

    // 주소설계 http://localhost:8080/board/10/delete ( form 태그 활용이기 때문에 delete 선언 )
    // form 태그에서는 GET, POST 방식만 지원하기 때문.
    @PostMapping("/board/{id}/delete")
    public String delete(@PathVariable(name = "id") Integer id) {
        boardNativeRepository.deleteById(id);
        return "redirect:/";
    }

    // 게시글 수정 화면 요청
    // board/{id}/update
    @GetMapping("/board/{id}/update-form")
    public String updateForm(@PathVariable(name = "id") Integer id, HttpServletRequest request) {
        Board board = boardNativeRepository.findById(id);
        request.setAttribute("board", board);
        return "board/update-form"; // 폴더 찾아가는 경로 src/main/resources/templates/board/update-form.mustache
    }


    // 게시글 수정 기능 요청
    // board/{id}/update
    @PostMapping("/board/{id}/update")
    public String update(@PathVariable(name = "id") Integer id, @RequestParam(name = "title") String title, @RequestParam(name = "content") String content) {
        boardNativeRepository.updateById(id, title, content);
        return "redirect:/board/" + id;
    }
}

 

Native Query와 JPQL 이란 ?

 

Native Query

Native Query는 데이터베이스의 고유한 SQL 문법을 사용하여 쿼리를 작성한다. 복잡한 쿼리나 JPQL로 표현하기 어려운 특정 기능을 사용할 때 유용하다.

 

JPQL (Java Persistence Query Language)

JPQL은 객체 지향 쿼리 언어로, 엔티티 객체를 대상으로 쿼리를 작성한다. JPQL은 데이터베이스 독립적이며, JPA의 장점을 최대한 활용할 수 있게 해준다.

 

템플릿 작성 (Mustache)

  • src/main/resources/templates/ 디렉토리에 Mustache 템플릿 파일을 생성한다.
  • 각 컨트롤러 메서드가 반환하는 뷰 이름에 맞춰 템플릿 파일을 작성한다.
    • index.mustache
    • board/save-form.mustache
    • board/detail.html
    • board/update-form.html
index.mustache

 

Mustache 태그

  1. {{> layout/header}} Partial(부분 템플릿) 태그이다. header.mustache 파일을 포함해 헤더를 재사용할 수 있다. 공통 레이아웃을 유지하는 데 유용하다.
  2. {{#boardList}} : Section(섹션) 태그이다. 조건부 반복 블록으로 boardList 데이터가 존재할 때만 내부 블록을 반복 처리한다. 주로 리스트 데이터 렌더링에 사용된다.
  3. {{title}} / {{id}}Variable(변수) 태그이다. Mustache에서 데이터 바인딩을 처리하는 구문으로, 해당 변수 값이 템플릿에서 렌더링된다. 이 경우 각 게시글의 title과 id값이 출력된다.

 

'Spring boot' 카테고리의 다른 글

블로그4 - 연관 관계 매핑: User와 Board 엔티티  (0) 2024.10.14
블로그3 - 사용자 관리 기본 코드 추가 및 설정  (0) 2024.10.08
블로그 - 프로젝트 생성  (0) 2024.10.07
Mustache  (0) 2024.10.04
템플릿 엔진  (0) 2024.10.04