๐œŒ Spring Boot ์˜ˆ์ œ1 (๊ฒŒ์‹œ๊ธ€ ๊ด€๋ฆฌ) ใƒฝ(โœฟ๏พŸโ–ฝ๏พŸ)ใƒŽ

@Autowiredยท2022๋…„ 1์›” 13์ผ
1

Spring Boot

๋ชฉ๋ก ๋ณด๊ธฐ
9/11

Spring Boot์—์„œ ๊ฒŒ์‹œ๊ธ€ ๊ด€๋ฆฌ ํ•ด๋ณด๊ธฐ! ใƒฝ(โœฟ๏พŸโ–ฝ๏พŸ)ใƒŽ

  • ์‚ฌ์šฉ IDE : IntelliJ IDEA Ultimate
  • ์‚ฌ์šฉ DB : MySQL
  • ์‚ฌ์šฉ ์–ธ์–ด & SDK : Java & Amazon correto 11
  • ์ •๋ฆฌ๋ณธ์ž…๋‹ˆ๋‹ค ์ฐธ๊ณ ์šฉ์œผ๋กœ๋งŒ ๋ด์ฃผ์„ธ์š” :D
  • ํšŒ์›๊ด€๋ฆฌ ์˜ˆ์ œ์ฒ˜๋Ÿผ ์ฒœ์ฒœํžˆ ์ง„ํ–‰ ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค! ๊ทธ๋Ÿผ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ๋งŒ๋“ค๋Ÿฌ ๊ฐ€์‹œ์ฃ !

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

  • ํ”„๋กœ์ ํŠธ ์ด๋ฆ„ : BoardProject

  • ๊ธฐ๋ณธ ํŒจํ‚ค์ง€ : com.icia.board

  • Type : Gradle

  • dependency : Spring web, lombok, Thyleaf, Validation, Spring Data Jpa, Mysql Driver

  • ์„ค์ • ํ›„ ์ƒ์„ฑ


์ดˆ๊ธฐ ์„ธํŒ…

build.gradle

  • ๋งจ ์ฒ˜์Œ์— dependency๋ฅผ ์„ค์ • ํ•  ๋•Œ ์ดˆ๋ฐ˜์— ํ•„์š”ํ•œ dependency๋Š” ์ „๋ถ€ ์ถ”๊ฐ€ํ–ˆ์œผ๋ฏ€๋กœ, ์„ค์ •ํ• ๊ฑฐ X

application.yml

# ํฌํŠธ๋ฒˆํ˜ธ๋Š” ์ „์—๋ž‘ ์•ˆ๊ฒน์น˜๊ฒŒ ์ž์œ ๋กญ๊ฒŒ ์“ฐ์„ธ์š”!
server:
  port: 8096

# DB์ ‘์† ์ •๋ณด
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springbootclass?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
    username: bootuser
    password: 1234
  # cache๊ฐ’์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒ ๋‹ค๋Š” ์˜๋ฏธ
  thymeleaf:
    cache: false

  # JPA ๊ด€๋ จ ์„ค์ •, datasource: ์œ„์น˜์™€ ๊ฐ™์€ ์œ„์น˜์— ์ž‘์„ฑํ•ด์ค˜์•ผํ•จ.
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    show-sql: true
    hibernate:
      ddl-auto: update
      # create : ์‹คํ–‰ํ•  ๋•Œ ๋งˆ๋‹ค ์ „์ฒด ์žฌ์‹œ์ž‘
      # update : ์‹คํ–‰ํ•  ๋•Œ table drop ์—†์ด ์‹คํ–‰.

BoardProject

  • ์ด๋ฒˆ ์‹œ๊ฐ„์— ๊ฒŒ์‹œ๊ธ€ ๊ด€๋ฆฌ ์˜ˆ์ œ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•  ๊ฒƒ
  1. ๊ธฐ๋ณธ๊ธฐ๋Šฅ

    • ๊ธฐ๋ณธ ์ฃผ์†Œ ์š”์ฒญ ์‹œ index.html ์ถœ๋ ฅ
    • MainController์—์„œ ๊ธฐ๋ณธ ์ฃผ์†Œ ์ฒ˜๋ฆฌ
  2. index.html

    • ๊ธ€์“ฐ๊ธฐ ํŽ˜์ด์ง€(/board/save), ๋ชฉ๋กํŽ˜์ด์ง€(/board/) ์š”์ฒญ ๋งํฌ ์กด์žฌ
  3. BoardController

    • ๊ธ€์ž‘์„ฑ ํŽ˜์ด์ง€ ์š”์ฒญ์ด ์˜ค๋ฉด ๊ธ€์ž‘์„ฑ ํŽ˜์ด์ง€ ์ถœ๋ ฅ
    • ๊ธ€์ž‘์„ฑ ํŽ˜์ด์ง€ ์œ„์น˜ : templates/board/save.html
  4. save.html

    • ๊ธ€์“ฐ๊ธฐ ํ•ญ๋ชฉ : ์ž‘์„ฑ์ž, ๋น„๋ฐ€๋ฒˆํ˜ธ, ์ œ๋ชฉ, ๋‚ด์šฉ
      ๊ฐ ํ•„๋“œ ์ด๋ฆ„ : boardWriter, boardPassword, boardTitle, boardContents
    • ๊ธ€์“ฐ๊ธฐ ํ•œ ๋‚ด์šฉ์€ BoardSaveDTO์— ๋‹ด์•„์„œ ์ปจํŠธ๋กค๋Ÿฌ๋กœ ์ „์†ก
  5. BoardSaveDTO

    • ํ•„๋“œ ์„ค์ •
    • String boardWriter
    • String boardPassword
    • String boardTitle
    • String boardContents
  6. BoardEntity

    • ์ปฌ๋Ÿผ ์„ค์ •
    • id : pk๋กœ ์ง€์ •
    • boardWriter
    • boardPassword
    • boardTitle
    • boardContents
    • boardDate : java.time.LocalDateTime์œผ๋กœ import
    • createTime : java.time.LocalDateTime์œผ๋กœ import
    • udparteTime :java.time.LocalDateTime์œผ๋กœ import
  7. BoardRepository

    • JpaRepository implementsํ•˜๊ธฐ

๊ฒŒ์‹œ๊ธ€ ์ €์žฅ

์œ„ ํ•ญ๋ชฉ ์ค‘ ๊ฐ„๋‹จํ•œ MainController, index ๋“ฑ์€ ๊ฑด๋„ˆ๋›ฐ๊ฒ ์Šต๋‹ˆ๋‹ค.
์ด๋ฒˆ ์‹œ๊ฐ„์—” Thymeleaf๋ฅผ ์ž์ฃผ ์‚ฌ์šฉํ•  ์˜ˆ์ •์ด๋‹ˆ ์ด ์  ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š” :D
๊ทธ๋Ÿผ Entity์—์„œ ์ปฌ๋Ÿผ ์„ค์ •์„ ์–ด๋–ป๊ฒŒ ํ–ˆ๋Š”์ง€๋งŒ ๋ณด๊ณ , ์ปจํŠธ๋กค๋Ÿฌ๋กœ ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.


Entity

๊ธด๊ธ‰์ˆ˜์ •! (22.01.13.๋ชฉ 19:00 ~ 20:00)

  • BaseEntity ์ถ”๊ฐ€

  • ๊ธฐ์กด Entity๋ฅผ ์ƒ์†๋ฐ›๋Š” ์ž์‹ํด๋ž˜์Šค๋กœ ์„ค์ •

  • boardDate ์‚ญ์ œ, createTime, updateTime ์ถ”๊ฐ€

  • ๊ธ‰ํ•˜๊ฒŒ ๋ณ€๊ฒฝํ•œ ์  ์‚ฌ๊ณผ๋“œ๋ฆฝ๋‹ˆ๋‹ค ( ; ฯ‰ ; )

@Entity
@Getter
@Setter
@Table(name = "board_table")
// BaseEntity๋ฅผ ์ƒ์†๋ฐ›๋Š” ์ž์‹ ํด๋ž˜์Šค๋กœ ์„ค์ •
public class BoardEntity extends BaseEntity {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "board_id")
	private Long id;

	@Column
	private String boardWriter;

	@Column
	private String boardPassword;

	@Column
	private String boardTitle;

	@Column
	private String boardContents;

	// ๊ธฐ์กด boardDate ์‚ญ์ œ
	// @Column
	// private LocalDateTime boardDate;
    
	// createTime, updateTime ์ถ”๊ฐ€๋Š” BaseEntity์—์„œ ์ถ”๊ฐ€
    
}

์ €๋Š” ์œ„์™€ ๊ฐ™์ด ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
Column ์ง€์ • ํ›„ ์•„๋ฌด๊ฒƒ๋„ ์ง€์ •์„ ์•ˆํ•ด์ค€๋‹ค๋ฉด String์€ varchar(255)๋กœ ์ƒ์„ฑ์ด ๋ฉ๋‹ˆ๋‹ค.
์ด๋Š” ํšŒ์›๊ด€๋ฆฌ ์˜ˆ์ œ์—์„œ ์„ค๋ช…ํ•ด๋†จ์œผ๋‹ˆ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.
๊ทธ๋Ÿผ Entity๋ฅผ ๋งŒ๋“ค์—ˆ์œผ๋‹ˆ Controller๋กœ ๋„˜์–ด๊ฐ€๋ ค๊ณ  ํ–ˆ์ง€๋งŒ ๊ธด๊ธ‰ ๋‚ด์šฉ ์ˆ˜์ •์ด ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค...
Entity๋ฅผ ๋ณด์กฐํ•ด์ฃผ๋Š” ํด๋ž˜์Šค๋ฅผ ํ•˜๋‚˜ ๋” ์ƒ์„ฑํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.ใƒฝ(โœฟ๏พŸโ–ฝ๏พŸ)ใƒŽ

BaseEntity

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
// ์ž์‹ Entity ์ฆ‰ ์ƒ์†๋ฐ›์€ Entity ํด๋ž˜์Šค๊ฐ€ ๋ฌด์Šจ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋Š”์ง€ ๊ฐ์ง€ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜
@Getter
public abstract class BaseEntity {
	// abstract : ์ถ”์ƒ ํด๋ž˜์Šค

	@CreationTimestamp // create ์ฆ‰ insert๊ฐ€ ์ˆ˜ํ–‰๋œ ์‹œ๊ฐ„
	@Column(updatable = false) // update ํ•  ๋•Œ ๊ฐ’์ด ๋“ค์–ด๊ฐ€์ง€ ์•Š๊ฒŒ
	private LocalDateTime createTime; // insert ์ˆ˜ํ–‰ํ•œ ์‹œ๊ฐ„.

	@UpdateTimestamp // update ์ฆ‰ update๊ฐ€ ์ˆ˜ํ–‰๋œ ์‹œ๊ฐ„
	@Column(insertable = false) // insert ํ•  ๋•Œ ๊ฐ’์ด ๋“ค์–ด๊ฐ€์ง€ ์•Š๊ฒŒ
	private LocalDateTime updateTime; // update ์ˆ˜ํ–‰ํ•œ ์‹œ๊ฐ„.

}
  • ๊ธด๊ธ‰ ์ˆ˜์ • ์‚ฌ์œ 
    ๊ธฐ์กด boardDate๋กœ๋งŒ ์‚ฌ์šฉ ์‹œ ์ฐจํ›„์— ์ˆ˜์ • ์‹œ๊ฐ„ ์‚ฌ์šฉ ๋ถˆ๊ฐ€ ๋ฐ ์˜ค๋ฅ˜ ๋ฐœ์ƒ์œผ๋กœ ์ธํ•œ ์ˆ˜์ •
    ๋ถˆํŽธ์„ ๋ผ์ณ๋“œ๋ ค ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค ลฬฅฬฅฬฅฬฅืลฬฅฬฅฬฅฬฅ
    ๊ทธ๋Ÿผ Controller์—์„œ ๋ต™๊ฒ ์Šต๋‹ˆ๋‹ค...

Controller

thymeleaf๋ฅผ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•ด Controller ํŽ˜์ด์ง€ ์š”์ฒญ๋ฉ”์„œ๋“œ์— ๋‚ด์šฉ์ด ๋ช‡๊ฐ€์ง€ ์ถ”๊ฐ€๋˜๋Š”๊ฒŒ ์žˆ์Šต๋‹ˆ๋‹ค.
์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉฐ ๊ฐ™์ด ์ดํ•ดํ•˜์‹œ์ฃ !

@Controller
@RequiredArgsConstructor
@Slf4j // ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(์–ด๋…ธํ…Œ์ด์…˜)
// ๋ชฉ๋ก ์ถœ๋ ฅ & ์ƒ์„ธ์กฐํšŒ ๋•Œ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
@RequestMapping("/board")
// /board/* ๋Œ€์‹ ์— /board ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ, ์ด๋ฒˆ์—” ํ›„์ž๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!
public class BoardController {

	// ๊ธ€์ž‘์„ฑ ํŽ˜์ด์ง€ ์š”์ฒญ
	@GetMapping("/save")
	public String saveForm(Model model) {
		model.addAttribute("board", new BoardSaveDTO());
		return "board/save";
	}
}

๊ธฐ์กด์€ ๊ทธ์ € ์ฃผ์†Œ ์š”์ฒญ์„ ๋ฐ›์œผ๋ฉด return์œผ๋กœ ํŽ˜์ด์ง€๋งŒ ๋„์›Œ์ฃผ๊ณ  ๋์ด์˜€์ง€๋งŒ
thymeleaf์• ์„œ ์ œ๊ณตํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ด์— ๋งž์ถฐ ์ฝ”๋“œ๊ฐ€ ๋ช‡๊ฐ€์ง€ ์ถ”๊ฐ€๊ฐ€ ๋œ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค.
๋งค๊ฐœ๋ณ€์ˆ˜์— Model์ด ์ถ”๊ฐ€๋˜์—ˆ๊ณ , return์„ ํ•˜๊ธฐ ์ „ BoardSaveDTO()๋ฅผ model์— ๋‹ด์•„๊ฐ‘๋‹ˆ๋‹ค.
์ด ์ฝ”๋“œ๊ฐ€ ๋ฌด์Šจ ์—ญํ• ์„ ํ•˜๋Š”์ง€ save.html์„ ๋งŒ๋“ค๋Ÿฌ ๊ฐ€์‹œ์ฃ  :D


save.html

<!DOCTYPE html>
<!-- ์•„๋ž˜ thymeleaf ์‚ฌ์šฉ๋ฌธ์„ ๊ผญ ์“ฐ์…”์•ผํ•ฉ๋‹ˆ๋‹ค!!!!!! -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
	<meta charset="UTF-8">
	<title>Title</title>
</head>
<body>
<h2>save.html</h2>
<form action="/board/save" method="post" th:object="${board}">
	<!-- th:object="${}" : model์—์„œ ์„ค์ •ํ•œ ํ‚ค๋ฅผ ์‚ฌ์šฉ -->
	<!-- Controller์—์„œ ์„ค์ •ํ•œ model ํ‚ค๊ฐ’๊ณผ ๊ฐ™๊ฒŒ ์ž‘์„ฑํ•ด์•ผ ์˜ค๋ฅ˜๊ฐ€ ์•ˆ๋‚ฉ๋‹ˆ๋‹ค!! -->
	<input type="text" th:field="*{boardWriter}" placeholder="์ž‘์„ฑ์ž">
	<!-- th:field="*{}" : DTO์˜ ํ•„๋“œ๊ฐ’์„ inputํƒœ๊ทธ์˜ id์™€ name๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค๋Š” ์˜๋ฏธ -->
	<!-- ๋ฐ˜๋“œ์‹œ DTO์˜ ํ•„๋“œ ๋„ค์ž„๊ณผ ๊ฐ™๊ฒŒ ์ž‘์„ฑํ•ด์•ผ ์˜ค๋ฅ˜๊ฐ€ ์•ˆ๋‚ฉ๋‹ˆ๋‹ค!! -->
	<input type="text" th:field="*{boardPassword}" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ">
	<input type="text" th:field="*{boardTitle}" placeholder="์ œ๋ชฉ">
	<input type="text" th:field="*{boardContents}" placeholder="๋‚ด์šฉ">
	<input type="submit" th:value="๊ธ€์ž‘์„ฑ">
</form>
</body>
</html>

์›๋ž˜๋Š” ๊ณต๋ฐฑ ์ฒดํฌ ๋ฐ ์˜ค๋ฅ˜์žก๋Š” @Variable๋„ ์‚ฌ์šฉํ•˜๋ ค ํ–ˆ์œผ๋‚˜ ํ•œ๋ฒˆ์— ๋„ˆ๋ฌด ๋งŽ์€๊ฑธ ์‚ฌ์šฉํ•˜๋ฉด
๋‹ค๋“ค ์–ด๋ ค์›Œํ• ๊ฑฐ ๊ฐ™์•„์„œ ์ง€๊ธˆ์€ ์ œ์™ธํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜์ค‘์— ์ž์„ธํžˆ ๋‹ค๋ค„๋ณผ๊ป˜์š”!!

์œ„์˜ ์ฃผ์„๊ณผ ๊ฐ™์ด th:object="${modelํ‚ค๊ฐ’}" & th:field="*{DTO ํ•„๋“œ ์ด๋ฆ„}" ๋ฅผ ์œ„ํ•ด
์ปจํŠธ๋กค๋Ÿฌ์—์„œ ํŽ˜์ด์ง€ ์š”์ฒญ ๋ฉ”์„œ๋“œ์— model์„ ์‚ฌ์šฉํ–ˆ๋˜๊ฒ๋‹ˆ๋‹ค!!
์œ„์—์ฒ˜๋Ÿผ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค๋ฉด id์™€ name๊ฐ’์„ ์ž˜๋ชป ์ง€์ •ํ•ด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ํ™•๋ฅ ์„ ์—†์•จ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ฒ˜์Œ์—๋Š” ์‚ฌ์šฉํ•˜๊ธฐ ๋ถˆํŽธํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๊ณ„์† ์‚ฌ์šฉํ•ด๋ณด๋ฉด์„œ ์ˆ™๋‹ฌํ•˜๋ฉด ์ข‹๊ฒ ์ฃ ??
๊ทธ๋Ÿผ save.html์„ ์™„์„ฑํ–ˆ์œผ๋‹ˆ ์ด๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค๋Ÿฌ Controller๋กœ ๊ฐ€๋ด…์‹œ๋‹ค :D


Controller

// ๊ธ€์ž‘์„ฑ ์ €์žฅ
// thymeleaf๋ฅผ ์“ฐ์ง€ ์•Š๋Š”๋‹ค๋ฉด @ModelAttribute ์ƒ๋žต ๊ฐ€๋Šฅ
// ์ €๋Š” ์ผ์œผ๋ฏ€๋กœ ์ž‘์„ฑ
@PostMapping("/save")
public String save(@ModelAttribute("board") BoardSaveDTO boardSaveDTO) {

	bs.save(boardSaveDTO);
	return "index";
    
}

์œ„ ์ฃผ์„์—์„œ ์„ค๋ช…ํ•œ๊ฒƒ์ฒ˜๋Ÿผ ๋งŒ์•ฝ thymeleaf๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ผ๋ฐ˜์ ์ธ ๋ฐฉ๋ฒ•์œผ๋กœ form์„ ์ž‘์„ฑํ–ˆ๋‹ค๋ฉด @ModelAttribute๋ฅผ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Boot๊ฐ€ ์•Œ์•„์„œ ์žก์•„์ฃผ๊ธฐ ๋•Œ๋ฌธ์ด์ฃ !!
๊ฒŒ์‹œ๊ธ€ ์ €์žฅ ๋ฉ”์„œ๋“œ์—” ๋ณ„ ๋‚ด์šฉ์ด ์—†์œผ๋ฏ€๋กœ ๋ฐ”๋กœ Service๋กœ ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.


Service

  • Interface class์—๋Š” ์„ค๋ช…ํ•  ๋‚ด์šฉ์ด ์—†์œผ๋ฏ€๋กœ ์•ž์œผ๋กœ๋Š” ์ƒ๋žตํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
    ์•Œ์•„์„œ Interface์— ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์‹  ํ›„ ์ž˜ ๊ตฌํ˜„ํ•˜์‹ค๊ฑฐ๋ผ ๋ฏฟ์Šต๋‹ˆ๋‹ค!!
@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService {

	private final BoardRepository br;

	@Override
	public Long save(BoardSaveDTO boardSaveDTO) {
		BoardEntity boardEntity = BoardEntity.saveBoard(boardSaveDTO);
		// DTOํƒ€์ž…์„ Entityํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜
		return br.save(boardEntity).getId();
		// ๋ฆฌํ„ด์œผ๋กœ ๊ฒŒ์‹œ๊ธ€ ๋ฒˆํ˜ธ๋ฅผ ๋„˜๊ฒจ์คŒ.
	}
}

Jpa๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” DTOํƒ€์ž…์„ Entityํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜์‹œ์ผœ์ฃผ๋Š” ์ž‘์—…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
Jpa๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ด๋ฅผ ํ•ญ์ƒ ์ฃผ์˜ํ•ด์ฃผ์„ธ์š”!


Entity

  • DTOํƒ€์ž…์„ Entityํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜์‹œ์ผœ์ฃผ๋Š” ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
public static BoardEntity saveBoard(BoardSaveDTO boardSaveDTO) {
	BoardEntity boardEntity = new BoardEntity();
	boardEntity.setBoardWriter(boardSaveDTO.getBoardWriter());
	boardEntity.setBoardPassword(boardSaveDTO.getBoardPassword());
	boardEntity.setBoardTitle(boardSaveDTO.getBoardTitle());
	boardEntity.setBoardContents(boardSaveDTO.getBoardContents());
	// boardEntity.setBoardDate(LocalDateTime.now());
	// ๊ธด๊ธ‰์ˆ˜์ •์œผ๋กœ ์ธํ•œ ์‚ญ์ œ
	return boardEntity;
    }

์—ฌ๊ธฐ๊นŒ์ง€ ์ž˜ ๋”ฐ๋ผ์˜ค์…จ๋‹ค๋ฉด ๊ฒŒ์‹œ๊ธ€ ์ €์žฅ์€ ๋ฌธ์ œ์—†์ด ์ž˜ ์ž‘๋™ํ•  ๊ฒ๋‹ˆ๋‹ค.
๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ ์‹œ๊ฐ„์€ BaseEntity์—์„œ ๋งŒ๋“  ๋‚ด์šฉ์œผ๋กœ ์ธํ•ด ์ž๋™์œผ๋กœ ๋“ค์–ด๊ฐ€์ง‘๋‹ˆ๋‹ค.
๊ทธ๋Ÿผ ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก ์ถœ๋ ฅ์œผ๋กœ ๋„˜์–ด๊ฐ€๋ด…์‹œ๋‹ค!! :D


๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก ์ถœ๋ ฅ & ์ƒ์„ธ์กฐํšŒ

์ง€๊ธˆ๋ถ€ํ„ฐ ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก์ถœ๋ ฅ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค!
์ด์ „์— ํšŒ์›๊ด€๋ฆฌ ์˜ˆ์ œ๋ฅผ ํ•˜์‹  ๋ถ„์ด๋ผ๋ฉด ๊ฑฐ์˜ ๋˜‘๊ฐ™์ด ํ˜๋Ÿฌ๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ์—†์ด ์ž˜ ํ•˜์‹ค ์ˆ˜ ์žˆ์„๊ฒ๋‹ˆ๋‹ค :D
๊ทธ๋Ÿผ ๋ฐ”๋กœ ๊ฐ€์‹œ์ฃ ~!ูฉ(เน‘โ€ขฬ€oโ€ขฬเน‘)ูˆ

BoardDetialDTO

๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก ์ถœ๋ ฅ์šฉ & ์ƒ์„ธ์กฐํšŒ๋ฅผ ๊ฐ™์ด ์ˆ˜ํ–‰ํ•˜๋Š” DTO๋ฅผ ๋งŒ๋“ค์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.
DTO๋ฅผ ๋งŒ๋“ค๋ฉด์„œ Entityํƒ€์ž…์„ DTOํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜์‹œ์ผœ์ฃผ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.
์•Œ์•„๋‘๋ฉด ์ข‹์€ ํŒ์œผ๋กœ ๋ณ€ํ™˜์ด ํ•„์š”ํ•œ ํƒ€์ž…์ด ์†ํ•œ ๋ฉ”์„œ๋“œ์— ๋ณ€ํ™˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š”๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค!!
toBoardDetailDTO : Entityํƒ€์ž…์„ DTOํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜์‹œ์ผœ์ฃผ๋Š” ๋ฉ”์„œ๋“œ
toBoardDetailDTOList : List<Entity>ํƒ€์ž…์„ List<BoardDetailDTO>ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BoardDetailDTO {

	private Long boardId;
	private String boardWriter;
	private String boardPassword;
	private String boardTitle;
	private String boardContents;
    
	// boardDate ์‚ญ์ œ
	// private LocalDateTime boardDate;
    
	// ์•„๋ž˜ ๋‘ ์ค„ ์ถ”๊ฐ€
	private LocalDateTime createTime;
	private LocalDateTime updateTime;

	public static BoardDetailDTO toBoardDetailDTO(BoardEntity boardEntity) {
		BoardDetailDTO boardDetailDTO = new BoardDetailDTO();
		boardDetailDTO.setBoardId(boardEntity.getId());
		boardDetailDTO.setBoardWriter(boardEntity.getBoardWriter());
		boardDetailDTO.setBoardPassword(boardEntity.getBoardPassword());
		boardDetailDTO.setBoardTitle(boardEntity.getBoardTitle());
		boardDetailDTO.setBoardContents(boardEntity.getBoardContents());
		// boardDetailDTO.setBoardDate(boardEntity.getBoardDate());
		boardDetailDTO.setCreateTime(boardEntity.getCreateTime());
		boardDetailDTO.setUpdateTime(boardEntity.getUpdateTime());
		return boardDetailDTO;
	}

	public static List<BoardDetailDTO> toBoardDetailDTOList(List<BoardEntity> boardEntityList) {
		List<BoardDetailDTO> boardDetailDTOList = new ArrayList<>();
		for(BoardEntity b: boardEntityList) {
			boardDetailDTOList.add(toBoardDetailDTO(b));
		}
		return boardDetailDTOList;
	}
}

Controller

// ๊ธ€๋ชฉ๋ก ์ถœ๋ ฅ
@GetMapping("/")
public String findAll(Model model) {
	log.info("๊ธ€๋ณด๊ธฐ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ. ์š”์ฒญ๊ธ€๋ฒˆํ˜ธ {}", boardId);
	List<BoardDetailDTO> boardDetailDTOList = bs.findAll();
	model.addAttribute("boardList", boardDetailDTOList);
	return "board/findAll";
}

@GetMapping("/{boardId}")
// {}์•ž์— / ๊ฐ€ ๋ถ™์–ด๋„ @PathVariable๋’ค ("boardId") ์ƒ๋žต๊ฐ€๋Šฅ
public String findById(@PathVariable Long boardId, Model model) {
	// ๋ณ€์ˆ˜๋ฅผ ์ฐ๊ณ ์‹ถ๋‹ค๋ฉด {} ๊ฐ€ ๋ฐ˜๋“œ์‹œ ํ•„์š”. {}์•ˆ์— ๋ณ€์ˆ˜๊ฐ€ ๋“ค์–ด๊ฐ
	// ๋กœ๊ทธ ์ข…๋ฅ˜
	// info : ํ•˜๋‚˜ํ•˜๋‚˜ ๋™์ž‘์„ ์ „๋ถ€ ๊ธฐ๋ก
	// warn : ๊ฒฝ๊ณ , ์—๋Ÿฌ๋Š” ์•„๋‹ˆ์ง€๋งŒ ๋ฌธ์ œ์˜ ์†Œ์ง€๊ฐ€ ์žˆ๋‹ค.
	// error : ๋ฌธ์ œ ๋ฐœ์ƒ
	log.info("๊ธ€๋ณด๊ธฐ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ. ์š”์ฒญ๊ธ€๋ฒˆํ˜ธ {}", boardId);
	BoardDetailDTO boardDetailDTO = bs.findById(boardId);
	model.addAttribute("boardDetailDTO",boardDetailDTO);
	return "board/findById";
}

// ajax๋Š” ์ œ์™ธํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉํ•˜์‹ค๋ถ„๋“ค์€ ์•„๋ž˜ ์ฃผ์„์„ ํ•ด์ œํ•˜์‹œ๊ณ  ์‚ฌ์šฉํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค!
// ResponseEntity๋กœ ๋ฐ์ดํ„ฐ์™€ ์ƒํƒœ์ฝ”๋“œ๋ฅผ ๊ฐ™์ด ๋ณด๋‚ด๋Š” ๋ฐฉ๋ฒ•.
// @PostMapping("/{boardId}")
// public ResponseEntity findById2(@PathVariable Long boardId) {
//	 BoardDetailDTO boardDetailDTO = bs.findById(boardId);
//	 return new ResponseEntity<BoardDetailDTO>(boardDetailDTO, HttpStatus.OK);
//	 ๋ณด๋‚ด๊ณ ์ž ํ•˜๋Š” ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์„ <> ์•ˆ์— ์จ์•ผํ•œ๋‹ค. (์ƒ๋žต๊ฐ€๋Šฅํ•˜๊ธดํ•˜์ง€๋งŒ ์“ฐ๋Š”๊ฒŒ ์ข‹์Œ)
//	 ์–‘์‹ : new ResponseEntity<๋ณด๋‚ด๊ณ ์žํ•˜๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž…>(๋ฐ์ดํ„ฐ ๋ณ€์ˆ˜์ด๋ฆ„, ์ƒํƒœ์ฝ”๋“œ)
//	 HttpStatus.BAD_REQUEST๋ฅผ ํ†ตํ•ด ์ž˜๋ชป ๋“ค์–ด์˜จ ์š”์ฒญ์— ๋Œ€ํ•ด ๋ถ„๊ธฐ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.
}

๊ธฐ์กด ํšŒ์›๊ด€๋ฆฌ์™€ ๋˜‘๊ฐ™์ด ๊ฐ€๋Š”๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋‹ค๋ฅธ์ ์ด ์žˆ๋‹ค๋ฉด log๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์ด ๋ณด์ผํ…๋ฐ์š”, ๊ผญ ํ•„์š”ํ•œ๊ฑด ์•„๋‹ˆ์ง€๋งŒ ์ด๋Ÿฌํ•œ ์ฝ˜์†” ๋กœ๊ทธ๊ธฐ๋ก์„ ํ†ตํ•ด
์–ด๋–ค ๋ฉ”์„œ๋“œ๊ฐ€ ์–ธ์ œ ํ˜ธ์ถœ์ด ๋ฌ๋Š”์ง€ ๋” ํŽธํ•˜๊ฒŒ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•œ๋ฒˆ์”ฉ ์‚ฌ์šฉํ•ด๋ณด์‹œ๋Š”๊ฒƒ๋„ ๋‚˜์˜์ง€ ์•Š์„๊ฒ๋‹ˆ๋‹ค :D
๊ทธ๋Ÿผ ๋ฐ”๋กœ html์„ ๋งŒ๋“ค๋Ÿฌ ๊ฐ€๋ณด์‹œ์ฃ !!


findAll.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://code.jquery.com/jquery-3.6.0.js"></script>
    <script>
	// ajax๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์‹ถ์œผ์‹  ๋ถ„๋“ค์€ ์•„๋ž˜ ์ฃผ์„์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ œํ•˜์‹œ๊ณ  ์“ฐ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
	// ์ž์„ธํ•œ ์„ค๋ช…์€ ์ƒ๋žตํ•˜๋‹ˆ ์ง์ ‘ ์•„๋ž˜ ์ฝ”๋“œ๋“ค์ด ๋ฌด์Šจ ์—ญํ• ์„ ํ•˜๋Š” ๊ฑด์ง€ ์•Œ์•„๋ณด์‹œ๋Š”๊ฒƒ๋„ ์ข‹์„๊ฑฐ ๊ฐ™์Šต๋‹ˆ๋‹ค:D
        // const detail = (boardId) => {
        //     console.log(boardId)
        //     const reqUrl = "/board/"+boardId
        //     $.ajax({
        //         type: "post",
        //         url: reqUrl,
        //         dataType: "json",
        //         success: function (result) {
        //             console.log(result)
        //             let output=""
        //             output += "<table>\n" +
        //                 "    <thead>\n" +
        //                 "        <tr>\n" +
        //                 "            <th>๋ฒˆํ˜ธ</th>\n" +
        //                 "            <th>์ž‘์„ฑ์ž</th>\n" +
        //                 "            <th>์ œ๋ชฉ</th>\n" +
        //                 "            <th>๋‚ด์šฉ</th>\n" +
        //                 "            <th>์ž‘์„ฑ์ผ์ž</th>\n" +
        //                 "        </tr>\n" +
        //                 "    </thead>\n" +
        //                 "    <tbody>\n" +
        //                 "        <tr>\n" +
        //                 "            <td>" + result.boardId + "</td>\n" +
        //                 "            <td>" + result.boardWriter + "</td>\n" +
        //                 "            <td>" + result.boardTitle + "</td>\n" +
        //                 "            <td>" + result.boardContents + "</td>\n" +
        //                 "            <td>" + result.createTime + "</td>\n" +
        //                 "        </tr>\n" +
        //                 "    </tbody>\n" +
        //                 "</table>"
        //             document.getElementById("board-detail").innerHTML = output;
        //         },
        //         error: function () {
        //             alert('ajax ์‹คํŒจ')
        //         }
        //     })
        // }
    </script>
</head>
<body>
<h2>findAll.html</h2>
<table>
    <thead>
        <tr>
            <th>๋ฒˆํ˜ธ</th>
            <th>์ž‘์„ฑ์ž</th>
            <th>์ œ๋ชฉ</th>
            <th>์ž‘์„ฑ์ผ์ž</th>
            <th>์ˆ˜์ •์ผ์ž</th>
            <!-- th>ajax ์ƒ์„ธ์กฐํšŒ</th> ajax์‚ฌ์šฉํ•˜์‹ค ๋ถ„์€ ์ฃผ์„ํ•ด์ œํ•˜์…”์šฉ -->
        </tr>
    </thead>
    <tbody>
        <tr th:each="board:${boardList}">
            <td th:text="${board.boardId}"></td>
            <td th:text="${board.boardWriter}"></td>
            <!-- ์ด ์ฝ”๋“œ๋กœ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ <td><a th:text="${board.boardTitle}" th:href="@{|/board/${board.boardId}|}"></a></td> -->
            <td>
                <a th:href="@{|/board/${board.boardId}|}">
                    <span th:text="${board.boardTitle}"></span>
                </a>
            </td>
            <td th:text="${board.createTime}"></td>
            <td th:text="${board.updateTime}"></td>
            <!-- ์ œ๋ชฉ ํด๋ฆญํ•˜๋ฉด ์ƒ์„ธ์กฐํšŒํ™”๋ฉด(findById.html) ์ถœ๋ ฅ -->
            <!-- ajax๋ฅผ ์ด์šฉํ•œ ์ƒ์„ธ์กฐํšŒ, ์กฐํšŒ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋ชฉ๋ก ์•„๋ž˜์— ํ•ด๋‹น ๊ธ€์˜ ์ƒ์„ธ ๋‚ด์šฉ ์ถœ๋ ฅ -->
            <!-- <td><button th:onclick="detail([[${board.boardId}]])">์ƒ์„ธ์กฐํšŒ(ajax)</button></td> -->
	    <!-- ์‚ฌ์šฉํ•˜์‹ค ๋ถ„๋“ค์€ ์ฃผ์„์„ ํ•ด์ œํ•ด์ฃผ์„ธ์š”:D -->
        </tr>
    </tbody>
</table>
<!-- ajax๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์œผ๋ฉด ์ฃผ์„์„ ํ•ด์ œํ•ด์ฃผ์„ธ์š”! -->
<!-- <div id="board-detail"></div> -->
</body>
</html>

findById.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>findById.html</h2>
<table>
    <thead>
        <tr>
            <th>๋ฒˆํ˜ธ</th>
            <th>์ž‘์„ฑ์ž</th>
            <th>์ œ๋ชฉ</th>
            <th>๋‚ด์šฉ</th>
            <th>์ž‘์„ฑ์‹œ๊ฐ„</th>
            <th>์ˆ˜์ •์‹œ๊ฐ„</th>
            <th>์ˆ˜์ •</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th th:text="${boardDetailDTO.boardId}"></th>
            <th th:text="${boardDetailDTO.boardWriter}"></th>
            <th th:text="${boardDetailDTO.boardTitle}"></th>
            <th th:text="${boardDetailDTO.boardContents}"></th>
            <th th:text="${boardDetailDTO.createTime}"></th>
            <th th:text="${boardDetailDTO.updateTime}"></th>
            <th><a th:href="@{|/board/update/${boardDetailDTO.boardId}|}">์ˆ˜์ •ํŽ˜์ด์ง€</a></th>
        </tr>
    </tbody>
</table>
</body>
</html>

์ด๋ ‡๊ฒŒ ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก์ถœ๋ ฅ์šฉ๊ณผ ์ƒ์„ธ์กฐํšŒ์šฉ html์„ ์ œ์ž‘ํ–ˆ์œผ๋‹ˆ Service์— ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค๋Ÿฌ ๊ฐ€๋ด…์‹œ๋‹ค!!
๋งŒ์•ฝ ajax๋กœ ํ•ด๋ณด๊ณ  ์‹ถ์œผ์‹  ๋ถ„๋“ค์€ ์ฃผ์„์ฒ˜๋ฆฌํ•œ ๋ถ€๋ถ„์„ ์‚ฌ์šฉํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
ajax์— ๋Œ€ํ•ด ์ƒˆ๋กœ์šด ๋‚ด์šฉ์€ ์ฃผ์„์œผ๋กœ ์„ค๋ช…์„ ๋ถ™์—ฌ๋†จ์œผ๋‹ˆ ๊ฑฑ์ • ์•Š์œผ์…”๋„ ๋ฉ๋‹ˆ๋‹ค!!


Service

@Override
public List<BoardDetailDTO> findAll() {
	List<BoardEntity> boardEntityList = br.findAll();
	List<BoardDetailDTO> boardDetailDTOList = BoardDetailDTO.toBoardDetailDTOList(boardEntityList);
	return boardDetailDTOList;
}

@Override
public BoardDetailDTO findById(Long boardId) {

	/*
		Optional ์‚ฌ์šฉ
		๋จผ์ € null ์ฒดํฌ๋ฅผ ํ•ด์•ผ ํ•จ.

		Optional ๊ฐ์ฒด ๋ฉ”์„œ๋“œ
		.isPresent() : ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด true, ์—†์œผ๋ฉด false ๋ฐ˜ํ™˜
		.isEmpty() : ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด false, ์—†์œผ๋ฉด true ๋ฐ˜ํ™˜
		.get() : Optional ๊ฐ์ฒด์— ๋“ค์–ด์žˆ๋Š” ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ
	*/

	Optional<BoardEntity> optionalBoardEntity = br.findById(boardId);
	BoardDetailDTO boardDetailDTO = null;
	if (optionalBoardEntity.isPresent()) {
		BoardEntity boardEntity = optionalBoardEntity.get();
		boardDetailDTO = BoardDetailDTO.toBoardDetailDTO(boardEntity);
	}
	return boardDetailDTO;
}

์ด๋ฒˆ์—๋Š” Optional๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์Šต๋‹ˆ๋‹ค.
Boot์—์„œ๋Š” DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ null์ด๋“  null์ด ์•„๋‹ˆ๋“  ๋ฌด์กฐ๊ฑด Optional๊ฐ์ฒด์— ๋‹ด์•„์„œ ๋ณด๋‚ด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
(findAll๊ฐ™์ด ๋ฐ์ดํ„ฐ ์ „์ฒด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๊ฒŒ ์•„๋‹Œ, findById๊ฐ™์€ ์ฟผ๋ฆฌ์—์„œ)
์ €๋ฒˆ์—๋Š” ๋ฐ”๋กœ .get()์œผ๋กœ ์•ˆ์— ๊ฐ’์„ ๊บผ๋‚ด ์‚ฌ์šฉํ–ˆ์ง€๋งŒ ์ด๋ฒˆ์—๋Š” ๋จผ์ € null์ฒดํฌ ํ›„ ๊ฐ’์„ ๊ฐ€์ ธ๊ฐ€๊ฒŒ๋” ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.
๋งŒ์•ฝ ๋” ์ถ”๊ฐ€๋˜๋Š” ๋‚ด์šฉ์ด ์žˆ๋‹ค๋ฉด ๋‚˜์ค‘์— ์ž์„ธํžˆ ๋‹ค๋ค„๋ณด๋„๋ก ํ•˜์ฃ !! :D


๋งˆ์น˜๋ฉฐ

์ด๋ ‡๊ฒŒ ๊ฒŒ์‹œ๊ธ€ ์ €์žฅ๋ถ€ํ„ฐ ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก ์ถœ๋ ฅ, ์ƒ์„ธ ์กฐํšŒ๊นŒ์ง€ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค.
์•„์ง๊นŒ์ง€๋Š” ํšŒ์›๊ด€๋ฆฌ ์˜ˆ์ œ์™€ ๊ฒน์น˜๋Š” ๋ถ€๋ถ„์ด ๋งŽ์•„ ์ด์ „ ๊ฒŒ์‹œ๊ธ€๋“ค์„ ๋”ฐ๋ผํ•ด๋ณด์‹  ๋ถ„๋“ค์ด๋ผ๋ฉด
๋ณ„ ๋ฌด๋ฆฌ์—†์ด ์ž˜ ํ•˜์…จ์„๊ฒ๋‹ˆ๋‹ค :D
๋‹ค์Œ์‹œ๊ฐ„์—๋Š” ์ˆ˜์ •๊ณผ ๊ทธ ์™ธ ๋‹ค๋ฅธ๊ฑฐ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿผ ๋‹ค๋“ค ๋‹ค์Œ์— ๊ฑด๊ฐ•ํ•œ ๋ชจ์Šต์œผ๋กœ ๋งŒ๋‚˜์š”~!! :D

profile
์ฆ๊ฒ๋‹ค!

0๊ฐœ์˜ ๋Œ“๊ธ€