2026. 4. 9. 21:14ㆍ대우개발원 수업 내용/spring boot, framework
4일차까지의 정리
list페이지 검색처리까지
5일차 해야할것 view register 게시물을 작성하는(등록)페이지 작성
26-04-09
1. 유효성 검사 (Validation) 설정
2. 게시글 등록 기능 (Register - Create)
3. 게시글 상세 조회 기능 (Read - Read)
4. 게시글 수정 기능 (Modify - Update)
5. 게시글 삭제 기능 (Remove - Delete)
오늘 바꾼것

Spring Boot와 Thymeleaf를 활용해서 웹 게시판의 핵심 기능인 CRUD(Create, Read, Update, Delete)와
유효성 검사(Validation) 흐름을 구현
1. 유효성 검사 (Validation) 설정
- 의존성 추가: build.gradle에 spring-boot-starter-validation을 추가하여 검증 기능을 활성화
- DTO 조건 설정: BoardDTO의 필드(title, content 등)에 @NotEmpty, @Size 등의 어노테이션을 붙여
입력값의 필수 여부와 길이를 제한
2. 게시글 등록 기능 (Register - Create)
- Controller 구현: @GetMapping으로 등록 폼 화면을 보여주고, @PostMapping으로 입력값을 검증(@Valid)한 뒤 DB에 저장하는 로직을 구현했습니다.
- View 작성 (register.html): * 타임리프 레이아웃(basic.html)을 적용해 공통 UI를 가져왔습니다.
- 사용자 입력을 받을 <form> 태그를 구성
- 유효성 검사 실패 시 alert 창을 띄워 사용자에게 어떤 입력이 잘못되었는지 알려주는 JS 코드를 작성
- 결과 알림 (list.html): 글 등록 성공 후 목록 페이지로 넘어왔을 때,
새로 등록된 글 번호(bno)와 함께 "게시물 등록이 완료되었습니다"라는 Bootstrap Modal 창이 뜨도록 연동
3. 게시글 상세 조회 기능 (Read - Read)
- Controller 구현: /read 경로로 들어오는 GET 요청을 받아 특정 글 번호(bno)의 데이터를
DB에서 꺼내 모델(Model)에 담아 화면으로 전달 - View 작성 (read.html):
- 전달받은 게시글 데이터를 readonly 속성을 사용해 읽기 전용으로 화면에 출력
- List(목록) 및 Modify(수정) 버튼을 만들고, 페이지 이동 시 검색이나 페이징 상태(link)가 유지되도록 파라미터를 연결
- 목록에서 연결 (list.html): 게시판 목록의 글 제목에 <a> 태그를 걸어 클릭 시 상세 페이지(/read)로 이동하도록 링크 처리
4. 게시글 수정 기능 (Modify - Update)
Controller 구현: * /modify GET 요청을 /read와 함께 묶어서 수정 화면을 띄우도록 설정.
- /modify POST 요청을 만들어 유효성 검사 후 통과하면 DB를 수정하고,
완료되면 다시 상세 페이지(/read)로 리다이렉트 하도록 구현
- View 작성 (modify.html):
- 작성자, 등록일 등은 readonly로 막고, 제목과 내용만 수정할 수 있는 <form>을 구성
- JavaScript를 활용해 Modify 버튼 클릭 시 폼 전송, List 버튼 클릭 시 목록으로 돌아가도록
5. 게시글 삭제 기능 (Remove - Delete)
- Controller 구현: /remove 요청을 받아 서비스 계층을 통해 해당 글 번호의 데이터를 삭제하고,
삭제가 완료되면 목록 페이지(/list)로 리다이렉트 - View 이벤트 연결 (modify.html): 화면의 Remove 버튼을 누르면 JavaScript가 작동하여
폼의 액션을 /board/remove로 변경한 뒤 POST 방식으로 제출(Submit)하여 삭제를 수행
bulid.gradle에
implementation 'org.springframework.boot:spring-boot-starter-validation'
dependency(의존성) 추가

@Notempty
@Future
같은 validation을 사용할 수 있게 하는 것.
BoardDTO에 validation을 추가

public class BoardDTO {
private Long bno;
@NotEmpty
@Size(min = 3, max = 100)
private String title;
@NotEmpty
private String content;
private String writer;
private LocalDateTime regDate;
private LocalDateTime modDate;
}
BoardController.java에 register 추가
입력받은 값을 전송하기 위한 PostMapping의 register도 필요

@GetMapping("/register")는 등록 페이지를 보여주고,
@PostMapping("/register")는 입력값을 검증해 오류가 있으면 다시 등록 페이지로 리다이렉트하고,
성공 시 새 글을 저장한 뒤 목록 페이지로 리다이렉트
즉, 등록 화면 표시 → 유효성 검사 → 저장 및 결과 전달 흐름을 구현한 코드
@GetMapping("/register")
public void registerGET() {
}
@PostMapping("/register")
public String registerPost(@Valid BoardDTO boardDTO, BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
log.info("board POST register.........");
if(bindingResult.hasErrors()) {
redirectAttributes.addFlashAttribute("errors",
bindingResult.getAllErrors());
return "redirect:/board/register";
}
log.info(boardDTO);
Long bno = boardService.register(boardDTO);
redirectAttributes.addFlashAttribute("result", bno);
return "redirect:/board/list";
}
Controller가 반환하는 뷰에 대응하는 화면을 만들기
list.html을 작성할 때 타임리프의 layout 기능을 활용해서 basic.html을 가져오고,
그 안에서 content 구간을 정의해서 실제 게시판 목록을 표시하는 구조로 만듬.
즉, basic.html은 공통 레이아웃(헤더, 푸터 등)을 담당하고, list.html은 그 레이아웃을 확장하면서 게시판 리스트를 보여주는 역할
templates/board/register.html 만들기

list.html에서 상단 부분을 가져옴
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/basic.html}">
register.html 상단에 코드를 추가
타임리프 레이아웃을 활용한 뷰 템플릿 조각
-
: basic.html 같은 공통 레이아웃에서 content 영역을 채우는 부분.
여기서 카드 형태로 "Board Register" 화면을 구성 - <script layout:fragment="script" th:inline="javascript"> : 레이아웃의 script 영역에 들어갈 자바스크립트를 정의하는 자리. 타임리프의 th:inline="javascript"를 사용하면 서버 데이터를 JS 코드에 안전하게 삽입 가능
즉, 이 파일은 공통 레이아웃을 가져와서 게시판 등록 화면의 본문과 스크립트 영역을 채우는 뷰
register.html
<head>
<title>Board Register</title>
</head>
<div layout:fragment="content">
<div class="row mt-3">
<div class="col">
<div class="card">
<div class="card-header">
Board Register
</div>
<div class="card-body">
</div> <!-- end card body -->
</div> <!-- end card -->
</div> <!-- end col -->
</div><!-- end row -->
</div>
<script layout:fragment="script" th:inline="javascript">
</script>
이 코드를 다 작성하고 실행하면 아래처럼 화면이 나옴

register.html
내용을 추가하기 위해
form을 추가
<form action="/board/register" method="post">
<div class="input-group mb-3">
<span class="input-group-text">Title</span>
<input type="text" name="title" class="form-control" placeholder="Title">
</div>
<div class="input-group mb-3">
<span class="input-group-text">Content</span>
<textarea class="form-control col-sm-5" rows="5" name="content"></textarea>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Writer</span>
<input type="text" name="writer" class="form-control" placeholder="Writer">
</div>
<div class="my-4">
<div class="float-end">
<button type="submit" class="btn btn-primary">Submit</button>
<button type="reset" class="btn btn-secondary">Reset</button>
</div>
</div>
</form>
그리고 아래쪽 script 쪽에
아무것도 작성하지 않으면 alert 창이 뜨도록 작성
register.html
<script layout:fragment="script" th:inline="javascript">
const errors = [[${errors}]]
console.log(errors)
let errorMsg = ''
if (errors) {
for(let i=0; i<errors.length; i++) {
errorMsg += `${errors[i].field}은(는) ${errors[i].code} \n`
}
alert(errorMsg)
}
</script>
실행 해서 아무것도 입력하지 않고 등록버튼을 눌러보면

사진처럼 뜬다.
내용을 입력하면 list에 정상적으로 등록됨

list.html
alert창이 뜨도록 아래 코드를 추가
==> 등록한 bno가 몇번인지 뜨도록

// show moal
const result = [[${result}]]
if (result){
alert(result)
}
코드를 추가하면
결과(bno)가 뜸


list.html
위에서 한것을 modal창으로 뜨게 변경
</div><!-- end row-->
코드 아래에
아래 modal 코드 추가

<div class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Modal body text goes here.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
list.html
하단 코드를 변경

// show modal
const result = [[${result}]]
// if (result){
// alert(result)
// }
const modal = new bootstrap.Moal(document.querySelector(".modal"))
if (result){
modal.show()
}
실행하면 modal창이 뜸

list.html
modal 창을 수정

<div class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">게시글 등록 확인</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>게시물 등록이 완료되었습니다.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<!-- <button type="button" class="btn btn-primary">Save changes</button>-->
</div>
</div>
</div>
</div>
result가 있다면 modal창을 띄우라고 해놨기 때문에
register에서 list로 넘어갈때만 modal창이 뜸
1. modal-title에 게시글 등록확인으로 변경
2. modal-body 게시글 등록이 완료되었습니다.로 변경
3. Save 버튼 없앰
실행하면

read
1. BoardController에 read 항목을 추가

@GetMapping("/read")
public void read(Long bno, PageRequestDTO pageRequestDTO, Model model) {
BoardDTO boardDTO = boardService.readOne(bno);
log.info(boardDTO);
model.addAttribute("dto", boardDTO);
}
2. read.html 생성

상단에 네임테그스페이스 추가
타임리프(Thymeleaf)와 Ultraq Layout Dialect를 활용한 뷰 템플릿
- 상단의 xmlns:th와 xmlns:layout 네임스페이스 선언은 타임리프와 레이아웃 기능을 사용하기 위해 필요
- layout:decorate="~{layout/basic.html}"는 basic.html을 기본 레이아웃으로 가져와서
이 페이지(read.html)의 구조를 그 레이아웃에 맞춰 확장 - <script layout:fragment="script" th:inline="javascript">는 basic.html에서 정의된 content 영역을 채우는 부분으로,
여기서는 "Board Read" 화면을 카드 형태로 구성
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/basic.html}">
<head>
<title>Board Read</title>
</head>
<div layout:fragment="content">
<div class="row mt-3">
<div class="col">
<div class="card">
<div class="card-header">
Board Read
</div>
<div class="card-body">
</div><!--end card body-->
</div><!--end card-->
</div><!-- end col-->
</div><!-- end row-->
</div>
<script layout:fragment="script" th:inline="javascript">
</script>
이제 실행하면 아래 사진처럼 나옴
(아직 링크처리는 안했기 때문에 직접 경로를 지정해서 실행.)
아직 내용 DB를 안불러 왔기 때문에 안뜨는게 맞음

<div class="card-body">
card-body 아래에(안에)
태그를 추가해서 내용이 보이도록
아래의 코드를 추가
- 각 <div class="input-group mb-3"> 블록은 글의 속성(Bno, Title, Content, Writer, RegDate, ModDate)을 보여주는
입력 필드나 텍스트 영역
- th:value="${dto.title}", [[${dto.content}]] 같은 표현식은 컨트롤러에서 전달한 BoardDTO 객체의 값을 화면에 출력
- 모든 필드에 readonly 속성을 줘서 수정은 못 하고 조회 전용으로 표시
- 마지막 버튼 영역(List, Modify)은 목록 화면으로 돌아가거나 수정 화면으로 이동할 수 있도록 UI를 제공
즉, 컨트롤러에서 넘겨준 게시글 데이터를 읽기 전용으로 보여주는 뷰 코드
<div class="input-group mb-3">
<span class="input-group-text">Bno</span>
<input type="text" class="form-control" th:value="${dto.bno}" readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Title</span>
<input type="text" class="form-control" th:value="${dto.title}" readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Content</span>
<textarea class="form-control col-sm-5" rows="5" readonly>[[${dto.content}]]</textarea>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Writer</span>
<input type="text" class="form-control" th:value="${dto.writer}" readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text">RegDate</span>
<input type="text" class="form-control" th:value="${#temporals.format(dto.regDate, 'yyyy-MM-dd HH:mm:ss')}" readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text">ModDate</span>
<input type="text" class="form-control" th:value="${#temporals.format(dto.modDate, 'yyyy-MM-dd HH:mm:ss')}" readonly>
</div>
<div class="my-4">
<div class="float-end" >
<button type="button" class="btn btn-primary">List</button>
<button type="button" class="btn btn-secondary">Modify</button>
</a>
</div>
</div>
이 코드를 추가 한다음에 실행하면

내용이 정상적으로 뜸

List, Modify에 연결될 수있게 하는 링크를 연결하는 코드 추가

th:with="link = ${pageRequestDTO.getLink()}"
: 컨트롤러에서 전달된 `pageRequestDTO`의 링크 정보를 지역 변수 `link`로 저장
th:href="|@{/board/list}?${link}|"
: 목록 버튼을 누르면 `board/list`로 이동하면서 페이지 요청 파라미터(`link`)를 붙여줌
th:href="|@{/board/modify(bno=${dto.bno})}&${link}|"
:수정 버튼을 누르면 해당 글 번호(`dto.bno`)와 페이지 파라미터를 포함해 `board/modify`로 이동
즉, 목록으로 돌아갈 때도 페이지 정보가 유지되고, 수정 화면으로 이동할 때도 현재 글 번호와 페이지 정보를 함께 전달하는 구조
read.html
<div class="my-4">
<div class="float-end" th:with="link = ${pageRequestDTO.getLink()}">
<a th:href="|@{/board/list}?${link}|" class="text-decoration-none">
<button type="button" class="btn btn-primary">List</button>
</a>
<a th:href="|@{/board/modify(bno=${dto.bno})}&${link}|" class="text-decoration-none">
<button type="button" class="btn btn-secondary">Modify</button>
</a>
</div>
</div>
실행 하면

List 버튼을 누르면

Modify는 아직 modify.html이 없어서 눌러도 오류가 뜸
list.html
title을 누르면 read로 연결되도록
코드 추가

<tbody th:with="link = ${pageRequestDTO.getLink()}">
: 컨트롤러에서 전달된 pageRequestDTO의 링크 정보를 지역 변수 link로 저장
<tr th:each="dto:${responseDTO.dtoList}">
: responseDTO.dtoList에 담긴 게시글 목록을 하나씩 반복하며 행을 생성
[[${dto.bno}]], [[${dto.title}]], [[${dto.writer}]]
: 각 게시글의 번호, 제목, 작성자를 출력
<a th:href="|@{/board/read(bno = ${dto.bno})}&${link}|">
: 제목을 클릭하면 해당 글 상세 페이지(/board/read)로 이동하며 페이지 정보(link)도 함께 전달
[[${#temporals.format(dto.regDate, 'yyyy-MM-dd')}]]
: 등록일을 yyyy-MM-dd 형식으로 출력
list.html
<tbody th:with="link = ${pageRequestDTO.getLink()}">
<tr th:each="dto:${responseDTO.dtoList}" >
<th scope="row">[[${dto.bno}]]</th>
<td>
<!-- [[${dto.title}]]-->
<a th:href="|@{/board/read(bno = ${dto.bno})}&${link}|"> [[${dto.title}]]</a>
</td>
<td>[[${dto.writer}]]</td>
<!-- <td>[[${dto.regDate}]]</td>-->
<td>[[${#temporals.format(dto.regDate, 'yyyy-MM-dd')}]]</td>
</tr>
</tbody>
실행하면 링크가 정상적으로 연결되서 list에서 title을 클릭하면 read로 연결된다.


modify도 가능하도록
1. BoardController 수정

BoardController
// @GetMapping("/read")
@GetMapping({"/read", "/modify"})
public void read(Long bno, PageRequestDTO pageRequestDTO, Model model) {
BoardDTO boardDTO = boardService.readOne(bno);
log.info(boardDTO);
model.addAttribute("dto", boardDTO);
}
2. modify.html 생성
modify는 수정을 해서 저장까지 해야하기 때문에
form 태그가 필요하다

bno, wirter, regDate, modDate는 readonly를 넣어서 수정이 불가능하게 하고
나머지는 수정이 가능하도록
예)
th:value="${dto.title}
이렇게 적는다
modify.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/basic.html}">
<head>
<title>Board Modify</title>
</head>
<div layout:fragment="content">
<div class="row mt-3">
<div class="col">
<div class="card">
<div class="card-header">
Board Modify
</div>
<div class="card-body">
<form th:action="@{/board/modify}" method="post" id="f1">
<div class="input-group mb-3">
<span class="input-group-text">Bno</span>
<input type="text" class="form-control" th:value="${dto.bno}" name="bno" readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Title</span>
<input type="text" class="form-control" name="title" th:value="${dto.title}">
</div>
<div class="input-group mb-3">
<span class="input-group-text">Content</span>
<textarea class="form-control col-sm-5" rows="5" name="content">[[${dto.content}]]</textarea>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Writer</span>
<input type="text" class="form-control" th:value="${dto.writer}" name="writer" readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text">RegDate</span>
<input type="text" class="form-control" th:value="${#temporals.format(dto.regDate, 'yyyy-MM-dd HH:mm:ss')}" readonly>
</div>
<div class="input-group mb-3">
<span class="input-group-text">ModDate</span>
<input type="text" class="form-control" th:value="${#temporals.format(dto.modDate, 'yyyy-MM-dd HH:mm:ss')}" readonly>
</div>
<div class="my-4">
<div class="float-end">
<button type="button" class="btn btn-primary listBtn">List</button>
<button type="button" class="btn btn-secondary modBtn">Modify</button>
<button type="button" class="btn btn-danger removeBtn">Remove</button>
</div>
</div>
</form>
</div><!--end card body-->
</div><!--end card-->
</div><!-- end col-->
</div><!-- end row-->
</div>
<script layout:fragment="script" th:inline="javascript">
const errors = [[${errors}]]
console.log(errors)
let errorMsg = ''
if(errors){
for (let i = 0; i < errors.length; i++) {
errorMsg += `${errors[i].field}은(는) ${errors[i].code} \n`
}
history.replaceState({}, null, null)
alert(errorMsg)
}
const link = [[${pageRequestDTO.getLink()}]]
const formObj = document.querySelector("#f1")
document.querySelector(".modBtn").addEventListener("click", function(e){
e.preventDefault()
e.stopPropagation()
formObj.action = `/board/modify?${link}`
formObj.method ='post'
formObj.submit()
}, false)
document.querySelector(".removeBtn").addEventListener("click", function(e){
e.preventDefault()
e.stopPropagation()
formObj.action = `/board/remove`
formObj.method ='post'
formObj.submit()
}, false)
document.querySelector(".listBtn").addEventListener("click", function(e){
e.preventDefault()
e.stopPropagation()
formObj.reset()
self.location =`/board/list?${link}`
}, false)
</script>
코드를 넣고
실행하면
title과 content만 변경이 가능한것을 알 수 있다.

이제 Modify 버튼이 동작 하도록
BoardController에 코드 추가

if(bindingResult.hasErrors()) { ... return "redirect:/board/modify?"+link; }
: 유효성 검사에서 오류가 발생하면 에러 메시지를 FlashAttribute로 담고, 수정 화면으로 다시 리다이렉트하며 페이지 정보(link)와 글 번호(bno)를 전달
boardService.modify(boardDTO);
: 오류가 없을 경우 서비스 계층을 호출해 실제 게시글 데이터를 수정
redirectAttributes.addFlashAttribute("result", "modified");
: 수정 성공 메시지를 FlashAttribute로 담아 다음 요청에서 사용할 수 있도록 전달
redirectAttributes.addAttribute("bno", boardDTO.getBno()); return "redirect:/board/read";
: 수정된 글 번호를 파라미터로 넘겨주고, 수정 완료 후 해당 글 상세보기 페이지로 리다이렉트
BoardController.java
@PostMapping("/modify")
public String modify(@Valid BoardDTO boardDTO,
BindingResult bindingResult,
PageRequestDTO pageRequestDTO,
RedirectAttributes redirectAttributes){
log.info("board modify post.........." + boardDTO);
if(bindingResult.hasErrors()) {
log.info("has errors..........");
String link = pageRequestDTO. getLink();
redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
redirectAttributes.addAttribute("bno", boardDTO.getBno());
return "redirect:/board/modify?"+link;
}
boardService.modify(boardDTO);
redirectAttributes.addFlashAttribute("result", "modified");
redirectAttributes.addAttribute("bno", boardDTO.getBno());
return "redirect:/board/read";
}
실행하면 정상적으로 변경된다.
modify버튼은 아래 코드로 인해 동작함

remove
1. BoardController에 코드 추가

boardService.remove(bno);
: 전달받은 글 번호(`bno`)를 이용해 서비스 계층에서 해당 게시글을 삭제
redirectAttributes.addFlashAttribute("result", "removed");
: 삭제 성공 메시지를 FlashAttribute로 담아 다음 요청에서 사용할 수 있도록 전달
return "redirect:/board/list";
: 삭제가 완료되면 게시글 목록 페이지로 리다이렉트
@GetMapping("remove")
public String remove(Long bno, RedirectAttributes redirectAttributes) {
log.info("remove post...." + bno);
boardService.remove(bno);
redirectAttributes.addFlashAttribute("result", "removed");
return "redirect:/board/list";
}
modify.html
에 이미 list와 remove 버튼에 대한 동작이 추가 되어 있다

삭제 버튼을 누르면 폼을 POST 방식으로 /board/remove에 제출해서 해당 글을 삭제하도록 동작하는 코드
document.querySelector(".removeBtn").addEventListener("click", function(e){
e.preventDefault()
e.stopPropagation()
formObj.action = `/board/remove`
formObj.method ='post'
formObj.submit()
}, false)
목록 버튼을 누르면 폼을 초기화하고, 현재 페이지 정보를 유지한 채 게시판 목록 화면으로 이동하는 코드
document.querySelector(".listBtn").addEventListener("click", function(e){
e.preventDefault()
e.stopPropagation()
formObj.reset()
self.location =`/board/list?${link}`
}, false)
근데 remove에서 list로 넘어갈때 아까 설정했던 게시글 등록이 완료되었다는 modal이 뜨기 때문에
다른 modal을 추가해서 삭제가 완료되었다는 modal창을 추가하면 좋음
'대우개발원 수업 내용 > spring boot, framework' 카테고리의 다른 글
| 자바 스프링 부트 7일차 b01Rest REST API 서버 구축부터 Axios를 활용한 프론트엔드 통신 준비 (0) | 2026.04.14 |
|---|---|
| 자바 스프링 부트 6일차 b01Rest / Ajax 와 JSON (1) | 2026.04.10 |
| 자바 스프링 부트 4일차 b01 (0) | 2026.04.06 |
| 자바 스프링 부트 3일차 b01 Spring Data JPA 기본 및 CRUD (0) | 2026.04.06 |
| 자바 스프링 부트 2일차 b01 (0) | 2026.04.02 |



