[Spring] JPA Entity

Yuriยท2025๋…„ 2์›” 6์ผ

Spring

๋ชฉ๋ก ๋ณด๊ธฐ
10/21

๐Ÿ”ซ ์ผ์ • ๊ด€๋ฆฌ ์•ฑ Develop API ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉฐ ๊ฒช์€ ๋ฌธ์ œ์ ๊ณผ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•, ์ƒˆ๋กœ ์•Œ๊ฒŒ๋œ ์ ์„ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.

๐Ÿฅ… ๋ชฉํ‘œ

  • ERD ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ JPA๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” Entity ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑ

JPA Auditing ( BaseEntity )

๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(RDB) ํ…Œ์ด๋ธ”์— ๋งคํ•‘ํ•  ๋•Œ ๋„๋ฉ”์ธ๋“ค์ด ๊ณตํ†ต์ ์œผ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํ•„๋“œ (์ƒ์„ฑ์ผ, ์ˆ˜์ •์ผ) ๊ฐ™์€ ํ•„๋“œ ๋ฐ ์ปฌ๋Ÿผ์ด ์กด์žฌ.

์ด๋Ÿฌํ•œ ๊ณตํ†ต ํ•„๋“œ๋ฅผ ์ค‘๋ณต์—†์ด ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด JPA์—์„œ Auditing์„ ์ œ๊ณตํ•œ๋‹ค. Audit์€ ๊ฐ์‹œํ•˜๋‹ค, ๊ฐ์‚ฌํ•˜๋‹ค ์˜ ๋œป์œผ๋กœ Spring JPA์—์„œ ์‹œ๊ฐ„์— ๋Œ€ํ•œ ๊ฐ’์„ ์ž๋™์œผ๋กœ ๋„ฃ์–ด์ค„ ๋•Œ ์œ ์šฉํ•˜๋‹ค.

@SpringBootApplication ํด๋ž˜์Šค

@EnableJpaAuditing
@SpringBootApplication
public class JpaScheduleApplication {

    public static void main(String[] args) {
        SpringApplication.run(JpaScheduleApplication.class, args);
    }

}

@EnableJpaAuditing ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ JPA Auditing ํ™œ์„ฑํ™”

BaseEntity

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;

    private LocalDateTime deletedAt;
}
  • ํ•„๋“œ: ์ƒ์„ฑ์ผ, ์ˆ˜์ •์ผ
  • ๋ชจ๋“  ๋„๋ฉ”์ธ์ด ์‚ญ์ œ์—ฌ๋ถ€์— ๋Œ€ํ•œ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฏ€๋กœ deletedAt ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์˜€๋‹ค. (Soft Delete)
    • default: null
    • Entity๊ฐ€ ์‚ญ์ œ๋  ๋•Œ ์‹œ๊ฐ„์„ ์ €์žฅ
์–ด๋…ธํ…Œ์ด์…˜์„ค๋ช…
@MappedSuperclassJPA Entity ํด๋ž˜์Šค๋“ค์ด ํ•ด๋‹น ์ถ”์ƒ ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•  ๊ฒฝ์šฐ ์ถ”์ƒํด๋ž˜์Šค์˜ ํ•„๋“œ๋ฅผ ์ปฌ๋Ÿผ์œผ๋กœ ์ธ์‹
@EntityListeners(AuditingEntityListener.class)ํ•ด๋‹น ํด๋ž˜์Šค์— Auditing ๊ธฐ๋Šฅ์„ ํฌํ•จ
@CreatedDateEntity๊ฐ€ ์ƒ์„ฑ๋˜์–ด ์ €์žฅ๋  ๋•Œ ์‹œ๊ฐ„์ด ์ž๋™ ์ €์žฅ
@LastModifiedDate์กฐํšŒํ•œ Entity์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ๋•Œ ์‹œ๊ฐ„์ด ์ž๋™ ์ €์žฅ

๐Ÿ”— ์ฐธ๊ณ 
JPA Auditing ๊ธฐ๋Šฅ์ด๋ž€?

JPA ์˜ Entity๋Š” ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜๋‹ค.

Entity ํด๋ž˜์Šค์— ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ์—†์„ ๊ฒฝ์šฐ, JpaSystemException์ด ๋ฐœ์ƒํ•œ๋‹ค.

repository์—์„œ ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ํ•„์š”ํ•˜๋‹ค. JPA๊ฐ€ DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๊ณ  ๊ฐ์ฒด๋กœ ๋งคํ•‘ํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋กœ์ง์— Java Reflection ์ด ์ ์šฉ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

JPA๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” @Entity๋กœ ์„ ์–ธํ•˜๋ฉด JPA๋Š” Java Reflection ์ด ์ ์šฉ๋  ์ˆ˜ ์žˆ๋Š”์ง€ ์ฒดํฌํ•˜์—ฌ ์ปดํŒŒ์ผ ์—๋Ÿฌ๋ฅผ ํ‘œ์‹œํ•œ๋‹ค.

Java Reflection

๊ตฌ์ฒด์ ์ธ ํด๋ž˜์Šค ํƒ€์ž…์„ ์•Œ์ง€ ๋ชปํ•ด๋„ ๊ทธ ํด๋ž˜์Šค ์ •๋ณด(๋ฉ”์„œ๋“œ, ํƒ€์ž… ๋“ฑ)์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” Java API ์ด๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ์บก์Аํ™”๊ฐ€ ์ ์šฉ๋œ ๊ฐ์ฒด์— ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ๊ธฐ ์œ„ํ•ด์„œ new ํ‚ค์›Œ๋“œ(์ƒ์„ฑ์ž)๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, setter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ๋Š”๋‹ค.

reflection ์„ ์‚ฌ์šฉํ•˜๋ฉด private ์ ‘๊ทผ ์ œ์–ด์ž๋ฅผ ๊ฐ€์ง„ ๋ฉ”์„œ๋“œ์—๋„ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

  • static ์˜์—ญ์— ์ €์žฅ๋˜์–ด์žˆ๋Š” ํด๋ž˜์Šค ์ •๋ณด์— ์ ‘๊ทผ
    โž• org.springframework.util.ReflectionUtils

    โ†’ Modifier(์ œ์–ด์ž)๊ฐ€ Public์ด ์•„๋‹ˆ๋ผ๋ฉด ์ธ์ž๋กœ ๋ฐ›์€ Method๋ฅผ setAccessible(true) : ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ๋งŒ๋“ ๋‹ค.

  • ์ ‘๊ทผ ๊ฐ€๋Šฅ(public)ํ•˜๋„๋ก ๋งŒ๋“  ๋‹ค์Œ ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์ƒ์„ฑ๋œ ๊ฐ์ฒด ํ•„๋“œ, ๋ฉ”์„œ๋“œ์— ์ง์ ‘ ์ ‘๊ทผํ•˜์—ฌ ๊ฐ’์„ ๋ณ€๊ฒฝํ•œ๋‹ค.

๐Ÿ’ซ Controller์—์„œ @RequestBody์„ ๋ฐ”์ธ๋”ฉํ•˜์—ฌ JSON ๋ฌธ์ž์—ด์˜ ๊ฐ’์„ ๋ฐ›์„ ๋•Œ setter ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์•„๋„ ๊ฐ์ฒด์— ์ €์žฅํ•˜๋Š” ์ด์œ ๋„ Java Reflection์ด ๊ธฐ๋ณธ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ ํ›„ ํ•„๋“œ ๊ฐ’์„ ๊ฐ•์ œ๋กœ ๋งคํ•‘ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ด๋Ÿฌํ•œ ์ด์œ ๋กœ JPA๋Š” Java Reflection์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๋ฅผ ๋ฐ˜๋“œ์‹œ ์ƒ์„ฑํ•˜๋„๋ก ๊ฐ•์ œํ•œ๋‹ค.

๐Ÿ”— ์ฐธ๊ณ 
JPA์˜ Entity๋Š” ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ์™œ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ• ๊นŒ?

์ฆ‰์‹œ ๋กœ๋”ฉ๊ณผ ์ง€์—ฐ ๋กœ๋”ฉ

Schedule ๊ณผ Member ์‚ฌ์ด์— @ManyToOne(N:1) ๊ด€๊ณ„๋กœ ๋งคํ•‘๋˜์–ด ์žˆ์„ ๊ฒฝ์šฐ,@ManyToOne ์–ด๋…ธํ…Œ์ด์…˜์— fetch ํƒ€์ž…์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค.

์ฆ‰์‹œ๋กœ๋”ฉ(FetchType.EAGER)

  • ํŠน์ง•
    • FetchType์„ ์ง€์ •ํ•˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ Default Type
    • JPA ๊ตฌํ˜„์ฒด๊ฐ€ JOIN์„ ์‚ฌ์šฉํ•˜์—ฌ SQL ํ•œ ๋ฒˆ์— ํ•จ๊ป˜ ์กฐํšŒํ•ด์˜จ๋‹ค.

์ง€์—ฐ๋กœ๋”ฉ(FetchType.LAZY)

@Getter
@Entity
@Table(name = "schedule")
public class Schedule extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    // ...

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    public Schedule() {
    }
}

  • ํŠน์ง•
    • ๋กœ๋”ฉ๋˜๋Š” ์‹œ์ ์— Lazy ๋กœ๋”ฉ์ด ์„ค์ •๋˜์–ด ์žˆ๋Š” ์—”ํ‹ฐํ‹ฐ๋Š” ํ”„๋ก์‹œ ๊ฐ์ฒด*๋กœ ๊ฐ€์ ธ์˜จ๋‹ค.
    • ํ›„์— ์‹ค์ œ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์‹œ์ ์— (Member์„ ์‚ฌ์šฉํ•˜๋Š” ์‹œ์ ์—) ์ดˆ๊ธฐํ™” ๋œ๋‹ค.
      • getMember()๋กœ ์กฐํšŒํ•˜๋ฉด ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์กฐํšŒํ•œ๋‹ค.
      • getMember().getUsername() ์œผ๋กœ ํ•„๋“œ์— ์ ‘๊ทผํ•  ๋•Œ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ„๋‹ค.

โœ๏ธ ํ”„๋ก์‹œ ๊ฐ์ฒด(Proxy)
JPA์˜ ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•„์š”ํ•  ๋•Œ๋งŒ ๊บผ๋‚ด์“ธ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฐ€์งœ ๊ฐ์ฒด์ด๋‹ค.

  • em.find(): ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ํ†ตํ•ด ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด ์กฐํšŒ
  • em.getReference(): ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ๋ฅผ ๋ฏธ๋ฃจ๋Š” ๊ฐ€์งœ ๊ฐ์ฒด ์กฐํšŒ

๐Ÿค” ์–ด๋–ค ์ „๋žต์„ ์จ์•ผํ• ๊นŒ?
๋Œ€๋ถ€๋ถ„์˜ ๋น„์Šค๋‹ˆ์Šค ๋กœ์ง์—์„œ Schedule ๊ณผ Member ์„ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋ผ๋ฉด LAZY ๋กœ๋”ฉ์ด ์†ํ•ด
๐Ÿ‘‰ ์กฐํšŒ๋ฅผ ํ•œ ๋ฒˆ๋งŒ ํ•˜๋Š”๊ฒŒ ์ข‹์œผ๋‹ˆ๊นŒ ๋ฌด์กฐ๊ฑด EAGER(์ฆ‰์‹œ ๋กœ๋”ฉ)์„ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๋‚˜์š”?

  • ์‹ค๋ฌด์—์„œ๋Š” ๊ฐ€๊ธ‰์  ์ง€์—ฐ ๋กœ๋”ฉ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค.
  • ์ฆ‰์‹œ ๋กœ๋”ฉ์„ ์ ์šฉํ•˜๋ฉด ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ SQL์ด ๋ฐœ์ƒํ•œ๋‹ค.
    • ์‹ค๋ฌด์—์„  ์—ฐ๊ด€๊ด€๊ณ„์˜ ํ…Œ์ด๋ธ”์ด ๋งŽ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค! ๋งŒ์•ฝ, @ManyToOne ํ•„๋“œ๊ฐ€ 5๊ฐœ๋ผ๋ฉด SQL์—์„œ๋„ JOIN์ด 5๊ฐœ ์ผ์–ด๋‚œ๋‹ค.
  • ์ฆ‰์‹œ ๋กœ๋”ฉ์€ JPQL์—์„œ N+1 ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚จ๋‹ค.
    • ํ•˜๋‚˜์˜ ์ฟผ๋ฆฌ ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ N๊ฐœ (ํŒŒ์ƒ)
  • ์‹ค๋ฌด์—์„œ๋Š” LAZY ๋กœ๋”ฉ ์ „๋žต์„ ๊ฐ€์ ธ๊ฐ„๋‹ค.
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์„ ๊ฒฝ์šฐ, JPQL fetch join์ด๋‚˜ ์—”ํ‹ฐํ‹ฐ ๊ทธ๋ž˜ํ”„ ๊ธฐ๋Šฅ์œผ๋กœ ํ•ด๊ฒฐํ•˜์ž.

๐Ÿ”— ์ฐธ๊ณ 
[JPA] ์ฆ‰์‹œ ๋กœ๋”ฉ๊ณผ ์ง€์—ฐ ๋กœ๋”ฉ(FetchType.LAZY or EAGER)
[JPA] ํ”„๋ก์‹œ


N + 1 ํ•ด๊ฒฐ์ฑ…

์ฆ‰์‹œ ๋กœ๋”ฉ์—์„œ ๋ฐœ์ƒํ•˜๋Š” N + 1์„ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ• โ‰  ์ง€์—ฐ ๋กœ๋”ฉ

์ง€์—ฐ ๋กœ๋”ฉ + fetch join์„ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ N + 1 ์ด ๋ฐœ์ƒํ•œ๋‹ค.

โ†’ ํ•ด๋‹น ์—ฐ๊ฒฐ Entity ์— ํ”„๋ก์‹œ๋กœ ๊ฑธ์–ด๋‘๊ณ , ๊ฒฐ๊ตญ ์‚ฌ์šฉํ•  ๋•Œ ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ์ฒ˜์Œ ์กฐํšŒ ํ•  ๋•Œ๋Š” N+1์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์ง€๋งŒ ์—ฐ๊ฒฐ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด ํ”„๋ก์‹œ์— ๋Œ€ํ•œ ์ฟผ๋ฆฌ๊ฐ€ ๋˜ ๋ฐœ์ƒํ•œ๋‹ค.

  • fetch join
  • @EntityGraph

๐Ÿซค ์œ ์˜์ 

  1. Pagination
    • ~ToOne ๊ด€๊ณ„์—์„œ์˜ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
    • @BatchSize
    • @Fetch(FetchMode.SUBSELECT)
  2. ๋‘˜ ์ด์ƒ์˜ Collection fetch join(~ToMany) ๋ถˆ๊ฐ€๋Šฅ
    • ์ž๋ฃŒํ˜•์„ Set ์œผ๋กœ
    • @BatchSize

๐Ÿ”— ์ฐธ๊ณ 
JPA ๋ชจ๋“  N+1 ๋ฐœ์ƒ ์ผ€์ด์Šค๊ณผ ํ•ด๊ฒฐ์ฑ…


๐Ÿ˜Ž ํ•™์Šต ๋‚ด์šฉ ์ถ”๊ฐ€์˜ˆ์ •

profile
์•ˆ๋…•ํ•˜์„ธ์š” :)

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