Contents
10. 게시글 목록(메인화면)1) BoardController.java2) BoardService.java3) index.mustache 수정11. 게시글 상세1) BoardController.java2) BoardService.java3) detail.mustache 수정12. 게시글 수정1) BoardController.java2) BoardService.java3) update-form.mustache 수정13. 게시글 등록1) BoardSaveDTO.java2) BoardController.java3) BoardService.java14. 게시글 삭제1) BoardController.java2) BoardService.java3) detail.mustache 수정15. warning 없애기1) application.properties 추가GitMVC (Model + View + Controller)
외부에서 컨트롤러에 요청 → 컨트롤러(C)는 DB에서 모델(M) 받아서 뷰(V)로 뿌림
10. 게시글 목록(메인화면)
1) BoardController.java
package com.example.boardv1.board;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor // final이 적혀있는 애로 생성자 만들어줌
@Controller // @Controller 적어야 리턴값이 파일이 됨 (외부진입점)
public class BoardController {
private final BoardService boardService;
@GetMapping("/")
public String index(HttpServletRequest req){
List<Board> list = boardService.게시글목록();
req.setAttribute("models", list);
return "index";
}
}alt+enter로 메서드 만들기 하면 됨2) BoardService.java
package com.example.boardv1.board;
import java.util.List;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service // 컴포넌트 스캔되서 IoC에 뜸 + 목적 : 트랜잭션 관리
public class BoardService {
private final BoardRepository boardRepository;
public List<Board> 게시글목록() {
return boardRepository.findAll();
}
}3) index.mustache 수정
{{> header}}
<div class="container mt-3">
<table class="table table-hover">
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>내용</th>
</tr>
</thead>
<tbody>
{{#models}}
<tr>
<td>{{id}}</td>
<td>
<a href="/boards/{{id}}" style="color: inherit; text-decoration: none;">
{{title}}
</a>
</td>
<td>{{content}}</td>
</tr>
{{/models}}
</tbody>
</table>
</div>
</body>
</html>11. 게시글 상세
1) BoardController.java
package com.example.boardv1.board;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor // final이 적혀있는 애로 생성자 만들어줌
@Controller // @Controller 적어야 리턴값이 파일이 됨 (외부진입점)
public class BoardController {
private final BoardService boardService;
@GetMapping("/boards/{id}")
public String detail(@PathVariable("id") int id, HttpServletRequest req){
Board board = boardService.상세보기(id);
req.setAttribute("model", board);
return "board/detail"; // mustache 파일의 경로
}
}2) BoardService.java
package com.example.boardv1.board;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service // 컴포넌트 스캔되서 IoC에 뜸 + 목적 : 트랜잭션 관리
public class BoardService {
private final BoardRepository boardRepository;
public Board 상세보기(int id) {
return boardRepository.findById(id);
}
}3) detail.mustache 수정
{{> header}}
<div class="container p-5">
<!-- 수정삭제버튼 -->
<div class="d-flex justify-content-end">
<a href = "/boards/{{model.id}}/update-form" class="btn btn-secondary me-1">수정</a>
<button class="btn btn-outline-secondary">삭제</button>
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{model.title}}</b></h2>
<hr />
<div class="d-flex justify-content-end">
작성자 : 익명
</div>
<div class="m-4 p-2">
{{model.content}}
</div>
</div>
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form action="/replies/save" method="post">
<textarea class="form-control" rows="2" name="comment"></textarea>
<div class="d-flex justify-content-end">
<button class="btn btn-secondary mt-1">
댓글등록
</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
<!-- 댓글아이템 -->
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-secondary text-white rounded">익명</div>
<div>댓글 내용2</div>
</div>
<form action="/replies/1/delete" method="post">
<button class="btn">🗑</button>
</form>
</div>
<!-- 댓글아이템 -->
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-secondary text-white rounded">익명</div>
<div>댓글 내용1</div>
</div>
<form action="/replies/1/delete" method="post">
<button class="btn">🗑</button>
</form>
</div>
</div>
</div>
</div>
</body>
</html>12. 게시글 수정
1) BoardController.java
package com.example.boardv1.board;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor // final이 적혀있는 애로 생성자 만들어줌
@Controller // @Controller 적어야 리턴값이 파일이 됨 (외부진입점)
public class BoardController {
private final BoardService boardService;
// body : title=제목&content=내용
@PostMapping("/boards/{id}/update")
public String update(@PathVariable("id") int id, @RequestParam("title") String title, @RequestParam("content") String content){
boardService.게시글수정(id,title,content);
return "redirect:/boards/"+id;
}
}2) BoardService.java
package com.example.boardv1.board;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service // 컴포넌트 스캔되서 IoC에 뜸 + 목적 : 트랜잭션 관리
public class BoardService {
private final BoardRepository boardRepository;
@Transactional // update, delete, insert 할 때 붙이기!! import 주의! springframework(O) 자카르타(X)
public void 게시글수정(int id, String title, String content) {
Board board = boardRepository.findById(id);
board.setTitle(title);
board.setContent(content);
// 자동 flush
}
}@Transactional 의 역할- 원자성
모든 것이 다 되면 commit, 하나라도 실패하면 rollback
- 자동
flush()
트랜잭션 종료 시 자동으로 flush 됨
3) update-form.mustache 수정
{{> header}}
<div class="container p-5">
<div class="card">
<div class="card-header"><b>게시글 수정</b></div>
<div class="card-body">
<form action="/boards/{{model.id}}/update" method="post" enctype="application/x-www-form-urlencoded">
<div class="mb-3">
<input type="text" class="form-control" placeholder="Enter title" name="title" value="{{model.title}}">
</div>
<div class="mb-3">
<textarea class="form-control" rows="5" name="content">{{model.content}}</textarea>
</div>
<button class="btn btn-secondary form-control">글수정하기</button>
</form>
</div>
</div>
</div>
</body>
</html>13. 게시글 등록
1) BoardSaveDTO.java
C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\board\BoardSaveDTO.java
컨트롤러에
save() 만들 때, 브라우저에서 입력되는 title, content를 클래스로 받아서 전달하기 위한 파일 (컨트롤러에 save만들기 전에 만들어놓기)package com.example.boardv1.board;
import lombok.Data;
@Data
public class BoardSaveDTO {
private String title;
private String content;
}2) BoardController.java
package com.example.boardv1.board;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor // final이 적혀있는 애로 생성자 만들어줌
@Controller // @Controller 적어야 리턴값이 파일이 됨 (외부진입점)
public class BoardController {
private final BoardService boardService;
// title : title=title7&content=content7 (x-www-form)
@PostMapping("/boards/save")
public String save(BoardSaveDTO reqDTO){ // new해서 넣어줌! 필드가 많을 때 상태만 추가하면 되기 때문에 매우 편함! 재사용 가능!(필드명 잘 적어야 함)
boardService.게시글쓰기(reqDTO.getTitle(), reqDTO.getContent());
return "redirect:/";
}
}3) BoardService.java
package com.example.boardv1.board;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service // 컴포넌트 스캔되서 IoC에 뜸 + 목적 : 트랜잭션 관리
public class BoardService {
private final BoardRepository boardRepository;
// 역할 1. 원자성(모든 게 다 되면 commit, 하나라도 실패하면 rollback)
// 역할 2. 트랜잭션 종료시 flush 됨
@Transactional
public void 게시글쓰기(String title, String content) {
// 1. 비영속 객체
Board board = new Board();
board.setTitle(title);
board.setContent(content);
System.out.println("before persist " + board.getId());
// 2. persist
boardRepository.save(board);
System.out.println("after persist " + board.getId());
}
}14. 게시글 삭제
1) BoardController.java
package com.example.boardv1.board;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor // final이 적혀있는 애로 생성자 만들어줌
@Controller // @Controller 적어야 리턴값이 파일이 됨 (외부진입점)
public class BoardController {
private final BoardService boardService;
@PostMapping("/boards/{id}/delete")
public String delete(@PathVariable("id") int id){
boardService.게시글삭제(id);
return "redirect:/";
}
}2) BoardService.java
package com.example.boardv1.board;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service // 컴포넌트 스캔되서 IoC에 뜸 + 목적 : 트랜잭션 관리
public class BoardService {
private final BoardRepository boardRepository;
@Transactional
public void 게시글삭제(int id) {
Board board = boardRepository.findById(id); // 영속화
boardRepository.delete(board); // 영속화된 객체를 삭제
}
}3) detail.mustache 수정
{{> header}}
<div class="container p-5">
<!-- 수정삭제버튼 -->
<div class="d-flex justify-content-end">
<a href = "/boards/{{model.id}}/update-form" class="btn btn-secondary me-1">수정</a>
<form action = "/boards/{{model.id}}/delete" method="post">
<button class="btn btn-outline-secondary">삭제</button>
</form>
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{model.title}}</b></h2>
<hr />
<div class="d-flex justify-content-end">
작성자 : 익명
</div>
<div class="m-4 p-2">
{{model.content}}
</div>
</div>
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form action="/replies/save" method="post">
<textarea class="form-control" rows="2" name="comment"></textarea>
<div class="d-flex justify-content-end">
<button class="btn btn-secondary mt-1">
댓글등록
</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
<!-- 댓글아이템 -->
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-secondary text-white rounded">익명</div>
<div>댓글 내용2</div>
</div>
<form action="/replies/1/delete" method="post">
<button class="btn">🗑</button>
</form>
</div>
<!-- 댓글아이템 -->
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-secondary text-white rounded">익명</div>
<div>댓글 내용1</div>
</div>
<form action="/replies/1/delete" method="post">
<button class="btn">🗑</button>
</form>
</div>
</div>
</div>
</div>
</body>
</html>15. warning 없애기
1) application.properties 추가
C:\workspace\spring_lab\boardv1\src\main\resources\application.properties
# ===== osiv =====
spring.jpa.open-in-view=true추가하면 warning 사라짐!
Git
spring_lab-boardv1
es0127park-dotcom • Updated Jan 28, 2026
Share article