https://github.com/likelasttime/board
https://github.com/likelasttime/board
java spring-boot
Last synced: 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/likelasttime/board
- Owner: likelasttime
- Created: 2021-05-18T13:05:21.000Z (about 5 years ago)
- Default Branch: master
- Last Pushed: 2022-10-18T10:28:44.000Z (almost 4 years ago)
- Last Synced: 2025-01-20T12:25:16.384Z (over 1 year ago)
- Topics: java, spring-boot
- Language: Java
- Homepage:
- Size: 468 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# ๐CRUD ๊ฒ์ํ
> SpringBoot์ Spring Data JPA๋ฅผ ์ด์ฉํ์ฌ ์น ๊ฒ์ํ ๊ตฌํ

## ๐ ToC
1. [๊ธฐ์ ์คํ](#-1-๊ธฐ์ -์คํ)
2. [ERD](#-2-erd)
2. [ํต์ฌ ๊ธฐ๋ฅ](#-3-ํต์ฌ-๊ธฐ๋ฅ)
3. [ํธ๋ฌ๋ธ ์ํ
](#-4-ํธ๋ฌ๋ธ-์ํ
)
4. [ํ๋ก์ ํธ๋ฅผ ํตํด ๋ฐฐ์ด ์ ](#-5-ํ๋ก์ ํธ๋ฅผ-ํตํด-๋ฐฐ์ด-์ )
## [๐ ](#-toc)1. ๊ธฐ์ ์คํ
|Spring Boot|Spring Boot 2.5.2๋ก ๊ฐ๋ฐํ์ต๋๋ค.|
|:----------:|:----------------------------------|
|**Java**|11.0.9 ๋ฒ์ ์ ์ฌ์ฉํ์ต๋๋ค.|
|**Spring Data JPA**|๋ฉ์๋ ์ด๋ฆ๋ง์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ์ต๋๋ค.|
|**MySQL**|์์์ ์ธ ๋ฐ์ดํฐ ์ฌ์ฉ์ ์ํด RDBMS์ธ MySQL์ ์ฌ์ฉํ์ต๋๋ค.|
|**JUnit5**|Given, When, Then ํ์์ผ๋ก ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ์ต๋๋ค.|
|**Redis**|spring-boot-starter-data-redis 2.7.0 ๋ฒ์ ์ ์ฌ์ฉํ์ต๋๋ค.|
|**Thymeleaf**|server-side Java template engine์
๋๋ค.|
|**HTML**|ํ๋ฉด์ ๋ง๋ค๊ธฐ ์ํด ์ฌ์ฉํ์ต๋๋ค.|
|**Summernote**|๊ธ ์์ฑ/์์ ์ Editor๋ฅผ ๋ฃ์์ต๋๋ค.|
## [๐ ](#-1-๊ธฐ์ -์คํ)2. ERD

## [๐ ](#-2-erd)3. ํต์ฌ ๊ธฐ๋ฅ
- [์์ฑ](#-3-1-๊ฒ์๊ธ-์์ฑ)
- [์กฐํ / ์์ธ๋ณด๊ธฐ](#-3-2-์ ์ฒด-๊ฒ์๊ธ-์กฐํ-๋ฐ-์์ธ๋ณด๊ธฐ)
- [์์ ](#-3-3-๊ฒ์๊ธ-์์ )
- [์ญ์ ](#-3-4-๊ธ-์ญ์ )
- [Pagination](#-3-5-pagination)
- [๊ฒ์](#-3-6-๊ฒ์)
- [์กฐํ์](#-3-7-์กฐํ์)
- [์ธ์ฆ](#-3-8-์ธ์ฆ)
- [๋๊ธ](#-3-9-๋๊ธ)
- [์บ์](#-3-10-์บ์)
- [์ค์๊ฐ ์ธ๊ธฐ ๊ฒ์๊ธ](#-3-11-์ค์๊ฐ-์ธ๊ธฐ-๊ฒ์๊ธ)
- [๋ค์ค ํ์ผ ์
๋ก๋](#-3-12-๋ค์ค-ํ์ผ-์
๋ก๋)
- [๊ณต์ ](#-3-13-๊ณต์ )
- [์์
๋ก๊ทธ์ธ](#-3-14-์์
-๋ก๊ทธ์ธ)
### [๐ ](#-3-ํต์ฌ-๊ธฐ๋ฅ)3-1. ๊ฒ์๊ธ ์์ฑ

์ ๋ชฉ, ๋ด์ฉ์ ์
๋ ฅํฉ๋๋ค.
์์ฑ์๋ ๋ก๊ทธ์ธํ ์ ์ ID์
๋๋ค.
```java
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
@CreatedDate
@Column(updatable=false)
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime modifiedDate;
public LocalDateTime getCreatedDate() {
return createdDate;
}
public LocalDateTime getModifiedDate() {
return modifiedDate;
}
}
```
๊ธ์ ์์ฑํ๋ฉด ๋ฑ๋ก์ผ์, ์ต๊ทผ ์์ ํ ์ผ์๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ฉ๋๋ค.
createdDate ์์ฑ์ updatable-false๋ก ํด์ ๊ธ์ ์์ ํ๋๋ผ๋ ๋ณํ์ง ์๊ฒ ํ์ต๋๋ค.
updatable-false๋ฅผ ๋ฃ์ง ์์์ ๋๋ ๊ฒ์๊ธ ์์ ์ null์ด ๋๋ ์๋ฌ๊ฐ ๋ฐ์ํ์์ต๋๋ค.
### [๐ ](#-3-1-๊ฒ์๊ธ-์์ฑ)3-2. ์ ์ฒด ๊ฒ์๊ธ ์กฐํ ๋ฐ ์์ธ๋ณด๊ธฐ

๊ฒ์๊ธ ๋ฆฌ์คํธ์์ ๊ฒ์๊ธ ๋ฒํธ, ์ ๋ชฉ, ์์ฑ์, ์์ฑ์ผ์, ์กฐํ์๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
์๋จ ์ข์ธก์๋ ์ด ๊ฒ์๊ธ ์๋ฅผ ๋ํ๋์ต๋๋ค.
### [๐ ](#-3-2-์ ์ฒด-๊ฒ์๊ธ-์กฐํ-๋ฐ-์์ธ๋ณด๊ธฐ)3-3. ๊ฒ์๊ธ ์์

JS๋ก ์์ ๋ฒํผ์ ํด๋ฆญํ์ ๋ ๋ฉ์์ง๋ฅผ ๋์๋๋ค.
๊ฒ์๊ธ์ ์์ ํ ๊ฒ์๊ธ ๋ฆฌ์คํธ ํ๋ฉด์ผ๋ก ์ด๋ํฉ๋๋ค.
### [๐ ](#-3-3-๊ฒ์๊ธ-์์ )3-4. ๊ธ ์ญ์

์ญ์ ํ ๊ฒ์์ ํตํด ํ์ธํ๋ ์์์
๋๋ค.
์์ธ๋ณด๊ธฐ์์ ํด๋น ๊ธ์ ์ญ์ ํ ์ ์์ต๋๋ค.
id๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ฒ์๊ธ์ ์ญ์ ํฉ๋๋ค.
### [๐ ](#-3-4-๊ธ-์ญ์ )3-5. Pagination

ํ ํ์ด์ง์ ์ต๋ 5๊ฐ์ ๊ฒ์๊ธ์ ๋ํ๋
๋๋ค.
์ต๊ทผ์ ๋ฑ๋กํ ๊ฒ์๊ธ ์์ผ๋ก ๋ด๋ฆผ์ฐจ์ ์ ๋ ฌํ์ต๋๋ค.
์ด์ , ๋ค์ ๋ฒํผ์ผ๋ก ์ผ์ชฝ ๋๋ ์ค๋ฅธ์ชฝ ์ด๋์ด ๊ฐ๋ฅํฉ๋๋ค.
๋งจ ์ฒ์, ๋งจ ๋์ ์์ ๊ฒฝ์ฐ ๊ฐ๊ฐ ์ด์ , ๋ค์ ๋ฒํผ์ด ๋นํ์ฑํ๋ฉ๋๋ค.
### [๐ ](#-3-5-pagination)3-6. ๊ฒ์

์ ๋ชฉ, ์์ฑ์, ๋ด์ฉ์ ๋์๋ฌธ์ ๊ตฌ๋ถ ์์ด ๊ฒ์ํ ์ ์์ต๋๋ค.
์ ์ฒด ๊ฒ์๊ธ์ ์กฐํํ๋ ์ปจํธ๋กค๋ฌ์์ ๊ฒ์์ด๊ฐ ํ๋ผ๋ฏธํฐ๋ก ๋ค์ด์ต๋๋ค.
๊ฒ์ํ ๋ด์ฉ๋ค๋ ํ์ด์ง๋ค์ด์
๋ฉ๋๋ค.
```java
Page findByTitleContainingIgnoreCaseOrContentContainingIgnoreCaseOrAuthorContainingIgnoreCase(String title, String content, String author, Pageable pageable);
```
### [๐ ](#-3-6-๊ฒ์)3-7. ์กฐํ์
๊ฒ์๊ธ ์์ธ๋ณด๊ธฐ๋ฅผ ํ ๋๋ง ์กฐํ์๊ฐ 1์ฉ ์ฆ๊ฐํฉ๋๋ค.
```java
// ์กฐํ์ ์ฆ๊ฐ
public void updateView(Long id) {
Post post=postRepository.findById(id).get();
post.update(post.getTitle(), post.getContent(), post.getView()+1);
}
```
### [๐ ](#-3-7-์กฐํ์)3-8. ์ธ์ฆ
Spring Security๋ฅผ ์ด์ฉํด ๋ก๊ทธ์ธ, ๋ก๊ทธ์์์ ๊ตฌํํ์ต๋๋ค.
๋น๋ฐ๋ฒํธ๋ PasswordEncoder ํด๋์ค๋ฅผ ์ด์ฉํด ์ํธํ๋์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ฉ๋๋ค.
Thymeleaf์ sec:authorize๋ฅผ ์ด์ฉํด์ ํํ์์ด ์ฐธ์ผ ๋๋ง ๋ก๊ทธ์ธ๊ณผ ํ์๊ฐ์
๋๋ ๋ก๊ทธ์์ ๋ฒํผ์ด ๋ํ๋๊ฒ ํ์ต๋๋ค.

๋ก๊ทธ์ธ

๋ก๊ทธ์์

์์ด๋ ์ฐพ๊ธฐ
```java
Optional findByNameAndEmailAndPhone(String name, String email, String phone);
```
์ด๋ฆ, ๋ฉ์ผ, ์ฐ๋ฝ์ฒ๊ฐ ์ผ์นํ๋ ์ฌ์ฉ์๋ฅผ ์ฐพ์ต๋๋ค.
์์ด๋๋ฅผ ๋ก๊ทธ์ธ ํ์ด์ง์ ์๋์ผ๋ก ์ฑ์์ค๋๋ค.
์ฐพ๋ ์ฌ์ฉ์๊ฐ ์์ผ๋ฉด "๊ฐ์
๋ ์์ด๋๊ฐ ์์ต๋๋ค" ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํฉ๋๋ค.

๋น๋ฐ๋ฒํธ ์ฐพ๊ธฐ
```java
boolean existsByUsernameAndEmail(String username, String mail);
```
์์ด๋, ๋ฉ์ผ์ ์ด์ฉํด ์ฌ์ฉ์๋ฅผ ์ฐพ์ต๋๋ค.
๊ฐ์
๋ ๋ฉ์ผ๋ก ์์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ฐ์กํฉ๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ ๋น๋ฐ๋ฒํธ๋ ์์ ๋น๋ฐ๋ฒํธ๋ก ๋ณ๊ฒฝ๋ฉ๋๋ค.


form ํ๊ทธ์ onsubmit ์์ฑ์ ์ด์ฉํด ๋ชจ๋ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํต๊ณผํด์ผ ์๋ฒ์ ์ ์กํฉ๋๋ค.
ํ๋ฐํธ์๋์์๋ง ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ๋ฉด ๊ฐ๋ฐ์ ๋๊ตฌ๋ก ์ฝ๊ฒ ๋ซ์ ์ ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ , ํ๋ฐํธ์๋์์์ ์ ํจ์ฑ ๊ฒ์ฌ๋ UI/UX ์ญํ ์ด๊ธฐ ๋๋ฌธ์ ๋ฐฑ์๋์์๋ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํฉ๋๋ค.
์์ด๋, ์ด๋ฉ์ผ, ์ฐ๋ฝ์ฒ๋ ajax๋ฅผ ์ด์ฉํด ์๋ฒ์ ์ ์กํ๊ณ ์ค๋ณต ๊ฒ์ฌ๋ฅผ ํฉ๋๋ค.
```java
@PostMapping("/user/idCheck") // ์์ด๋ ์ค๋ณต ์ฒดํฌ
@ResponseBody
public boolean idCheck(@RequestParam("id") String id){
LOG.info("userIdCheck ์ง์
");
LOG.info("์ ๋ฌ๋ฐ์ id:"+id);
boolean flag=userService.userIdCheck(id);
LOG.info("ํ์ธ ๊ฒฐ๊ณผ:" + flag);
return flag;
}
@PostMapping("/user/emailCheck") // ์ด๋ฉ์ผ ์ค๋ณต ์ฒดํฌ
@ResponseBody
public boolean emailCheck(@RequestParam("email") String email){
LOG.info("userEmailCheck ์ง์
");
LOG.info("์ ๋ฌ๋ฐ์ email:"+email);
boolean flag=userService.userEmailCheck(email);
LOG.info("ํ์ธ ๊ฒฐ๊ณผ:"+flag);
return flag;
}
@PostMapping("/user/phoneCheck") // ์ ํ๋ฒํธ ์ค๋ณต ๊ฒ์ฌ
@ResponseBody
public boolean phoneCheck(@RequestParam("phone") String phone){
LOG.info("userPhoneCheck ์ง์
");
LOG.info("์ ๋ฌ๋ฐ์ ๋ฒํธ:"+phone);
boolean flag=userService.userPhoneCheck(phone);
LOG.info("ํ์ธ ๊ฒฐ๊ณผ:"+flag);
return flag;
}
```
```java
// ์ค๋ณต ์์ด๋ ์ฒดํฌ
public boolean userIdCheck(String user_id) {return userRepository.existsByUsername(user_id);}
// ์ค๋ณต ์ด๋ฉ์ผ ์ฒดํฌ
public boolean userEmailCheck(String email){return userRepository.existsByEmail(email);}
// ์ค๋ณต ํด๋ํฐ ๋ฒํธ ์ฒดํฌ
public boolean userPhoneCheck(String phone){return userRepository.existsByPhone(phone);}
```
์๋ฒ์์๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํฉ๋๋ค.
1. UserRequestDto์์ validation annotation์ ์ด์ฉํด ์๋์ ๊ฐ์ด ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํฉ๋๋ค.
๋๋ฉ์ธ์ ๋ณต์ก์ฑ์ ์ค์ด๊ธฐ ์ํด entity์์ validation์ ํ์ง ์๊ณ , UserRequestDto์์ ํ์ต๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ ๋ toEntity ๋ฉ์๋๋ฅผ ์ด์ฉํด UserRequestDto๋ฅผ User๋ก ๋ณํํฉ๋๋ค.
```java
@NotBlank(message="์ด๋ฆ์ ์์ฑํด์ฃผ์ธ์.")
@Pattern(regexp="^[๊ฐ-ํฃ]{2,5}$", message="2~5์์ ํ๊ธ๋ง ์
๋ ฅ๊ฐ๋ฅํฉ๋๋ค.")
private String name;
@NotBlank(message="์ ํ๋ฒํธ๋ฅผ ์์ฑํด์ฃผ์ธ์.")
@Pattern(regexp="^[0-9]{10,11}$", message="10~11์๋ฆฌ์ ์ซ์๋ง ์
๋ ฅ๊ฐ๋ฅํฉ๋๋ค.")
private String phone;
@NotBlank(message="๋ฉ์ผ์ ์์ฑํด์ฃผ์ธ์.")
@Email(message="๋ฉ์ผ์ ์์์ ์ง์ผ์ฃผ์ธ์.")
@Pattern(regexp="^[A-Za-z0-9_\\.\\-]+@[A-Za-z0-9\\-]+\\.[A-Za-z0-9\\-]+$"
, message="์์) ๋ฉ์ผ๋ช
@gmail.com")
private String email;
@NotBlank(message="์์ด๋๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.")
@Pattern(regexp="^(?=.*[0-9]+)[a-zA-Z][a-zA-Z0-9]{5,10}$",
message="์๋ฌธ์/๋๋ฌธ์, ์ซ์๊ฐ ํฌํจ๋ 5~10์์ ์์ด๋์ฌ์ผ ํฉ๋๋ค.")
private String username;
@NotBlank(message="๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.")
@Pattern(regexp="(?=.*[0-9])(?=.*[a-z])(?=.*\\W)(?=\\S+$).{8,20}",
message="์๋ฌธ์, ์ซ์, ํน์๋ฌธ์๊ฐ ๋ชจ๋ ํฌํจ๋ 8~20์์ ๋น๋ฐ๋ฒํธ์ฌ์ผ ํฉ๋๋ค.")
private String password;
```
2. Validator๋ฅผ ๊ตฌํํ UserValidator์์ ์ด๋ฉ์ผ/์์ด๋/์ฐ๋ฝ์ฒ ์ค๋ณต ๊ฒ์ฌ, ์์ด๋์ ์ฒซ ๋ฌธ์ ๊ฒ์ฌ๋ฅผ ํฉ๋๋ค.
```java
@Override
public void validate(Object object, Errors errors){
UserRequestDto userRequestDto=(UserRequestDto) object;
if(userRepository.existsByEmail(userRequestDto.getEmail())){ // ์ด๋ฉ์ผ ์ค๋ณต
errors.rejectValue("email", "invalid.email"
, new Object[]{userRequestDto.getEmail()}
, "์ด๋ฏธ ์ฌ์ฉ์ค์ธ ์ด๋ฉ์ผ์
๋๋ค.");
}
if(!Pattern.matches(pattern, userRequestDto.getUsername())){ // ์ฒซ ๋ฌธ์๊ฐ ์๋ฌธ์๊ฐ ์๋๋ฉด
errors.rejectValue("username", "invalid.username"
, new Object[]{userRequestDto.getUsername()}
, "์ฒซ ๋ฌธ์๋ ์๋ฌธ์์ผ ํฉ๋๋ค.");
}
if(userRepository.existsByUsername(userRequestDto.getUsername())){ // ์์ด๋ ์ค๋ณต
errors.rejectValue("username", "invalid.username"
, new Object[]{userRequestDto.getUsername()}
, "์ด๋ฏธ ์ฌ์ฉ์ค์ธ ์์ด๋์
๋๋ค.");
}
if(userRepository.existsByPhone(userRequestDto.getPhone())){ // ์ฐ๋ต์ฒ ์ค๋ณต
errors.rejectValue("phone", "invalid.phone"
, new Object[]{userRequestDto.getPhone()}
, "์ด๋ฏธ ๊ฐ์
๋ ๋ฒํธ์
๋๋ค.");
}
}
```
## [๐ ](#-3-8-์ธ์ฆ)3-9. ๋๊ธ
๋๊ธ ๋ฑ๋ก/์์ /์ญ์ ํ ๋น๋๊ธฐ๋ก ๋๊ธ ๋ฆฌ์คํธ๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
#### ๋๊ธ ๋ฑ๋ก

Ajax๋ฅผ ์ด์ฉํด ๋น๋๊ธฐ์์ผ๋ก ๋๊ธ์ ๋ฑ๋กํฉ๋๋ค.
๋๊ธ ์์ ๊ณผ ์ญ์ ๋ Modal์ ๋์์ ํฉ๋๋ค.
๋๊ธ์ ์ด ํ์์๊ฒ๋ง Modal์ ๋์ฐ๋ ์์ด์ฝ์ด ๋ณด์
๋๋ค.
#### ๋๊ธ ์์

Entity์ ๋ณ๊ฒฝ์ฌํญ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์๋์ผ๋ก ๋ฐ์ํ๋ ๊ธฐ๋ฅ Dirty Checking์ ์ด์ฉํ์ต๋๋ค.
๋๊ธ์ ์์ ํ๊ธฐ ์ ์๋ฆผ์ฐฝ์ ๋์ฐ๊ณ true๋ฅผ ๋ฆฌํด๋ฐ์ผ๋ฉด form์ submitํฉ๋๋ค.
#### ๋๊ธ ์ญ์

๋๊ธ์ ์ญ์ ํ๊ธฐ ์ ์๋ฆผ์ฐฝ์ ๋์ฐ๊ณ true๋ฅผ ๋ฆฌํด๋ฐ์ผ๋ฉด POST ๋ฐฉ์์ผ๋ก ์์ฒญ์ ๋ณด๋
๋๋ค.
## [๐ ](#-3-9-๋๊ธ)3-10. ์บ์
์ ์ฒด ๊ฒ์๊ธ ์กฐํ, ์กฐํ์๋ฅผ ๊ธฐ์ค์ผ๋ก 10๊ฐ์ ์ธ๊ธฐ ๊ฒ์๊ธ ์กฐํ์ ์บ์๋ฅผ ์ ์ฉํ์ต๋๋ค.
์กฐํํ ๋ ๋จผ์ ์บ์์ ๋ฐ์ดํฐ๊ฐ ์๋์ง ํ์ธํฉ๋๋ค.
์๋ค๋ฉด DB์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ , ์บ์์ ์ ์ฅํฉ๋๋ค.
```java
//์ ์ฒด ๊ฒ์๊ธ ์กฐํ(์บ์)
public List findAllByCache(){
TreeMap treeMap=new TreeMap(Collections.reverseOrder());
treeMap.putAll(redisTemplate.opsForHash().entries("findAll"));
List postResponseDtoList=treeMap.values().stream().collect(Collectors.toList());
return postResponseDtoList;
}
```
์ ์ฒด ๊ฒ์๊ธ ์กฐํ๋ Hash๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
Hash์ field๋ ๊ฒ์๊ธ id, value๋ PostResponseDto ๊ฐ์ฒด์
๋๋ค.
Hash์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ TreeMap์ ๋ด์ต๋๋ค.
TreeMap์ Collections.reverseOrder()์ ๋ฃ์ด์ key๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ด๋ฆผ์ฐจ์ ์ ๋ ฌํฉ๋๋ค.
```java
redisTemplate.opsForHash().put("findAll", id, postResponseDto);
```
๊ธ์ ์์ฑํ๋ฉด ์บ์์๋ ์ ์ฅํฉ๋๋ค.
```java
redisTemplate.opsForHash().put("rankByHash", stringId, postResponseDto);
redisTemplate.opsForHash().put("findAll", stringId, postResponseDto);
```
๊ธ์ ์์ ํ๋ฉด ์บ์์๋ ๋ฐ์ํฉ๋๋ค.
```java
redisTemplate.opsForZSet().add(key, id, dto.getView());
redisTemplate.opsForHash().put("rankByHash", id, dto);
redisTemplate.opsForHash().put("findAll", id, dto);
```
๊ฒ์๊ธ ์์ธ๋ณด๊ธฐ๋ฅผ ํ๋ฉด ์กฐํ์๊ฐ ์ฆ๊ฐํ๋ฏ๋ก ์บ์์๋ ๋ฐ์ํฉ๋๋ค.
```java
redisTemplate.opsForZSet().remove("findByRank", postId);
redisTemplate.opsForHash().delete("rankByHash", postId);
redisTemplate.opsForHash().delete("findAll", postId);
```
๊ฒ์๊ธ์ ์ญ์ ํ๋ฉด ์บ์์์๋ id๋ฅผ ์ฐพ์์ ์ญ์ ํฉ๋๋ค.
## [๐ ](#-3-10-์บ์)3-11. ์ค์๊ฐ ์ธ๊ธฐ ๊ฒ์๊ธ
์ค์๊ฐ ์ธ๊ธฐ ๊ฒ์๊ธ ์กฐํ๋ Sorted set๊ณผ Hash๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
Sorted Set์ member๋ ๊ฒ์๊ธ id, score๋ ์กฐํ์์
๋๋ค.
Hash์ field๋ ๊ฒ์๊ธ id, value๋ PostResponseDto ๊ฐ์ฒด์
๋๋ค.
์ต๊ทผ์ ์กฐํ๋ ๊ฒ์๊ธ ์ค์์ ์์๋ฅผ ๊ณ์ฐํฉ๋๋ค.
๋ฐ๋ผ์, ๊ฒ์๊ธ ์์ธ๋ณด๊ธฐ๋ฅผ ํ ๋ Sorted set๊ณผ Hash์ ๋ด์ต๋๋ค.
```java
Set> typedTuples = zSetOperations.reverseRangeWithScores(key, 0, 9);
```
score๋ฅผ ๋ด๋ฆผ์ฐจ์ ์ ๋ ฌํด์ 10๊ฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.

10์์ ์๋ '์ด์ด ์ฌ๋ผ์' ๊ฒ์๊ธ์ด ์กฐํ ํ 8์๋ก ์ฌ๋ผ๊ฐ๋๋ค.

Before

After

10์์ ์๋ '๋ฌด๋์' ๊ฒ์๊ธ์ ์ญ์ ํ๋ ํ
์คํธ์
๋๋ค.
์ ์์ ์ผ๋ก ์ญ์ ๋์ด ์ค์๊ฐ ์ธ๊ธฐ ๊ฒ์๊ธ, ์ ์ฒด ๊ฒ์๊ธ ์กฐํ์์ ๊ฒ์์ ๋ํ๋์ง ์์ต๋๋ค.

Before

After

1์์ ์๋ '์ผ์' ๊ฒ์๊ธ์ '์ผ์(์์ )'์ผ๋ก ์์ ํ์ต๋๋ค.

Before

After
## [๐ ](#-3-11-์ค์๊ฐ-์ธ๊ธฐ-๊ฒ์๊ธ)3-12. ๋ค์ค ํ์ผ ์
๋ก๋

๋ค์ค ํ์ผ์ ์
๋ก๋ํ๋ ์์์
๋๋ค.

ํ์ผ์ ๊ด๋ จ๋ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ UPLOAD_FILE ํ
์ด๋ธ์
๋๋ค.
origin_file_name ํ๋๋ ํด๋ผ์ด์ธํธ์์ ์
๋ก๋ํ ์๋ณธ ํ์ผ๋ช
์
๋๋ค.
uuid_file_name์ UUID๋ฅผ ์ด์ฉํด ๋ก์ปฌ์ ์ ์ฅ๋ ํ์ผ๋ช
์
๋๋ค.
## [๐ ](#-3-12-๋ค์ค-ํ์ผ-์
๋ก๋)3-13. ๊ณต์
์นด์นด์คํก ๋ฉ์์ง ๊ณต์ API๋ฅผ ์ฌ์ฉํ์ต๋๋ค.

์นด์นด์คํก ์์ด์ฝ์ ํด๋ฆญํ๋ฉด ์นด์นด์คํก์ด ์คํ๋๊ณ ๋ฉ์์ง๋ฅผ ๋ณด๋ผ ์ ์์ต๋๋ค.
ํ๋ฉด ๋
นํ์๋ ๋ด๊ธฐ์ง ์์์ ์๋ ์ฌ์ง์ ์ฒจ๋ถํ์ต๋๋ค.

## [๐ ](#-3-13-๊ณต์ )3-14. ์์
๋ก๊ทธ์ธ

UserDetails, OAuth2User๋ฅผ ์์๋ฐ์ PrincipalDetails ํด๋์ค๋ฅผ ์์ฑํ์ต๋๋ค.
PrincipalDetails๋ ์ผ๋ฐ ๋ก๊ทธ์ธ ๋๋ OAuth2 ๋ก๊ทธ์ธ์ ํ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ด์ต๋๋ค.
์ด๋ฉ์ผ์ด DB์ ์์ผ๋ฉด ํด๋น ์ฌ์ฉ์๋ฅผ DB์ ์ ์ฅํฉ๋๋ค.
## [๐ ](#-3-ํต์ฌ-๊ธฐ๋ฅ)4. ํธ๋ฌ๋ธ ์ํ
### 4-1. ์บ์์ ์๋ ๋ฐ์ดํฐ๊ฐ ์ต์ ์ํ๊ฐ ์๋ ๋ฌธ์
์กฐํ๋ฅผ ํ๋ ๋ฉ์๋์ @Cacheable ๋ฉ์๋๋ฅผ ๋ถ์์ต๋๋ค.
๊ฒ์๊ธ์ ๋ฑ๋ก, ์์ , ์ญ์ ๋ฅผ ํ๋ฉด ์บ์์ ๋ฐ์์ด ๋์ง ์์์ต๋๋ค.
์ฒซ ๋ฒ์งธ๋ก ์ด ๋ฐฉ๋ฒ์ @CachePut ์ด๋
ธํ
์ด์
์ฌ์ฉ์
๋๋ค.
@CachePut์ ์บ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฑ๋กํ๊ธฐ ๋๋ฌธ์ ์บ์์ ๋ฐ์ดํฐ๊ฐ ์ต์ ์ํ๋ก ์ ์ง๋ฉ๋๋ค.
ํ์ง๋ง, ์บ์๋ฅผ ์ฌ์ฉํ ์ด์ ๋ฅผ ์๊ฐํด๋ณด์์ ๋ @CachePut์ ์ฌ์ฉํ ๊ฒ์ ์ ์ ํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์๋์์ต๋๋ค.
๋ ๋ฒ์งธ๋ก ์ด ๋ฐฉ๋ฒ์ ๋ฑ๋ก, ์์ , ์ญ์ ํ ๋ @CacheEvict๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์
๋๋ค.
์ด ๋ฐฉ๋ฒ์ผ๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ต๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์๋ ๋ฐ์ดํฐ์ ์บ์์ ์๋ ๋ฐ์ดํฐ๊ฐ ๋ฌ๋ผ์ง ๋ ์บ์๋ฅผ ์ญ์ ํฉ๋๋ค.
์กฐํํ ๋ ์บ์์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฏ๋ก ๋ฉ์๋๋ฅผ ์คํํ๊ณ , ์บ์์ ์ ์ฅํฉ๋๋ค.
### 4-2. ์บ์ ์ด๋
ธํ
์ด์
์ ์ ์ฉํ ๋ฉ์๋์ ๋ฐํ๊ฐ์ด ๋ฆฌ์คํธ๋ก ์ ์ฅ๋์ง ์์
๋ฆฌ์คํธ๋ฅผ ์บ์์ ์ ์ฅํ๊ณ , ์บ์์์ ํด๋น key์ ํ์
์ ์กฐํํด๋ณด๋ ๋ฆฌ์คํธ๊ฐ ์๋ ๋ฌธ์์ด์ด์์ต๋๋ค.
RedisTemplate ํด๋์ค๋ฅผ ์ด์ฉํด ์ํ๋ ๋ฐ์ดํฐ ํ์
์ ๋ง๊ฒ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ณ ์ ์ฅํ์ต๋๋ค.
### 4-3. ์บ์์ Entity๋ฅผ ์ ์ฅํด์ Infinite recursion์ด ๋ฐ์
POST ํ
์ด๋ธ๊ณผ COMMENT ํ
์ด๋ธ์ 1:M์ผ๋ก ์ฐ๊ด๊ด๊ณ๊ฐ ์์ด์ Infinite recursion ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค.
์ฒซ ๋ฒ์งธ๋ก ์ด ๋ฐฉ๋ฒ์ @JsonManagedReference, @JsonBackReference๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
POST์ comment์ @JsonManagedReference๋ฅผ ๋ถ์์ต๋๋ค.
COMMENT์ post์ @JsonBackReference๋ฅผ ๋ถ์์ต๋๋ค.
์ด๋ฒ์๋ M:M ๊ด๊ณ์ธ USER์ ROLE ํ
์ด๋ธ์์ Infinite recursion ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค.
@JsonIgnore๋ฅผ ์ฌ์ฉํ๋ ํด๊ฒฐ๋ฐฉ๋ฒ๋ ์์์ง๋ง, ์ ์ ๊ฒฝ์ฐ์๋ ์ฌ์ฉํ๊ธฐ์ ๋ถ์ ์ ํ์ต๋๋ค.
๋ ๋ฒ์งธ๋ก ์ด ๋ฐฉ๋ฒ์ ์บ์์ DTO๋ฅผ ์ ์ฅํ๋ ๊ฒ ์
๋๋ค.
์ด ๋ฐฉ๋ฒ์ผ๋ก Infinite recursion ์๋ฌ๋ฅผ ํด๊ฒฐํ์ต๋๋ค.
๋ฉ์๋ ๋ฐํ ๊ฐ์ด ์บ์์ ์ ์ฅ๋ ์ ์๋๋ก ์ํฐํฐ๋ฅผ DTO๋ก ๋ณํํด์ ๋ฐํํ์ต๋๋ค.
```java
@Cacheable(value="findByRank")
public List findByRank(){
List postList=postRepository.findTop10ByOrderByViewDesc();
List postDto=postList.stream().map(PostResponseDto::new).collect(Collectors.toList());
return postDto;
}
```
### 4-4. Spring Security ๋๋ฌธ์ ์ปจํธ๋กค๋ฌ๊ฐ ๋์ํ์ง ์์
ajax๋ฅผ ์ด์ฉํด์ ์์ด๋ ์ค๋ณต ํ์ธ์ ํ๋ ๊ธฐ๋ฅ์ ๋ง๋ค๋ค๊ฐ ๋ฌธ์ ๊ฐ ์๊ฒผ์ต๋๋ค.
์ค๋ณต๋์ง ์์ ์์ด๋๋ฅผ ์
๋ ฅํด๋ ์ปจํธ๋กค๋ฌ๊ฐ ๋์ํ์ง ์๋ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ ํ์ต๋๋ค.
์๋ฒ์์ ๋ฐ์ ๊ฐ์ ํ๋ฐํธ์์ ์ถ๋ ฅํ๋ฉด ์๋ฑํ๊ฒ ์ ์ฒด ํ์๊ฐ์
HTML ์์ค ์ฝ๋๊ฐ ๋์์ต๋๋ค.
์๋ฌด๋ฆฌ ์๋ฐ์คํฌ๋ฆฝํธ ๋ฌธ๋ฒ, ์ปจํธ๋กค๋ฌ๋ฅผ ๋ด๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
๋ฌธ์ ์ ์์ธ์ Spring Security์ ์์์ต๋๋ค.
Spring Security ๋๋ฌธ์ post ๋ฐฉ์์ผ๋ก ์์ฒญํ '/user/idCheck'์ ๋ชป ๊ฐ๊ณ , ํ์๊ฐ์
ํ์ด์ง๋ฅผ ๋ฆฌํดํ๋ ์ปจํธ๋กค๋ฌ๋ก ๊ฐ์ต๋๋ค.
WebSecurityConfigurerAdapter๋ฅผ ์์๋ฐ์ WebSecurityConfig์์ ์ธ์ฆ ์์ด๋ ์ ๊ทผํ๋๋ก '/user/idCheck'๋ฅผ ์ถ๊ฐํด ํด๊ฒฐํ์ต๋๋ค.
์ด๋ฉ์ผ, ์ฐ๋ฝ์ฒ ์ค๋ณต ๊ฒ์ฌ๋ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํด์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ํด๊ฒฐํ์ต๋๋ค.
```java
@Override
protected void configure(HttpSecurity http) throws Exception{
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/user/joinForm", "/user/idCheck", "/user/join", "/user/emailCheck", "/user/phoneCheck").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/user/login")
.permitAll()
.and()
.logout()
.permitAll();
}
```
### 4-5. ๊ฒ์๊ธ ์์ ์ put์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ
์ฒ์์ post๋ก ์์ฒญ์ ๋ฐ์ ์ฒ๋ฆฌํ๋ @PostMapping์ ์ฌ์ฉํ์ต๋๋ค.
๊ธ์ ์์ ํ ๋๋ post๋ณด๋ค ๋ฉฑ๋ฑ์ฑ์ด ์๋ put์ผ๋ก ์์ฒญ์ ๋ฐ์์ผ ํ๋ค๋ ์๊ฒ ๋์์ต๋๋ค.
@PostMapping์ @PutMapping์ผ๋ก ๋ฐ๊พธ๊ธฐ๋ง ํ๋ฉด ๋ ์ค ์์์ง๋ง, ๋ค์๊ณผ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค.
```
DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]
```
application.properties์ @PutMapping, @DeleteMapping๊ณผ ๊ฐ์ annotation์ ์ธ ์ ์๊ฒ ๋ค์ ์ฝ๋๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
```
spring.mvc.hiddenmethod.filter.enabled=true
```
๊ทธ๋๋ ์ ๋ผ์ html ์ชฝ ์ฝ๋๋ฅผ ์ดํด๋ดค์ต๋๋ค.
์จ๊ฒจ์ง ์
๋ ฅ ํ๋๋ฅผ form์ ์ถ๊ฐํ์ต๋๋ค.
```html
```
### 4-6. ์ฐ์์ ์ธ ๊ฒ์๊ธ ๋ฒํธ
์ญ์ ๊ธฐ๋ฅ์ ํ
์คํธํ๋ค ๋ณด๋ ๊ฒ์๊ธ ๋ฒํธ๊ฐ ๋ถ์ฐ์์ ์ผ๋ก ์ถ๋ ฅ๋์ต๋๋ค.
์ญ์ ํด๋ 1๋ถํฐ n๊น์ง ์ฐ์์ ์ผ๋ก ๋ฒํธ๊ฐ ์ถ๋ ฅ๋์ด์ผ ํ๋ค๊ณ ์๊ฐํ์ต๋๋ค.
๊ฐ์ฅ ๋จผ์ ํ ์๊ฐ์ ์ญ์ ํ ๋๋ง๋ค ๋ฐ์ดํฐ๋ฒ ์ด์ค์ id ๊ฐ๋ค์ ์ฌ์กฐ์ ํด์ฃผ๋ ๊ฒ์
๋๋ค.
์ค์ ๋ก ์ด๋ ๊ฒ ์ฒ๋ฆฌํ๋ ๊ฒ์ผ๊น? ํ๊ณ ์ฐพ์๋ณด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ id ๊ฐ์ ์ฌ์กฐ์ ํ ๊ฒฝ์ฐ ๋ฌธ์ ์ ์ด ๋ง๋ค๋ ๊ฒ์ ์๊ฒ ๋์์ต๋๋ค.
๋ ๋ฒ์งธ๋ก ์๊ฐํ ๋ฐฉ๋ฒ์ 1๋ถํฐ ์ ์ฒด ๊ฒ์๊ธ ์ n๊น์ง ์ถ๋ ฅํ๋ ๊ฒ์ด์์ต๋๋ค.
Thymeleaf ๋ฌธ์๋ฅผ ์ฐพ์๋ณด๋ th:each๋ฅผ ์ธ ๋ ์ธ๋ฑ์ค๊ฐ๋ ๋ฝ์๋ผ ์ ์์์ต๋๋ค.
```html
```
### 4-7. ๊นํ๋ธ ์ปค๋ฐ reset ํ ๋ณ๊ฒฝ
ํ๋ฒ์ ํ์ผ์ ์ค์๋ก ๋นผ๋จน๊ณ ์๊ฒฉ repository์ ์ปค๋ฐ์ ์ฌ๋ฆฐ ์ ์ด ์์ต๋๋ค.
```
git reset --hard HEAD~1
git push -f origin main
```
์์ ์ฝ๋๋ก ์ปค๋ฐ์ ํ๊ธฐ ์ ์ผ๋ก ๋์๊ฐ์ต๋๋ค.
ํ์ง๋ง, ๋ก์ปฌ์์๋ ์์
ํ ์ฝ๋๊ฐ ์ฌ๋ผ์ก์ต๋๋ค.
IntelliJ์ Local History๋ฅผ ํตํด ์ปค๋ฐํ ํ์ผ๋ค์ ๋ณต์ํ์ต๋๋ค.
```
git reflog
git reset --hard commit_id
```
์ญ์ ๋ ์ปค๋ฐ ์์ด๋๋ฅผ ์์๋ด๊ณ , ๋ค์ ์ญ์ ํ๋ ์ปค๋ฐ์ผ๋ก ๋์์์ต๋๋ค.
ํ์ผ์ ์ถ๊ฐ ํ ์ ์์ ์ผ๋ก ์ปค๋ฐ์ ํ ์ ์์์ต๋๋ค.
reset์ ํด๋ ์์ ํ ์ญ์ ๋๋ ๊ฒ ์๋๋ผ HEAD๊ฐ ๊ฐ๋ฆฌํค๋ ์ปค๋ฐ์ด ๋ณํ๋ ๊ฒ์ ๋ฐฐ์ ์ต๋๋ค.
## [๐ ](#-4-ํธ๋ฌ๋ธ-์ํ
)5. ํ๋ก์ ํธ๋ฅผ ํตํด ๋ฐฐ์ด ์
### 5-1. ์คํ๋ง MVC ํจํด
Model, View, Controller์ ์๋ฏธ์ ํ๋ฆ์ ์ดํดํ์ต๋๋ค.
Controller๋ HTTP ์์ฒญ์ ์ฒ๋ฆฌํฉ๋๋ค. ์ถ๋ ฅํ View ์ด๋ฆ์ ๋ฐํํฉ๋๋ค.
Model์ View์์ ์ถ๋ ฅํ ๋ฐ์ดํฐ๋ฅผ ๋ด๊ณ ์์ต๋๋ค.
View๋ ์ถ๋ ฅ๋ ํ๋ฉด์
๋๋ค.
Controller, Model, View ์์ผ๋ก ์งํ๋ฉ๋๋ค.
HTTP ๋ฉ์๋ GET, POST, PUT, DELETE์ ์ฐจ์ด๋ฅผ ์์์ต๋๋ค.
GET์ ์กฐํ, POST๋ ์์ฑ, PUT์ ์์ , DELETE๋ ์ญ์ ํ ๋ ์ฌ์ฉํฉ๋๋ค.
POST์ PUT์ด ๋น์ทํ ๊ฒ ์๋๊ฐ ํด์ ์ฐพ์๋ณด๋ '๋ฉฑ๋ฑ์ฑ'์ ์๊ฒ ๋์์ต๋๋ค.
๋ฉฑ๋ฑ์ฑ์ ์ฌ๋ฌ ๋ฒ ๊ฐ์ ์์ฒญ์ ๋ณด๋ด๋๋ผ๋ ํ ๋ฒ๋ง ์์ฒญ๋ฐ์ ๊ฒ๊ณผ ๊ฐ์ด ์ฒ๋ฆฌํฉ๋๋ค.
POST๋ ๋ฉฑ๋ฑ์ฑ์ด ์์ด์ ์์ ์์
์ ์ ํฉํ์ง ์์ต๋๋ค.
### 5-2. ์คํ๋ง DI
ํ๋ ์ฃผ์
์ ์์ฑ์ ์ฃผ์
์ผ๋ก ๋ณ๊ฒฝํ์ต๋๋ค.
๋ถํธ, ํ์ ์์กด ๊ด๊ณ์ ์์ฑ์ ์ฃผ์
์ ์ฃผ๋ก ์ด๋ค๋ ๊ฒ์ ์์์ต๋๋ค.
ํ๋ ์ฃผ์
์ ์ธ๋ถ์์ ๋ณ๊ฒฝ์ด ๋ถ๊ฐ๋ฅํด์ ํ
์คํธ ํ๊ธฐ ํ๋ค๋ค๋ ๋จ์ ์ด ์์ต๋๋ค.
private final ๋ฉค๋ฒ๋ณ์๋ฅผ lombok์ @RequiredArgsConstructor๋ก ์์ฑ์ ์ฃผ์
์ ํ์ต๋๋ค.
### 5-3. Git
ํ๋ก์ ํธ์ ๋ณ๊ฒฝ ์ฌํญ์ด ์๊ธธ ๋ ๋ง๋ค Git์ ์ฌ์ฉํด์ ๋ฒ์ ๊ด๋ฆฌ๋ฅผ ํ๋ฉด์ Git์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ตํ์ต๋๋ค.
๋ก์ปฌ ์ ์ฅ์์ ์๊ฒฉ ์ ์ฅ์์ ์์ค ์ฝ๋๊ฐ ๋ค๋ฅด๋ฉด pull์ ํด์์ผ ํฉ๋๋ค.
add๋ก ํ์ผ์ ์ถ๊ฐํฉ๋๋ค.
status๋ก Staged(commit์ผ๋ก ์ ์ฅ์์ ๊ธฐ๋กํ ์ํ)์ ์๋ ํ์ผ์ ํ์ธํฉ๋๋ค.
commit ๋ฉ์์ง๋ฅผ ๊ฐ์กฐ์์ผ๋ก ์์ฑํฉ๋๋ค. ๋ณธ๋ฌธ์ ์์ฑํ๊ณ ์ถ์ ๋๋ Vim์ ์ฌ์ฉํฉ๋๋ค.
push๋ก ์๊ฒฉ ์ ์ฅ์์ ์ฌ๋ฆฝ๋๋ค.
reset์ผ๋ก HEAD๊ฐ ๊ฐ๋ฆฌํค๋ ์ปค๋ฐ์ ์ง์ ํด์ ์ปค๋ฐ์ ์ญ์ ํ๋ ๋ฐฉ๋ฒ์ ์์์ต๋๋ค.
### 5-4. Spring Data JPA
๋ฉ๋ชจ๋ฆฌ ๊ธฐ๋ฐ์ repository๋ฅผ ๋ง๋๋ ๊ฒ๋ถํฐ ์์ํด์ JPA, Spring Data JPA ์์ผ๋ก ๊ตฌํํด๋ดค์ต๋๋ค.
๋จ์ํ Spring Data JPA๋ถํฐ ์ฌ์ฉํ๋๋ผ๋ฉด ๋ฐ์ดํฐ ์ ๊ทผ ๊ณ์ธต์ ๋ํ ๊ธฐ๋ณธ์ ์์ง ๋ชปํ๊ณ ์ผ์ ๊ฒ์
๋๋ค.
์ฒ์์๋ JPA์ Spring Data JPA์ ์ฐจ์ด๋ฅผ ์ ๋ชฐ๋์์ต๋๋ค.
Spring Data JPA๋ ๋ฐ์ดํฐ ์ ๊ทผ ๊ณ์ธต์ ๊ฐ๋ฐํ ๋ ์ธํฐํ์ด์ค๋ง ์์ฑํด๋ ๊ฐ๋ฐํ ์ ์๊ฒ ํด์ฃผ๋ ์ด์ ์ด ์์ต๋๋ค.
์ด๋ฌํ ์ด์ ๋๋ฌธ์ Spring Data JPA๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
JpaRepository๋ฅผ ์์๋ฐ๋ ์ธํฐํ์ด์ค๋ฅผ ์์ฑํ๊ณ , ๋ฉ์๋ ์ด๋ฆ์ผ๋ก ์ฟผ๋ฆฌ๊ฐ ์๋์ผ๋ก ์์ฑ๋๋ ์ ์ด ๋งค์ฐ ํฅ๋ฏธ๋ก์ ์ต๋๋ค.
### 5-5. ํ
์คํธ
JUnit5๋ฅผ ์ด์ฉํด Given, When, Then ํ์์ผ๋ก ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์์์ต๋๋ค.
ํ
์คํธ๋ ๊ฐ๊ฐ ๋
๋ฆฝ์ ์ผ๋ก ์คํ ํ๊ธฐ ์ํด @AfterEach๋ก ํ
์คํธ๋ง๋ค ๋ฉ๋ชจ๋ฆฌ DB๋ฅผ ๋น์ ์ต๋๋ค.
ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ ์๋ฒ๋ฅผ ์คํํ๋ ์๊ฐ์ ์ ์ฝํ ์ ์์ด์ ์ข์์ต๋๋ค.
### 5-6. ์ ๊ท ํํ์
์ ๊ท ํํ์์ ์๊ณ ์๋ค๊ณ ์๊ฐํ๋๋ฐ, ํ์๊ฐ์
์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๊ตฌํํ๋ฉด์ ๋ชฐ๋๋ ์ ์ ๋ฐ๊ฒฌํ์ต๋๋ค.
์๋ฌธ์/๋๋ฌธ์, ์ซ์ ์กฐํฉ์ ์์ด๋๋ฅผ ๋ง๋ค๋ ค๊ณ ํ์ต๋๋ค.
๊ฒ์์ ํตํด ์์ด๋ ์ ๊ท ํํ์์ ์ฐพ์๋ณด๋ ^[A-za-z0-9]{4,12}$๊ฐ ์์์ต๋๋ค.
ํ์ง๋ง, ์ด๋ ๊ฒ ํ๋ฉด ์๋ฌธ์/๋๋ฌธ์, ์ซ์ ์กฐํฉ์ด ์ ๋ฉ๋๋ค.
์ซ์๋ง 4์ ์ด์ 12์ ์ดํ ์
๋ ฅํด๋ ํต๊ณผ๋ฉ๋๋ค.
์ ๋ ์๋ฌธ์/๋๋ฌธ์๋ก ์์ํ๊ณ ์๋ฌธ์+์ซ์ ์กฐํฉ์ธ ์ ๊ท ํํ์์ ์ผ์ต๋๋ค.
```java
^(?=.*[0-9]+)[a-zA-Z][a-zA-Z0-9]{5,10}$
```
์ ๊ท ํํ์์ ํ
์คํธํ ์ ์๋ ์ฌ์ดํธ์์(https://regexr.com/) ์ฌ๋ฌ๊ฐ์ง ์ผ์ด์ค๋ฅผ ๋ฃ์ด๊ฐ๋ฉด์ ์ ๊ท ํํ์์ ์ ๋๋ก ์ตํ๊ธฐ ์ํด ๊ณต๋ถํ์ต๋๋ค.
๋ง๊ฒ ์์ฑํ ๊ฒ ๊ฐ๋ค๊ณ ์๊ฐ์ด ๋ค๋๋ผ๋ ๋ค์ ํ์ธํด ๋ณด๋ ๊ฒ ์ค์ํ๋ค๊ณ ๋๊ผ์ต๋๋ค.