λ·°ν νλ¦Ώμ λ§λλ λꡬ=λ·° ν νλ¦Ώ μμ§
src > main > resources > templatesμ λ¨Έμ€ν μΉ νμΌ μ μ₯νλ©΄ μ€νλ§λΆνΈμμ μλμΌλ‘ λ‘λ©
μ μΌ μμ€μ doc μ λ ₯ ν tabν€ λλ₯΄λ©΄ κΈ°λ³Έ htmlμ½λ μλμΌλ‘ μμ±λ¨
μ΄ νμ΄μ§λ₯Ό μΉμμ λ³΄λ €λ©΄ 컨νΈλ‘€λ¬, λͺ¨λΈ μ΄μ©ν΄μΌν¨
src > main > javaλλ ν°λ¦¬μ κΈ°λ³Έ ν¨ν€μ§ μμ 컨νΈλ‘€λ¬ ν¨ν€μ§λ‘ μμ±
κΈ°λ³ΈμΌλ‘ μ λ ₯λ ν¨ν€μ§λͺ λ€μ controller μΆκ°, ν΄λΉ ν¨ν€μ§ μμ ###Controller Class μμ±
μ΄ ν΄λμ€κ° 컨νΈλ‘€λ¬μμ μ μΈνλ @Controller μ΄λ
Έν
μ΄μ
μμ±, λ·° λ°νν λ©μλ μμ± ν returnλ¬Έ μμ mustache νμ΄μ§ λ°ν
Controller Classμμ @GetMapping μΌλ‘ URLμ£Όμ λ°ν
컨νΈλ‘€λ¬ λ©μλμ λ§€κ°λ³μλ‘ λ°μμ΄
λ·°ν
νλ¦Ώμ λ³μ μ½μ
{{λ³μλͺ
}}
λ·° λ°ννλ λ©μλμ Model νμ μ model λ§€κ°λ³μ μΆκ°
model.addAttribute("λ³μλͺ
", "λ³μ«κ°") : λͺ¨λΈμμ λ³μλ₯Ό λ±λ‘νλ λ©μλ
νΌλ°μ΄ν°: html μμμΈ < form>νκ·Έμ μ€λ € μ μ‘λλ λ°μ΄ν°
<form> νκ·Έ : μΉνλΌμ°μ μμ μλ²λ‘ λ°μ΄ν°λ₯Ό μ μ‘ν λ μ¬μ©, λ°μ΄ν°λ₯Ό μ μ‘ν λ μ΄λλ‘, μ΄λ»κ² 보λΌμ§ λ±μ μ μ΄μ 보λ
< form>νκ·Έμ μ€μ΄ λ³΄λΈ λ°μ΄ν°λ μλ²μ 컨νΈλ‘€λ¬κ° κ°μ²΄μ λ΄μμ λ°μ
DTO: data transfer object, < form>νκ·Έμ μ€μ΄λ³΄λΈ λ°μ΄ν°λ₯Ό λ΄μ λ°λ μλ² μ»¨νΈλ‘€λ¬μ κ°μ²΄, DTOλ‘ λ°μ λ°μ΄ν°λ μ΅μ’
μ μΌλ‘ λ°μ΄ν°νμ΄μ€μ μ μ₯λ¨
- action: μ΄λλ‘ λ³΄λΌμ§μ κ΄ν μ 보, URL μ°κ²° μ£Όμ. ex)
action="/articles/create"ν΄λΉ νμ΄μ§λ‘ νΌλ°μ΄ν°λ₯Ό 보λΈλ€λ μλ―Έ - method: μ΄λ»κ² 보λΌμ§μ κ΄ν μ 보, μμ±κ°μΌλ‘ get, post 2κ°μ§ μ€μ κ°λ₯.
λ·°νμ΄μ§μμ νΌλ°μ΄ν°λ₯Ό postλ°©μμΌλ‘ μ μ‘νλ―λ‘ μ»¨νΈλ‘€λ¬μμ λ°μλλ @PostMapping()μΌλ‘ λ°μ.
νλ‘μ νΈ νμΌμμ newβpackage μμ± ν dto ν¨ν€μ§ μμ± (컨νΈλ‘€λ¬μ κ°μ λ 벨)
μλ‘μ΄ μλ° ν΄λμ€ μμ± νλ©΄ ν΄λΉ μλ°νμΌμ΄ νΌ λ°μ΄ν°λ₯Ό λ°μμ¬ κ·Έλ¦, DTOκ° λ¨.
μ λ ₯λ°λ μ°½λ§νΌμ νλ κ°―μ νμ. μμ±μμ toString λ©μλ μΆκ°
DTOν΄λμ€λ₯Ό 컨νΈλ‘€λ¬ λ©μλμ λ§€κ°λ³μλ‘ λ°μμ΄, form κ°μ²΄λ₯Ό λ§€κ°λ³μλ‘ μ μΈ
mustache μ λ ₯νΌμ νλλͺ μ§μ νλ©΄ ν΄λΉ μ λ ₯νΌμ΄ DTOμ νλμ μ°κ²°λ¨
- λ·°νμ΄μ§ λ§λ€κΈ° (form action, method μ§μ )
- 컨νΈλ‘€λ¬ λ§λ€κΈ° (PostMappingμΌλ‘ URL μ£Όμ μ°κ²°)
- DTO λ§λ€κΈ°
- 컨νΈλ‘€λ¬μμ νΌ λ°μ΄ν° μ μ‘λ°μ DTO κ°μ²΄μ λ΄κΈ°
λ°μ΄ν°λ² μ΄μ€: λ°μ΄ν°λ₯Ό κ΄λ¦¬νλ μ°½κ³ , DBμ λͺ¨λ λ°μ΄ν°λ νκ³Ό μ΄λ‘ ꡬμ±λ ν
μ΄λΈμ μ μ₯ν΄ κ΄λ¦¬
JPA: μλ° μΈμ΄λ‘ DBμ λͺ
λ Ήμ λ΄λ¦¬λ λꡬ, λ°μ΄ν°λ₯Ό κ°μ²΄ μ§ν₯μ μΌλ‘ κ΄λ¦¬ν μ μκ² ν΄μ€
μν°ν°: μλ° κ°μ²΄κ° DBλ₯Ό μ΄ν΄ν μ μκ² λ§λ κ², μ΄λ₯Ό κΈ°λ°μΌλ‘ ν
μ΄λΈμ΄ λ§λ€μ΄μ§
리νμ§ν°λ¦¬: μν°ν°κ° DBμ ν
μ΄λΈμ μ μ₯ λ° κ΄λ¦¬λ μ μκ² νλ μΈν°νμ΄μ€
νΌ λ°μ΄ν°λ₯Ό DBμ μ μ₯νλ €λ©΄
- DTOλ₯Ό μν°ν°λ‘ λ³ννκΈ°
- 리νμ§ν°λ¦¬λ₯Ό μ΄μ©ν΄ μν°ν°λ₯Ό DBμ μ μ₯νκΈ°
Article article = form.toEntity(); // form κ°μ²΄μ toEntity() λ©μλ νΈμΆ, κ·Έ λ°ν κ°μ Article νμ
μ article μν°ν°μ μ μ₯
Article ν΄λμ€ λ§λ€κΈ°: νλ‘μ νΈμ entity ν¨ν€μ§ λ§λ ν ν΄λμ€ μμ±
@Entityμ΄λ Έν μ΄μ λΆμ΄κΈ°@Columnμ΄λ Έν μ΄μ λΆμ΄κ³ νλ μμ±- λν―κ°
@Idλ‘ μ μΈ ν@GeneratedValueλ‘ λν―κ° μλ μμ± -> λν―κ°μΌλ‘ μ€λ³΅λ λ°μ΄ν° μλλΌλ κ΅¬λΆ κ°λ₯ - μμ±μμ toString() λ©μλ μμ±
toEntity()λ©μλ μμ±: DTOμΈ form κ°μ²΄λ₯Ό μν°ν° κ°μ²΄λ‘ λ³ννλ μν
ArticleForm(DTO ν΄λμ€)μ toEntity() λ©μλ μΆκ°- DTO κ°μ²΄ μν°ν°λ‘ λ°ν,
return new Article(null, title, content);//idμ 보 μ μΈν ArticleForm κ°μ²΄μ μ λ¬κ° μ λ ₯
- 컨νΈλ‘€λ¬ νλ μ μΈλΆμ 리νμ§ν°λ¦¬ κ°μ²΄ μ μΈ
Article saved = articleRepository.save();// save() λ©μλ νΈμΆν΄ article μν°ν° μ μ₯. save() λ©μλλ μ μ₯λ μν°ν°λ₯Ό λ°ννμ¬ Article νμ μ savedλΌλ κ°μ²΄μ λ°μμ΄
리νμ§ν°λ¦¬ λ§λ€κΈ°: μΈν°νμ΄μ€ μμ±
- νλ‘μ νΈμ
repositoryν¨ν€μ§ μμ±,ArticleRepositoryμΈν°νμ΄μ€ μμ± - JPAμμ μ 곡νλ μΈν°νμ΄μ€ νμ©. 리νμ§ν°λ¦¬ μ΄λ¦ λ€μ
extend CrudRepository<T, ID>μ ν, <> μμ 2κ°μ μ λ€λ¦ μμλ₯Ό λ°μArticle: κ΄λ¦¬ λμ μν°ν°μ ν΄λμ€ νμ , μ¬κΈ°μλ ArticleLong: κ΄λ¦¬ λμ μν°ν°μ λν―κ° νμ . idκ° λν―κ°μ΄λ―λ‘ Longνμ μ λ ₯
κ°μ²΄ μ£Όμ
νκΈ°: μ€νλ§ λΆνΈλ κ°μ²΄λ₯Ό λ§λ€μ§ μμλ 미리 μμ±ν΄λμ κ°μ²΄ κ°μ Έλ€ μ°κ²°ν΄μ μ¬μ© κ°λ₯
μμ‘΄μ± μ£Όμ
(DI): 컨νΈλ‘€λ¬ ν΄λμ€μ @AutoWired μ΄λ
Έν
μ΄μ
λΆμ΄λ©΄ μ€νλ§λΆνΈκ° λ§λ€μ΄λμ κ°μ²΄ κ°μ Έμ μ£Όμ
create: μμ± read : μ‘°ν update : μμ delete : μμ , CRUD μ‘°μμ SQLλ‘ μν
src > main > resources > application.propertiesμ spring.h2.console.enabled=true μμ±
localhost:8080/h2-console μ μ, RUN νμμ jdbc μ£Όμ μ°Ύμ ν JDBC URLμ λΆμ¬ λ£κ³ Connect
SELECT μμ±λͺ
FROM ν
μ΄λΈλͺ
;
μμ±λͺ λμ * μ¬μ© μ λͺ¨λ μμ±μ μ‘°ννλΌλ λ»
INSERT INTO ν
μ΄λΈλͺ
(μμ
©λͺ
1, μμ±λͺ
2, μμ±λͺ
3, ...) VALUES (κ°1, κ°2, κ°3, ...);
둬볡: μ½λλ₯Ό κ°μνν΄μ£Όλ λΌμ΄λΈλ¬λ¦¬, νμ μ½λ₯Ό κ°νΈνκ² μμ±ν μ μμ
λ‘κΉ
: νλ‘κ·Έλ¨μ μν κ³Όμ μ κΈ°λ‘μΌλ‘ λ¨κΈ°λ κ²
리ν©ν°λ§: μ½λμ κΈ°λ₯μλ λ³ν¨μμ΄ μ½λμ ꡬ쑰 λλ μ±λ₯μ κ°μ νλ μμ
@AllArgsContructor: μμ±μ μ΄λ
Έν
μ΄μ
@ToString: toString() λ©μλ μ΄λ
Έν
μ΄μ
@Slf4j: λ‘κΉ
μ μν μ΄λ
Έν
μ΄μ
, Simple Logging Facade for Javaμ μ½μ
log.info(): 컨νΈλ‘€λ¬μ printλ¬Έ λμ λ‘κ·Έ λ¨κΈ°κΈ° μν΄ μ¬μ©
@GetMapping("/articles/{id}")
public String show(@PathVariable Long id, Model model){
log.info("id =" + id); // idλ₯Ό μ λ°μλμ§ νμΈνλ λ‘κ·Έ μ°κΈ°
// 1. idλ₯Ό μ‘°νν΄ λ°μ΄ν° κ°μ Έμ€κΈ°
Article articleEntity = articleRepository.findById(id).orElse(null); //.orElse(null) λΆνμ§ μκ³ Article λμ Optional<Article> λ£μ΄λ λ¨
// 2. λͺ¨λΈμ λ°μ΄ν° λ±λ‘νκΈ°
model.addAttribute("article", articleEntity); // articleμ΄λΌλ μ΄λ¦μΌλ‘ articleEntity κ°μ²΄ λ±λ‘
// 3. λ·° νμ΄μ§ λ°ννκΈ°
return "articles/show";
}{{>layouts/header}}
<table class="table">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Title</th>
<th scope="col">Content</th>
</tr>
</thead>
<tbody>
{{#article}}
<tr>
<th>{{id}}</th>
<td>{{title}}</td>
<td>{{content}}</td>
</tr>
{{/article}}
</tbody>
</table>
{{>layouts/footer}}@GetMapping("/articles")
public String index(Model model) {
// 1. λͺ¨λ λ°μ΄ν° κ°μ Έμ€κΈ°
List<Article> articleEntityList = articleRepository.findAll();
// 2. λͺ¨λΈμ λ°μ΄ν° λ±λ‘νκΈ°
model.addAttribute("articleList", articleEntityList);
// 3. λ·° νμ΄μ§ μ€μ νκΈ°
return "articles/index";
}{{>layouts/header}}
<table class="table">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Title</th>
<th scope="col">Content</th>
</tr>
</thead>
<tbody>
{{#articleList}}
<tr>
<th>{{id}}</th>
<td>{{title}}</td>
<td>{{content}}</td>
</tr>
{{/articleList}}
</tbody>
</table>
{{>layouts/footer}}λ§ν¬: 미리 μ ν΄ λμ μμ²μ κ°νΈνκ² μ μ‘νλ κΈ°λ₯, νμ΄μ§ μ΄λμ μν΄ μ¬μ©. HTMLμ <a> νκ·Έ νΉμ <form> νκ·Έλ‘ μμ±, ν΄λΌμ΄μΈνΈκ° λ§ν¬λ₯Ό ν΅ν΄ μ΄λ νμ΄μ§λ‘ μ΄λνκ² λ€κ³ μμ²νλ©΄ μλ²λ κ²°κ³Ό νμ΄μ§λ₯Ό μλ΅
리λ€μ΄λ νΈ: ν΄λΌμ΄μΈνΈκ° λ³΄λΈ μμ²μ λ§μΉ ν κ³μν΄μ μ²λ¦¬ν λ€μ μμ² μ£Όμλ₯Ό μ¬μ§μ, 리λ€μ΄λ νΈλ₯Ό μ§μλ°μ ν΄λΌμ΄μΈνΈλ ν΄λΉ μ£Όμλ‘ λ€μ μμ²μ 보λ΄κ³ μλ²λ μ΄μ λν κ²°κ³Όλ₯Ό μλ΅
λ·° νμΌμ λ§ν¬ κ±ΈκΈ°: <a> νκ·Έ μ΄μ©ν΄ λ€μκ³Ό κ°μ νμμΌλ‘ μμ±
<a href="URL μ£Όμ">λ§ν¬λ₯Ό κ±Έ λμ</a>리λ€μ΄λ νΈ μ μνκΈ°: returnλ¬Έμ μ¬μ©ν΄ λ€μκ³Ό κ°μ νμμΌλ‘ μμ±
return "redirect:URL_μ£Όμ";- <μμ νμ΄μ§> λ§λ€κ³ κΈ°μ‘΄ λ°μ΄ν° λΆλ¬μ€κΈ°
- λ°μ΄ν°λ₯Ό μμ ν΄ DBμ λ°μν ν κ²°κ³Όλ₯Ό λ³Ό μ μκ² <μμΈνμ΄μ§>λ‘ λ¦¬λ€μ΄λ νΈνκΈ°
@GetMapping("/articles/{id}/edit")
public String edit(@PathVariable Long id, Model model){
// μμ ν λ°μ΄ν° κ°μ Έμ€κΈ°
Article articleEntity = articleRepository.findById(id).orElse(null);
// λͺ¨λΈμ λ°μ΄ν° λ±λ‘νκΈ°
model.addAttribute("article", articleEntity);
// λ·° νμ΄μ§ μ€μ νκΈ°
return "articles/edit";
}value="{{title}}" <!--λ°μ΄ν° λΆλ¬μ€κΈ°, article.titleμμ article μλ΅ κ°λ₯-->
<a href="/articles/{{id}}">Back</a> <!--Back λ²νΌ μ£Όμ μ€μ -->- ν΄λΌμ΄μΈνΈλ‘λΆν° λ°μ΄ν° μμ μμ²μ΄ λ€μ΄μ¨λ€
- μμ μμ² λ°μ΄ν°λ₯Ό DBμμ μ°Ύλλ€
- μμ μμ² λ°μ΄ν°λ₯Ό λͺ¨λΈμ λ±λ‘νλ€
- λ·°νμ΄μ§μ μμ ν λ°μ΄ν°λ₯Ό ν¨κ» 보μ¬μ€λ€
MVC:μλ² μν μ λΆλ΄ν΄ μ²λ¦¬νλ κΈ°λ²
JPA:μλ²μ DB κ° μν΅μ κ΄μ¬νλ κΈ°μ
SQL:DB λ°μ΄ν°λ₯Ό κ΄λ¦¬νλ μΈμ΄
HTTP:λ°μ΄ν°λ₯Ό μ£Όκ³ λ°κΈ° μν ν΅μ κ·μ½
νλ‘ν μ½: μ»΄ν¨ν° κ°μ μννκ² ν΅μ νκΈ° μν΄ μ¬μ©νλ μ μΈκ³ νμ€μΈμ΄

New -> File -> data.sql μλ²λ₯Ό κ»λ€ μΌ€λλ§λ€ λ°μ΄ν° μλμΌλ‘ μ½μ λ¨
INSERT INTO article(id, title, content) VALUES (1, 'κ°κ°κ°κ°', '1111');
INSERT INTO article(id, title, content) VALUES (2, 'λλλλ', '2222');
INSERT INTO article(id, title, content) VALUES (3, 'λ€λ€λ€λ€', '3333');
edit.mustache: action μμ±μ νΌλ°μ΄ν°λ₯Ό μ΄λλ‘ λ³΄λΌμ§ URL μ§μ , method μμ±μ μ΄λ»κ² 보λΌμ§ λ°©μ μ§μ , idλ λͺλ² articleμ μμ νλμ§ μλ €μ€μΌν¨
<form class="container" action="/articles/update" method="post">
<input name="id" type="hidden" value="{{id}}">@PostMapping("/articles/update")
public String update(ArticleForm form){ // λ§€κ°λ³μλ‘ DTO λ°μμ€κΈ°
log.info(form.toString());
// 1. DTOλ₯Ό μν°ν°λ‘ λ³ννκΈ°
Article articleEntity = form.toEntity();
log.info(articleEntity.toString());
// 2. μν°ν°λ₯Ό DBμ μ μ₯
// 2-1. DBμμ κΈ°μ‘΄ λ°μ΄ν° κ°μ Έμ€κΈ°
Article target = articleRepository.findById(articleEntity.getId()).orElse(null);
// 2-2. κΈ°μ‘΄ λ°μ΄ν° κ°μ κ°±μ νκΈ°
if (target!=null) {
articleRepository.save(articleEntity); // μν°ν°λ₯Ό DB μ μ₯ (κ°±μ )
}
// 3. μμ κ²°κ³Ό νμ΄μ§λ‘ 리λ€μ΄λ νΈ
return "redirect:/articles/" + articleEntity.getId();
}UPDATE ν
μ΄λΈλͺ
SET μμ±λͺ
=λ³κ²½ν _κ° WHERE 쑰건 ;
UPDATE article SET title='κ°κ°κ°', content='λλλ' where id='2';
SELECT * FROM article
RedirectAttributes: RedirectAttributes κ°μ²΄μ addFlashAttribute() λ©μλλ 리λ€μ΄λ νΈλ νμ΄μ§μμ μ¬μ©ν μΌνμ± λ°μ΄ν°λ₯Ό λ±λ‘ν μ μμ

@GetMapping("/articles/{id}/delete")
public String delete(@PathVariable Long id, RedirectAttributes rttr){ // idλ₯Ό λ§€κ°λ³μλ‘ κ°μ Έμ€κΈ°
log.info("μμ μμ²μ΄ λ€μ΄μμ΅λλ€!!");
// 1. μμ ν λμ κ°μ Έμ€κΈ°
Article target = articleRepository.findById(id).orElse(null); // λ°μ΄ν° μ°ΎκΈ°
log.info(target.toString());
// 2. λμ μν°ν° μμ νκΈ°
if (target!=null) {
articleRepository.delete(target);
rttr.addFlashAttribute("msg", "μμ λμ΅λλ€!");
}
// 3. κ²°κ³Ό νμ΄μ§λ‘ 리λ€μ΄λ νΈνκΈ°
return "redirect:/articles";
}addFlashAttribute() λ©μλ: 리λ€μ΄λ νΈ μμ μ νλ²λ§ μ¬μ©ν λ°μ΄ν° λ±λ‘ν μ μμ (νλ² μ°κ³ μ¬λΌμ§λ νλ°μ± λ°μ΄ν°λ₯Ό λ±λ‘)
κ°μ²΄λͺ
.addFlashAttribute(λκ²¨μ£Όλ €λ_ν€_λ¬Έμμ΄, λκ²¨μ£Όλ €λ_κ°_κ°μ²΄);header.mustache
{{#msg}}
<div class="alert alert-primary alert-dismissible">
{{msg}}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{{/msg}}DELETE [FROM] ν
μ΄λΈλͺ
WHERE 쑰건; --[]:μλ΅κ°λ₯
DELETE article WHERE id=2;
쿼리: DBμ μ 보λ₯Ό μμ²νλ ꡬ문
λ‘κΉ
: μμ€ν
μ΄ μλν λ λΉμμ μνμ μλ μ 보λ₯Ό κΈ°λ‘νλ κ²
# 17κ°: JPA λ‘κΉ
μ€μ
## λλ²κ·Έ λ λ²¨λ‘ μΏΌλ¦¬ μΆλ ₯
logging.level.org.hibernate.SQL=DEBUG
## μ΄μκ² λ³΄μ¬μ£ΌκΈ°
spring.jpa.properties.hibernate.format_sql=true
## νλΌλ―Έν° 보μ¬μ£ΌκΈ°
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
## κ³ μ url μ€μ
spring.datasource.url=jdbc:h2:mem:testdbinsert
into
article
(content,title,id)
values
(?,?,default)select
a1_0.id,
a1_0.content,
a1_0.title
from
article a1_0select
a1_0.id,
a1_0.content,
a1_0.title
from
article a1_0
where
a1_0.id=?μ μ²΄κ° μλ νκ°μ§λ§ μ νν κ²½μ°β where
update
article
set
content=?,
title=?
where
id=?delete
from
article
where
id=?create table article (
id bigint generated by default as identity,
content varchar(255),
title varchar(255),
primary key (id)
)@GeneratedValue(strategy = GenerationType.*IDENTITY*) // DBκ° idλ₯Ό μλ μμ± μ΄λ
Έν
μ΄μ
, idκ°μ΄ κ²ΉμΉμ§ μλλ‘ ν¨
REST API: μΉμλ²μ μμμ ν΄λΌμ΄μΈνΈμ ꡬμ λ°μ§ μκ³ μ¬μ©ν μ μκ² νλ μ€κ³ λ°©μ, httpλ₯Ό μ΄μ©ν΄ μλ²μ μμ λ°ν. μ΄λ μλ²μμ 보λ΄λ μλ΅μ νΉμ κΈ°κΈ°μ μ’
μλμ§ μλλ‘ λͺ¨λ κΈ°κΈ°μμ ν΅νλ λ°μ΄ν°λ₯Ό λ°ν
JSON: μλ°μ€ν¬λ¦½νΈλ₯Ό μ΄μ©ν κ°μ²΄ ννμ, REST APIμ μλ΅ λ°μ΄ν°
| μνμ½λ | μ€λͺ |
|---|---|
| 1XX (μ 보) | μμ²μ΄ μμ λΌ μ²λ¦¬ μ€μ λλ€ |
| 2XX (μ±κ³΅) | μμ²μ΄ μ μμ μΌλ‘ μ²λ¦¬λμ΅λλ€ |
| 3XX (리λ€μ΄λ μ λ©μΈμ§) | μμ²μ μλ£νλ €λ©΄ μΆκ° νλμ΄ νμν©λλ€ |
| 4XX (ν΄λΌμ΄μΈνΈ μμ² μ€λ₯) | ν΄λΌμ΄μΈνΈμ μμ²μ΄ μλͺ»λΌ μλ²κ° μμ²μ μνν μ μμ΅λλ€ |
| 5XX (μλ² μλ΅ μ€λ₯) | μλ² λ΄λΆμ μλ¬κ° λ°μν΄ ν΄λΌμ΄μΈνΈ μμ²μ λν΄ μ μ ν μννμ§ λͺ»νμ΅λλ€ |
200:μλ΅ μ±κ³΅
404: μ°Ύμ μ μλ νμ΄μ§ μμ²
201: λ°μ΄ν° μμ± μλ£
500: μλ² λ΄λΆ μλ¬ λ°μ
@RestController: Rest APIμ© μ»¨νΈλ‘€λ¬, JSON λ°ν β λ°μ΄ν°λ₯Ό λ°ννλ€ (μΌλ° 컨νΈλ‘€λ¬λ λ·°ν
νλ¦Ώνμ΄μ§λ₯Ό λ°ν)
ν΄λΌμ΄μΈνΈμ λ°μ΄ν° μ‘°ν, μμ±, μμ , μμ μμ²μ HTTP λ©μλμ λ§κ² κ°κ° @GetMapping, @PostMapping, @PatchMapping, @DeleteMapping μΌλ‘ λ°μ μ²λ¦¬ν¨
@RequestBody : JSON λ°μ΄ν° λ°κΈ°
HttpStatus: Http μν μ½λλ₯Ό κ΄λ¦¬νλ ν΄λμ€
ResponseEntity: Rest API μμ²μ λ°μ μλ΅ν λ HTTP μν μ½λ, ν€λ, λ³Έλ¬Έμ μ€μ΄ 보λ΄λ ν΄λμ€
-
μ 체 μ½λ
@Autowired // DI private ArticleRepository articleRepository; // Get @GetMapping("/api/articles") public List<Article> index() { return articleRepository.findAll(); } @GetMapping("/api/articles/{id}") public Article show(@PathVariable Long id) { return articleRepository.findById(id).orElse(null); } // Post @PostMapping("api/articles") public Article create(@RequestBody ArticleForm dto) { Article article = dto.toEntity(); return articleRepository.save(article); } // Patch @PatchMapping("api/articles/{id}") public ResponseEntity<Article> update(@PathVariable Long id, @RequestBody ArticleForm dto) { // 1. μμ μ© μν°ν° μμ± Article article = dto.toEntity(); log.info("id: {}, article: {}", id, article.toString()); // 2. λμ μν°ν°λ₯Ό μ‘°ν Article target = articleRepository.findById(id).orElse(null); // 3. μλͺ»λ μμ² μ²λ¦¬(λμμ΄ μκ±°λ idκ° λ€λ₯Έ κ²½μ°) if (target == null || id != article.getId()) { // 400, μλͺ»λ μμ² μλ΅ log.info("μλͺ»λ μμ²! id: {}, article: {}", id, article.toString()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); } // 4. μ λ°μ΄νΈ λ° μ μ μλ΅(200) target.patch(article); // λκ° μμ± μνκ³ μμ ν μ κ·Έ μ μν κ·Έλλ‘ μ μ§λλλ‘ Article updated = articleRepository.save(target); return ResponseEntity.status(HttpStatus.OK).body(updated); } // Delete @DeleteMapping("/api/articles/{id}") public ResponseEntity<Article> delete(@PathVariable Long id) { // λμ μ°ΎκΈ° Article target = articleRepository.findById(id).orElse(null); // μλͺ»λ μμ² μ²λ¦¬ if (target == null){ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); } // λμ μμ articleRepository.delete(target); // λ°μ΄ν° λ°ν return ResponseEntity.status(HttpStatus.OK).build(); }
[μ€νλ§ λΆνΈ μ λ¬Έ 20] αα ₯αα ΅αα ³ αα ¨αα ³αΌαα ͺ αα ³α α ’α«αα ’α¨αα §α«
μλΉμ€: 컨νΈλ‘€λ¬μ 리νμ§ν°λ¦¬ μ¬μ΄μ μμΉνλ κ³μΈ΅, μ²λ¦¬ μ
무μ μμλ₯Ό μ΄κ΄
νΈλμμ
: μλΉμ€μ μ
무 μ²λ¦¬λ νΈλμ μ
λ¨μλ‘ μ§νλ¨, λͺ¨λ μ±κ³΅ν΄μΌνλ μΌλ ¨μ κ³Όμ μ λ»ν¨
λ‘€λ°±: νΈλμμ
μ€ν¨ μ μ§ν μ΄κΈ° λ¨κ³λ‘ λ리λ κ²
@Service: ν΄λΉ μ΄λ
Έν
μ΄μ
μ΄ μ μΈλ ν΄λμ€λ₯Ό μλΉμ€λ‘ μΈμ, μλΉμ€ κ°μ²΄ μμ±
@Transaction: ν΄λΉ μ΄λ
Έν
μ΄μ
μ΄ μ μΈλ λ©μλλ₯Ό νΈλμμ
μΌλ‘ λ¬Άμ, μ΄λ κ² νΈλμμ
μΌλ‘ λ¬ΆμΈ λ©μλλ μ²μλΆν° λκΉμ§ μμ ν μ€νλκ±°λ μμ μ€νλμ§ μκ±°λ λ μ€ νλλ‘ λμ. μ€κ°μ μ€ν¨νλ©΄ λ‘€λ°±ν΄ μ²μμνλ‘ λλμκ°κΈ° λλ¬Έ
-
컨νΈλ‘€λ¬ κ°λ¨νκ² νν κ°λ₯
package com.example.firstproject.api; import com.example.firstproject.dto.ArticleForm; import com.example.firstproject.entity.Article; import com.example.firstproject.repository.ArticleRepository; import com.example.firstproject.service.ArticleService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @Slf4j @RestController //RestAPIμ© μ»¨νΈλ‘€λ¬, λ°μ΄ν°(json)μ λ°ν public class ArticleApiController { @Autowired // DI μμ±, κ°μ²΄λ₯Ό κ°μ Έμ μ°κ²° private ArticleService articleService; // Get @GetMapping("/api/articles") public List<Article> index() { return articleService.index(); } @GetMapping("/api/articles/{id}") public Article index(@PathVariable Long id) { return articleService.show(id); } // Post @PostMapping("api/articles") public ResponseEntity<Article> create(@RequestBody ArticleForm dto) { Article created = articleService.create(dto); return (created != null) ? ResponseEntity.status(HttpStatus.OK).body(created) : ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } // Patch @PatchMapping("api/articles/{id}") public ResponseEntity<Article> update(@PathVariable Long id, @RequestBody ArticleForm dto) { Article updated = articleService.update(id, dto); return (updated != null) ? ResponseEntity.status(HttpStatus.OK).body(updated) : ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } // Delete @DeleteMapping("/api/articles/{id}") public ResponseEntity<Article> delete(@PathVariable Long id) { Article deleted = articleService.delete(id); return (deleted!=null) ? ResponseEntity.status(HttpStatus.OK).build() : ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } // νΈλμμ -> μ€ν¨ -> λ‘€λ°±! @PostMapping("/api/transaction-test") public ResponseEntity<List<Article>> transactionTest(@RequestBody List<ArticleForm> dtos) { List<Article> createdList = articleService.createArticles(dtos); return (createdList != null) ? ResponseEntity.status(HttpStatus.OK).body(createdList) : ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } }
-
μλΉμ€ κ³μΈ΅μ νν
package com.example.firstproject.service; import com.example.firstproject.dto.ArticleForm; import com.example.firstproject.entity.Article; import com.example.firstproject.repository.ArticleRepository; import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; @Slf4j @Service // μλΉμ€ μ μΈ(μλΉμ€ κ°μ²΄λ₯Ό μ€νλ§λΆνΈμ μμ±) public class ArticleService { @Autowired private ArticleRepository articleRepository; public List<Article> index() { return articleRepository.findAll(); } public Article show(Long id) { return articleRepository.findById(id).orElse(null); } public Article create(ArticleForm dto) { Article article = dto.toEntity(); if (article.getId()!=null) { return null; } return articleRepository.save(article); } public Article update(Long id, ArticleForm dto) { // 1. μμ μ© μν°ν° μμ± Article article = dto.toEntity(); log.info("id: {}, article: {}", id, article.toString()); // 2. λμ μν°ν°λ₯Ό μ‘°ν Article target = articleRepository.findById(id).orElse(null); // 3. μλͺ»λ μμ² μ²λ¦¬(λμμ΄ μκ±°λ idκ° λ€λ₯Έ κ²½μ°) if (target == null || id != article.getId()) { // 400, μλͺ»λ μμ² μλ΅ log.info("μλͺ»λ μμ²! id: {}, article: {}", id, article.toString()); return null; } // 4. μ λ°μ΄νΈ target.patch(article); // λκ° μμ± μνκ³ μμ ν μ κ·Έ μ μν κ·Έλλ‘ μ μ§λλλ‘ Article updated = articleRepository.save(target); return updated; } public Article delete(Long id) { // λμ μ°ΎκΈ° Article target = articleRepository.findById(id).orElse(null); // μλͺ»λ μμ² μ²λ¦¬ if (target == null){ return null; } // λμ μμ ν λ°ν articleRepository.delete(target); return target; } @Transactional public List<Article> createArticles(List<ArticleForm> dtos) { // dto λ¬Άμμ entity λ¬ΆμμΌλ‘ λ³ν List<Article> articleList = dtos.stream() .map(dto -> dto.toEntity()) .collect(Collectors.toList()); // entity λ¬Άμμ DBλ‘ μ μ₯ articleList.stream() .forEach(article -> articleRepository.save(article)); // κ°μ μμΈ λ°μ articleRepository.findById(-1L).orElseThrow( () -> new IllegalArgumentException("κ²°μ μ€ν¨!") ); // κ²°κ³Όκ° λ°ν return articleList; } }
ν
μ€νΈ:νλ‘κ·Έλ¨μ νμ§ κ²μ¦μ μν¨, μλλλ‘ νλ‘κ·Έλ¨μ΄ λμνλμ§ νμΈνλ κ²
ν
μ€νΈμΌμ΄μ€:μ±κ³΅κ³Ό μ€ν¨λ‘ λλ¨, 쑰건μ λ°λΌ λ€μν κ²½μ°λ‘ μμ±λ μ μμ
TDD:ν
μ€νΈλ₯Ό ν΅ν μ½λ κ²μ¦κ³Ό 리ν©ν°λ§μ κΈ°λ°μΌλ‘ ν κ°λ° λ°©λ²λ‘ μΈ ν
μ€νΈ μ£Όλ κ°λ°. ν
μ€νΈ μ½λλ₯Ό λ§λ ν μ΄λ₯Ό ν΅κ³Όνλ μ΅μνμ μ½λλΆν° μμν΄ μ μ§μ μΌλ‘ μ½λλ₯Ό κ°μ λ° νμ₯ν΄λκ°λ κ°λ°λ°©μ
μ±κ³΅νλ©΄ 리ν©ν°λ§, μ€ν¨νλ©΄ λλ²κΉ
@SpringBootTest: μ€νλ§ λΆνΈ νκ²½κ³Ό μ°λλ ν
μ€νΈλ₯Ό μν μ΄λ
Έν
μ΄μ
@Transactional:λ°μ΄ν° μ‘°ν μΈμ κ²½μ°(μμ±, λ³κ²½, μμ )μλ νΈλμμ
μ²λ¦¬λ₯Ό ν΅ν΄ λ‘€λ°±νλλ‘ ν΄μΌ ν¨
[μ€νλ§ λΆνΈ μ λ¬Έ 22] αα ’αΊαα ³α― αα ¦α«αα ΅αα ΅αα ͺ α α ΅αα ‘αα ΅αα ₯α α ΅(feat. ν μ€νΈ)
μΌλλ€ κ΄κ³:νλμ κ²μκΈμ μλ§μ λκΈμ΄ λ¬λ¦Ό
λ€λμΌ κ΄κ³:μ¬λ¬ λκΈμ΄ νλμ κ°μκΈμ λ¬λ¦Ό
PK, λνν€:idμ κ°μ΄ μμ μ λννλ μμ±
FK, μΈλν€:μ°κ΄ λμμ κ°λ¦¬ν€λ μμ± (ν΄λΉ λκΈμ΄ μ΄λ€ κ²μκΈμ λ¬λ¦° κ²μΈμ§ μ μ μμ)

λ€μ΄ν°λΈ 쿼리 λ©μλ: 쿼리λ₯Ό λ©μλλ‘ μμ±, μ§μ μμ±ν SQL쿼리λ₯Ό 리νμ§ν°λ¦¬ λ©μλλ‘ μ€νν μ μκ² ν΄μ€
@Queryμ΄λ Έν μ΄μ μ΄μ© -> νΉμ κ²μκΈμ λͺ¨λ λκΈ μ‘°νμ μ¬μ©
@Query(value =
"SELECT * " +
"FROM comment " +
"WHERE article_id = :articleId",
nativeQuery = true)
List<Comment> findbyArticleId(Long articleID);- orm.xml νμΌ μ΄μ© -> νΉμ λλ€μμ λͺ¨λ λκΈ μ‘°νμ μ¬μ©
@DataJpaTest: JPAμ μ°λν ν
μ€νΈλ₯Ό μ§ννλ μ΄λ
Έν
μ΄μ
, μ΄λ₯Ό ν΅ν΄ 리νμ§ν°λ¦¬μ μν°ν° λ±μ κ°μ²΄λ₯Ό ν
μ€νΈ μ½λμμ μ¬μ©ν μ μμ
@DisplayName: ν
μ€νΈ μ΄λ¦μ λΆμΌ λ μ¬μ©, κΈ°λ³Έ ν
μ€νΈ μ΄λ¦μ λ©μλ μ΄λ¦μ λ°λΌκ°μ§λ§ λ©μλ μ΄λ¦μ κ·Έλλ‘ λ μ± ν
μ€νΈ μ΄λ¦μ λ°κΎΈκ³ μΆμλ μ΄ μ΄λ
Έν
μ΄μ
μ¬μ©. @Display("ν
μ€νΈ_κ²°κ³Όμ_보μ¬μ€_μ΄λ¦)
@JoinColumn: ν΄λΉ μν°ν°μ μΈλν€λ₯Ό μμ±νλ μ΄λ
Έν
μ΄μ
, name μμ±μΌλ‘ λ§€νν μΈλν€ μ΄λ¦ μ§μ @JoinColumn(name="μΈλν€_μ΄λ¦")



