설정
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 –> 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