An open API service indexing awesome lists of open source software.

https://github.com/likelasttime/board


https://github.com/likelasttime/board

java spring-boot

Last synced: 2 months ago
JSON representation

Awesome Lists containing this project

README

          

# ๐Ÿ“ŒCRUD ๊ฒŒ์‹œํŒ
> SpringBoot์™€ Spring Data JPA๋ฅผ ์ด์šฉํ•˜์—ฌ ์›น ๊ฒŒ์‹œํŒ ๊ตฌํ˜„

![first](https://user-images.githubusercontent.com/46569105/144240175-0aab13f9-3425-44c8-ad7c-06e66dc6e69e.png)

## ๐Ÿ“š 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
![์ตœ์‹  dbdiagram](https://user-images.githubusercontent.com/46569105/186899577-3384f079-78f2-4fbf-af96-080bbd8f2fde.png)


## [๐Ÿ” ](#-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. ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ
![save](https://user-images.githubusercontent.com/46569105/184127699-5f931f92-0c2a-46fb-bb46-c8f19ff18eb7.gif)

์ œ๋ชฉ, ๋‚ด์šฉ์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.
์ž‘์„ฑ์ž๋Š” ๋กœ๊ทธ์ธํ•œ ์œ ์ € 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. ์ „์ฒด ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ ๋ฐ ์ƒ์„ธ๋ณด๊ธฐ

![์ƒ์„ธ๋ณด๊ธฐ](https://user-images.githubusercontent.com/46569105/184129386-b7ada09e-8874-4a1d-9527-9b5c2dea5ccb.gif)

๊ฒŒ์‹œ๊ธ€ ๋ฆฌ์ŠคํŠธ์—์„œ ๊ฒŒ์‹œ๊ธ€ ๋ฒˆํ˜ธ, ์ œ๋ชฉ, ์ž‘์„ฑ์ž, ์ž‘์„ฑ์ผ์ž, ์กฐํšŒ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
์ƒ๋‹จ ์ขŒ์ธก์—๋Š” ์ด ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ๋‚˜ํƒ€๋ƒˆ์Šต๋‹ˆ๋‹ค.

### [๐Ÿ” ](#-3-2-์ „์ฒด-๊ฒŒ์‹œ๊ธ€-์กฐํšŒ-๋ฐ-์ƒ์„ธ๋ณด๊ธฐ)3-3. ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •

![์ตœ์‹  ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •](https://user-images.githubusercontent.com/46569105/184324886-67211ee5-ac8a-4454-a6ee-59fcbc71036f.gif)

JS๋กœ ์ˆ˜์ • ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ๋ฉ”์‹œ์ง€๋ฅผ ๋„์›๋‹ˆ๋‹ค.
๊ฒŒ์‹œ๊ธ€์„ ์ˆ˜์ •ํ›„ ๊ฒŒ์‹œ๊ธ€ ๋ฆฌ์ŠคํŠธ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

### [๐Ÿ” ](#-3-3-๊ฒŒ์‹œ๊ธ€-์ˆ˜์ •)3-4. ๊ธ€ ์‚ญ์ œ

![์‚ญ์ œ](https://user-images.githubusercontent.com/46569105/184129956-b53b8f91-836b-4730-be46-dd343635767c.gif)

์‚ญ์ œ ํ›„ ๊ฒ€์ƒ‰์„ ํ†ตํ•ด ํ™•์ธํ•˜๋Š” ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.
์ƒ์„ธ๋ณด๊ธฐ์—์„œ ํ•ด๋‹น ๊ธ€์„ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
id๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ฒŒ์‹œ๊ธ€์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.

### [๐Ÿ” ](#-3-4-๊ธ€-์‚ญ์ œ)3-5. Pagination

![ํŽ˜์ด์ง•๋„ค์ด์…˜](https://user-images.githubusercontent.com/46569105/144242014-c0a69817-b38e-426a-ae9c-eddec62d250c.gif)

ํ•œ ํŽ˜์ด์ง€์— ์ตœ๋Œ€ 5๊ฐœ์˜ ๊ฒŒ์‹œ๊ธ€์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
์ตœ๊ทผ์— ๋“ฑ๋กํ•œ ๊ฒŒ์‹œ๊ธ€ ์ˆœ์œผ๋กœ ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌํ–ˆ์Šต๋‹ˆ๋‹ค.
์ด์ „, ๋‹ค์Œ ๋ฒ„ํŠผ์œผ๋กœ ์™ผ์ชฝ ๋˜๋Š” ์˜ค๋ฅธ์ชฝ ์ด๋™์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
๋งจ ์ฒ˜์Œ, ๋งจ ๋์— ์žˆ์„ ๊ฒฝ์šฐ ๊ฐ๊ฐ ์ด์ „, ๋‹ค์Œ ๋ฒ„ํŠผ์ด ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.

### [๐Ÿ” ](#-3-5-pagination)3-6. ๊ฒ€์ƒ‰

![๊ฒ€์ƒ‰](https://user-images.githubusercontent.com/46569105/144238073-0f3656e4-c749-4fea-a4ab-e5e02270b55a.gif)

์ œ๋ชฉ, ์ž‘์„ฑ์ž, ๋‚ด์šฉ์„ ๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„ ์—†์ด ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ „์ฒด ๊ฒŒ์‹œ๊ธ€์„ ์กฐํšŒํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๊ฒ€์ƒ‰์–ด๊ฐ€ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋“ค์–ด์˜ต๋‹ˆ๋‹ค.
๊ฒ€์ƒ‰ํ•œ ๋‚ด์šฉ๋“ค๋„ ํŽ˜์ด์ง•๋„ค์ด์…˜๋ฉ๋‹ˆ๋‹ค.
```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๋ฅผ ์ด์šฉํ•ด์„œ ํ‘œํ˜„์‹์ด ์ฐธ์ผ ๋•Œ๋งŒ ๋กœ๊ทธ์ธ๊ณผ ํšŒ์›๊ฐ€์ž… ๋˜๋Š” ๋กœ๊ทธ์•„์›ƒ ๋ฒ„ํŠผ์ด ๋‚˜ํƒ€๋‚˜๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค.

![login](https://user-images.githubusercontent.com/46569105/159108736-221c1824-2e31-4630-9eb1-a1770a864b5e.gif)

๋กœ๊ทธ์ธ

![logout](https://user-images.githubusercontent.com/46569105/159108738-5d82dfb3-f8cf-4ba6-87f3-c99cd159ad20.gif)

๋กœ๊ทธ์•„์›ƒ

![id](https://user-images.githubusercontent.com/46569105/159108778-a2576ace-412f-48ad-96cc-6b16ab364631.gif)

์•„์ด๋”” ์ฐพ๊ธฐ
```java
Optional findByNameAndEmailAndPhone(String name, String email, String phone);
```
์ด๋ฆ„, ๋ฉ”์ผ, ์—ฐ๋ฝ์ฒ˜๊ฐ€ ์ผ์น˜ํ•˜๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.
์•„์ด๋””๋ฅผ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์— ์ž๋™์œผ๋กœ ์ฑ„์›Œ์ค๋‹ˆ๋‹ค.
์ฐพ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์—†์œผ๋ฉด "๊ฐ€์ž…๋œ ์•„์ด๋””๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค" ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.

![๋น„๋ฒˆform](https://user-images.githubusercontent.com/46569105/159108977-5a2d78b5-6c66-422b-8f39-b7bfc7898b1b.png)
๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ
```java
boolean existsByUsernameAndEmail(String username, String mail);
```
์•„์ด๋””, ๋ฉ”์ผ์„ ์ด์šฉํ•ด ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.
๊ฐ€์ž…๋œ ๋ฉ”์ผ๋กœ ์ž„์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐœ์†กํ•ฉ๋‹ˆ๋‹ค.
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ž„์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.
![๋ฉ”์ผ](https://user-images.githubusercontent.com/46569105/159108979-85f7941c-184d-49d5-aad8-9ed3093559cd.png)

![ํšŒ์›๊ฐ€์ž…](https://user-images.githubusercontent.com/46569105/149871638-80fc7ad3-18e4-479b-8bbf-dafcf76fda68.png)
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. ๋Œ“๊ธ€
๋Œ“๊ธ€ ๋“ฑ๋ก/์ˆ˜์ •/์‚ญ์ œ ํ›„ ๋น„๋™๊ธฐ๋กœ ๋Œ“๊ธ€ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
#### ๋Œ“๊ธ€ ๋“ฑ๋ก
![๋Œ“๊ธ€ ๋“ฑ๋ก](https://user-images.githubusercontent.com/46569105/188293089-c1c7a07c-e013-4323-a165-617c1f4ab786.gif)
Ajax๋ฅผ ์ด์šฉํ•ด ๋น„๋™๊ธฐ์‹์œผ๋กœ ๋Œ“๊ธ€์„ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

๋Œ“๊ธ€ ์ˆ˜์ •๊ณผ ์‚ญ์ œ๋Š” Modal์„ ๋„์›Œ์„œ ํ•ฉ๋‹ˆ๋‹ค.
๋Œ“๊ธ€์„ ์“ด ํšŒ์›์—๊ฒŒ๋งŒ Modal์„ ๋„์šฐ๋Š” ์•„์ด์ฝ˜์ด ๋ณด์ž…๋‹ˆ๋‹ค.

#### ๋Œ“๊ธ€ ์ˆ˜์ •
![๋Œ“๊ธ€ ์ˆ˜์ •](https://user-images.githubusercontent.com/46569105/188293090-f82ca696-5089-4bb2-80b5-e64dc061b709.gif)
Entity์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ž๋™์œผ๋กœ ๋ฐ˜์˜ํ•˜๋Š” ๊ธฐ๋Šฅ Dirty Checking์„ ์ด์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
๋Œ“๊ธ€์„ ์ˆ˜์ •ํ•˜๊ธฐ ์ „ ์•Œ๋ฆผ์ฐฝ์„ ๋„์šฐ๊ณ  true๋ฅผ ๋ฆฌํ„ด๋ฐ›์œผ๋ฉด form์„ submitํ•ฉ๋‹ˆ๋‹ค.

#### ๋Œ“๊ธ€ ์‚ญ์ œ
![๋Œ“๊ธ€ ์‚ญ์ œ](https://user-images.githubusercontent.com/46569105/188293092-69146f90-2b60-432a-8a91-407d77c624dd.gif)
๋Œ“๊ธ€์„ ์‚ญ์ œํ•˜๊ธฐ ์ „ ์•Œ๋ฆผ์ฐฝ์„ ๋„์šฐ๊ณ  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๊ฐœ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
![์‹ค์‹œ๊ฐ„์ธ๊ธฐ](https://user-images.githubusercontent.com/46569105/180739211-4a73a97d-2e60-4091-9549-cb11a14a6421.gif)
10์œ„์— ์žˆ๋˜ '์—ด์ด ์˜ฌ๋ผ์š”' ๊ฒŒ์‹œ๊ธ€์ด ์กฐํšŒ ํ›„ 8์œ„๋กœ ์˜ฌ๋ผ๊ฐ‘๋‹ˆ๋‹ค.
![10๋ฒˆ](https://user-images.githubusercontent.com/46569105/180740224-968d37c1-371b-4bfd-853a-37b0c84c51b4.png)
Before
![8๋ฒˆ](https://user-images.githubusercontent.com/46569105/180740214-d9b16a37-976a-4dc0-b996-41470438db43.png)
After

![์บ์‹œ ์‚ญ์ œ](https://user-images.githubusercontent.com/46569105/180976912-62b5468e-1957-49eb-bc71-d68cbf83d63b.gif)
10์œ„์— ์žˆ๋˜ '๋ฌด๋”์œ„' ๊ฒŒ์‹œ๊ธ€์„ ์‚ญ์ œํ•˜๋Š” ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.
์ •์ƒ์ ์œผ๋กœ ์‚ญ์ œ๋˜์–ด ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒŒ์‹œ๊ธ€, ์ „์ฒด ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ์—์„œ ๊ฒ€์ƒ‰์‹œ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
![์‚ญ์ œ์ „](https://user-images.githubusercontent.com/46569105/180977628-a6976202-41a5-485a-badb-12dc929e67fd.png)
Before
![์‚ญ์ œ ํ›„](https://user-images.githubusercontent.com/46569105/180977911-e11bec9c-e305-4ceb-b47a-0cce50f96982.png)
After

![์ตœ์‹  ์บ์‹œ ์ˆ˜์ •](https://user-images.githubusercontent.com/46569105/181681578-8549a76b-8184-4c34-b4b9-6acded16ee34.gif)
1์œ„์— ์žˆ๋˜ '์ผ์ƒ' ๊ฒŒ์‹œ๊ธ€์„ '์ผ์ƒ(์ˆ˜์ •)'์œผ๋กœ ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
![์ผ์ƒ](https://user-images.githubusercontent.com/46569105/181682121-5137adaa-8bee-4c0f-bcf6-fd1f4740fac7.png)
Before
![์ผ์ƒ ์ˆ˜์ •](https://user-images.githubusercontent.com/46569105/181682126-e2f0c52a-b0ba-4baa-b28a-973dd6c2e536.png)
After

## [๐Ÿ” ](#-3-11-์‹ค์‹œ๊ฐ„-์ธ๊ธฐ-๊ฒŒ์‹œ๊ธ€)3-12. ๋‹ค์ค‘ ํŒŒ์ผ ์—…๋กœ๋“œ
![๋‹ค์ค‘ ํŒŒ์ผ ์—…๋กœ๋“œ ์˜ˆ์‹œ](https://user-images.githubusercontent.com/46569105/184126337-2024f355-7fe9-4d4a-aa1f-8d807f68d681.gif)
๋‹ค์ค‘ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๋Š” ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.
![์—…๋กœ๋“œํŒŒ์ผ ๋ฐ๋ฒ ](https://user-images.githubusercontent.com/46569105/184131861-9f48915b-b728-4b32-9840-61612b171852.png)
ํŒŒ์ผ์— ๊ด€๋ จ๋œ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” UPLOAD_FILE ํ…Œ์ด๋ธ”์ž…๋‹ˆ๋‹ค.
origin_file_name ํ•„๋“œ๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ ์—…๋กœ๋“œํ•œ ์›๋ณธ ํŒŒ์ผ๋ช…์ž…๋‹ˆ๋‹ค.
uuid_file_name์€ UUID๋ฅผ ์ด์šฉํ•ด ๋กœ์ปฌ์— ์ €์žฅ๋œ ํŒŒ์ผ๋ช…์ž…๋‹ˆ๋‹ค.

## [๐Ÿ” ](#-3-12-๋‹ค์ค‘-ํŒŒ์ผ-์—…๋กœ๋“œ)3-13. ๊ณต์œ 
์นด์นด์˜คํ†ก ๋ฉ”์‹œ์ง€ ๊ณต์œ  API๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
![๊ณต์œ ](https://user-images.githubusercontent.com/46569105/188299253-fde26642-81be-41cb-81c3-a15ddb665bb8.gif)
์นด์นด์˜คํ†ก ์•„์ด์ฝ˜์„ ํด๋ฆญํ•˜๋ฉด ์นด์นด์˜คํ†ก์ด ์‹คํ–‰๋˜๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํ™”๋ฉด ๋…นํ™”์—๋Š” ๋‹ด๊ธฐ์ง€ ์•Š์•„์„œ ์•„๋ž˜ ์‚ฌ์ง„์„ ์ฒจ๋ถ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
![์นด์นด์˜คํ†ก ๊ณต์œ ](https://user-images.githubusercontent.com/46569105/188299294-c1165e2f-7530-4d7c-a0ef-9bb60bec3bd7.jpg)

## [๐Ÿ” ](#-3-13-๊ณต์œ )3-14. ์†Œ์…œ ๋กœ๊ทธ์ธ
![์†Œ์…œ ๋กœ๊ทธ์ธ](https://user-images.githubusercontent.com/46569105/195322364-95be5cf9-dd5b-4595-912c-00ff43a7ade0.png)
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/) ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ผ€์ด์Šค๋ฅผ ๋„ฃ์–ด๊ฐ€๋ฉด์„œ ์ •๊ทœ ํ‘œํ˜„์‹์„ ์ œ๋Œ€๋กœ ์ตํžˆ๊ธฐ ์œ„ํ•ด ๊ณต๋ถ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
๋งž๊ฒŒ ์ž‘์„ฑํ•œ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋“ค๋”๋ผ๋„ ๋‹ค์‹œ ํ™•์ธํ•ด ๋ณด๋Š” ๊ฒŒ ์ค‘์š”ํ•˜๋‹ค๊ณ  ๋А๊ผˆ์Šต๋‹ˆ๋‹ค.