[스프링부트]익명 블로그 만들어보기 v1(간단한 CRUD)

송송승현's avatar
Nov 25, 2024
[스프링부트]익명 블로그 만들어보기 v1(간단한 CRUD)

설정

build.gradle

plugins { id 'java' id 'org.springframework.boot' version '3.3.5' id 'io.spring.dependency-management' version '1.1.6' } group = 'com.example' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { // jpa implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // mustache implementation 'org.springframework.boot:spring-boot-starter-mustache' implementation 'org.springframework.boot:spring-boot-starter-web' // lombok compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' // h2 runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { useJUnitPlatform() }

application.properties

# 0. UTF-8 설정 server.servlet.encoding.charset=UTF-8 server.servlet.encoding.force=true # 1. DB 설정 spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:test spring.datasource.username=sa # 2. hibernate 설정 spring.jpa.hibernate.ddl-auto=create spring.jpa.show-sql=true # 3. SQL 초기화와 JPA 초기화 설정 spring.sql.init.data-locations=classpath:db/data.sql spring.jpa.defer-datasource-initialization=true

기본 header(header.mustache)

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>blog</title> </head> <body> <nav> <ul> <li> <a href="/"></a> </li> <li> <a href="/save-form">글쓰기</a> </li> </ul> </nav> <hr>

Entity

package com.example.blog.board; import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import org.antlr.v4.runtime.misc.NotNull; import java.sql.Timestamp; @Table(name = "board_tb") // 언더스코어 @Entity @Getter @NoArgsConstructor // DB에서 조회헤서 가져온 RS를 디폴트 생성자를 호출해서 new하고 리플렉션 해서 값을 채워준다. public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @NotNull private String title; @NotNull private String content; private Timestamp createdAt; }

data.sql

insert into board_tb(title, content,created_at) values ('제목1', '내용1', now()); insert into board_tb(title, content,created_at) values ('제목2', '내용2', now()); insert into board_tb(title, content,created_at) values ('제목3', '내용3', now()); insert into board_tb(title, content,created_at) values ('제목4', '내용4', now()); insert into board_tb(title, content,created_at) values ('제목5', '내용5', now());

게시글 조회

view

{{> layout/header}} --> mustache 부분 템플릿 문법 <section> <table border="1"> <tr> <th>번호</th> <th>제목</th> <th></th> </tr> {{#models}} --> mustache 반복 문법 <tr> <td>{{id}}</td> --> mustache 변수 문법 <td>{{title}}</td> <td><a href="/board/{{id}}">상세보기</a></td> </tr> {{/models}} --> mustache 반복 종료 </table> </section> </body> </html>

controller

// Controller의 책임 : 외부 클라이언트의 요청을 받고 그 요청에 대한 응답을 함 @Controller @RequiredArgsConstructor public class BoardController { private final BoardService boardService; @GetMapping("/") public String list(Model model) { // DS(request 객체를 model이라는 객체로 랩핑해서 전달.) List<BoardResponse.DTO> boardList = boardService.게시글목록보기(); model.addAttribute("models", boardList); return "list"; } } // Service의 책임 : 비지니스 로직 처리(트렌젝션 관리), DTO @Service @RequiredArgsConstructor public class BoardService {

service

// Service의 책임 : 비지니스 로직 처리(트렌젝션 관리), DTO @Service @RequiredArgsConstructor public class BoardService { private final BoartRepository boardRepository; public List<BoardResponse.DTO> 게시글목록보기(){ List<Board> boardList = boardRepository.findAll(); List<BoardResponse.DTO> boardDtoList = new ArrayList<>(); for(Board board : boardList){ BoardResponse.DTO boardDto = new BoardResponse.DTO(board); boardDtoList.add(boardDto); } return boardDtoList; }; }

respository

// Repository 책임 : DB 상호작용 @Repository @RequiredArgsConstructor public class BoartRepository { //JPA는 EntityManager로 DB에 접근한다.(JAVA에서 DBConnection) private final EntityManager em; public List<Board> findAll() { Query q = em.createNativeQuery("select * from board_tb order by id desc", Board.class); List<Board> list = q.getResultList(); return list; } }
 

게시글 상세 조회

view

{{> layout/header}} <section> <article> <form action="/board/{{model.id}}/delete" method="post"> <button type="submit">삭제</button> </form> <button> <a href="/board/{{model.id}}/update">수정하러가기</a> </button> <div> 번호 : {{model.id}}<br> 제목 : {{model.title}} <br> 내용 : {{model.content}} <br> 작성일 :{{model.createdAt}} </div> </article> </section> </body> </html>

DTO

@Data public static class DetailDTO{ private int id; private String title; private String content; private String createdAt; public DetailDTO(Board board) { this.id = board.getId(); this.title = board.getTitle(); this.content = board.getContent(); this.createdAt = board.getCreatedAt().toString();// TODO: 2024.11.18 형태로 변경 } }

Controller

// @PathVariable : url 경로에서 변수 값을 추출 -> 매개변수에 사용 @GetMapping("/board/{id}") public String detail(@PathVariable("id") int id, Model model) { model.addAttribute("model",boardService.게시글상세보기(id)); return "detail"; }

service

public BoardResponse.DetailDTO 게시글상세보기(int id) { Board board = boardRepository.findById(id); return new BoardResponse.DetailDTO(board); }

repository

// 네이티브쿼리 사용 public Board findById(int id) { Query q = em.createNativeQuery("select * from board_tb where id = ?", Board.class); q.setParameter(1, id);// 물음표 완성하기(물음표 순서, 물음펴에 바인딩될 변수) // 다운캐스팅의 조건 : 메모리에 올려져 있어야함 return (Board)q.getSingleResult(); }

게시글 작성

view

{{> layout/header}} <section> <article> <!-- title = 제목6&content=내용6 &ndash;&gt; application/x-www-form-urlencoded key값은 input 태그의 name 값 value값은 input태그에 사용자가 입력하는 값--> <form method="POST" action="/board/save" enctype="application/x-www-form-urlencoded"> <input type="text" name = "title" placeholder = "제목"/><br> <input type="text" name = "content" placeholder = "내용"/><br> <button type="submit">글쓰기</button> </form> </article> </section> </body> </html>

DTO

@Data public static class SaveDTO{ private String title; private String content; }

controller

// 게시글 작성 view 호출 @GetMapping("/save-form") public String saveForm() { return "save-form"; } // 게시글 작성 api @PostMapping("/board/save") public String save(BoardRequest.SaveDTO saveDTO) { // x-www는 클래스로 받을 수 있다. json으로 받고 싶으면 @RequestBody를 사용 boardService.게시글쓰기(saveDTO); return "redirect:/"; // 302코드를 줘야함 redirect }

service

@Transactional public void 게시글쓰기(BoardRequest.SaveDTO saveDTO) { boardRepository.save(saveDTO.getTitle(),saveDTO.getContent()); } // commit

repository

public void save(String title, String content) { Query q = em.createNativeQuery("insert into board_tb(title, content, created_at) values(?, ?, now())"); q.setParameter(1, title); q.setParameter(2, content); q.executeUpdate(); }

게시글 수정

view

{{> layout/header}} <section> <article> <form method="POST" action="/board/{{model.id}}/update"> <input type="text" name = "title" placeholder = "제목" value="{{model.title}}"/><br> <input type="text" name = "content" placeholder = "내용" value="{{model.content}}"/><br> <button type="submit">수정하기</button> </form> </article> </section> </body> </html>

DTO

// 수정 요청 DTO @Data //getter,setter, toString public static class updateDTO{ private String title; private String content; } // 수정 화면 DTO @Data public static class updateFormDTO{ private int id; private String title; private String content; private String createdAt; public updateFormDTO(Board board) { this.id = board.getId(); this.title = board.getTitle(); this.content = board.getContent(); this.createdAt = board.getCreatedAt().toString();// TODO: 2024.11.18 형태로 변경 } }

controller

// 수정 화면 호출 @GetMapping("/board/{id}/update") public String updateForm(@PathVariable int id, Model model) { model.addAttribute("model",boardService.게시글수정화면보기(id)); return "update-form"; } // form 데이터 통신이라 put X @PostMapping("/board/{id}/update") public String update(@PathVariable("id") int id, BoardRequest.updateDTO updateDTO) { boardService.게시글수정(id,updateDTO); return "redirect:/"; }

service

public BoardResponse.updateFormDTO 게시글수정화면보기(int id) { Board board = boardRepository.findById(id); return new BoardResponse.updateFormDTO(board); } public void 게시글수정(int id, BoardRequest.updateDTO updateDTO) { boardRepository.update(id, updateDTO.getTitle(),updateDTO.getContent()); } }

repository

public Board findById(int id) { Query q = em.createNativeQuery("select * from board_tb where id = ?", Board.class); q.setParameter(1, id);// 물음표 완성하기(물음표 순서, 물음펴에 바인딩될 변수) return (Board)q.getSingleResult(); // return em.find(Board.class, id); } public void update(int id, String title, String content) { Query q = em.createNativeQuery("update board_tb set title = ?, content = ? where id = ?"); q.setParameter(1, title); q.setParameter(2, content); q.setParameter(3, id); q.executeUpdate(); }

게시글 삭제

controller

@PostMapping("/board/{id}/delete") public String delete(@PathVariable("id") int id) { boardService.게시글삭제(id); return "redirect:/"; }

service

@Transactional public void 게시글삭제(int id) { boardRepository.delete(id); }

repository

public void delete(int id){ Query q = em.createNativeQuery("delete from board_tb where id = ?"); q.setParameter(1, id); q.executeUpdate(); // insert, update, delete때 사용 }
 
Share article

송승현의 블로그