뷰템플릿을 만드는 도구=뷰 템플릿 엔진
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(); }
서비스: 컨트롤러와 리파지터리 사이에 위치하는 계층, 처리 업무의 순서를 총괄
트랜잭션: 서비스의 업무 처리는 트랜젝션 단위로 진행됨, 모두 성공해야하는 일련의 과정을 뜻함
롤백: 트랜잭션 실패 시 진행 초기 단계로 돌리는 것
@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:데이터 조회 외의 경우(생성, 변경, 삭제)에는 트랜잭션 처리를 통해 롤백하도록 해야 함
일대다 관계:하나의 게시글에 수많은 댓글이 달림
다대일 관계:여러 댓글이 하나의 개시글에 달림
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="외래키_이름")



