πŸ“– μŠ€ν”„λ§ λΆ€νŠΈμ™€ JPA 싀무 μ™„μ „ 정볡 λ‘œλ“œλ§΅

κΉ€μ˜ν•œμ˜ μŠ€ν”„λ§ λΆ€νŠΈμ™€ JPA 싀무 μ™„μ „ 정볡 λ‘œλ“œλ§΅ λ‚΄μš©μ„ 기반으둜 Spring boot + JPA + Querydsl ν•™μŠ΅ λ‚΄μš©μ„ μ •λ¦¬ν•œ μžλ£Œμž…λ‹ˆλ‹€.


연관관계 맀핑

닀쀑성, 단방ν–₯VSμ–‘λ°©ν–₯, μ—°κ΄€κ΄€κ³„μ˜ 주인

JPAλ₯Ό 처음 λ°°μš°λŠ” μ‚¬λžŒλ“€μ΄ κ°€μž₯ ν—·κ°ˆλ¦¬κ³  μ–΄λ ΅κ²Œ λŠλ‚„ 수 μžˆλŠ” 뢀뢄일 것 κ°™λ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€. μ΄ν•˜ λ‚΄μš©μ€ κΉ€μ˜ν•œλ‹˜ μΈν”„λŸ° λ‘œλ“œλ§΅ ν•™μŠ΅λ‚΄μš©μ„ 기반으둜 연관관계 맀핑에 λŒ€ν•΄ μ •λ¦¬ν–ˆμŠ΅λ‹ˆλ‹€.
μ—°κ΄€κ΄€κ³„λž€ λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ μ™Έλž˜ν‚€λ‘œ RDB 관계λ₯Ό μ„€μ •ν•˜λŠ” κ²ƒμ²˜λŸΌ 객체지ν–₯ μ„€κ³„μ—μ„œ 객체λ₯Ό μ°Έμ‘°ν•˜λŠ” 방식을 μ΄μ•ΌκΈ°ν•©λ‹ˆλ‹€. 연관이 μžˆλŠ” 관계, 예λ₯Ό λ“€λ©΄, μ–΄λ–€ λ°˜μ— μ†ν•˜λŠ” 학생이 μ—¬λŸ¬ λͺ…이라면 κ·Έ 반과 학생듀은 1:N λ§€ν•‘μœΌλ‘œ μ„€λͺ…ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ JPAμ—μ„œλŠ” 연관관계λ₯Ό μ™Έλž˜ν‚€κ°€ 없이 객체의 μ°Έμ‘° λ°©μ‹μœΌλ‘œ 연관을 지을 수 μžˆλŠ” 방법을 μ œκ³΅ν•©λ‹ˆλ‹€.

연관관계 λ§€ν•‘μ‹œ 고렀사항
μ—°κ΄€κ΄€κ³„λŠ” 크게 단방ν–₯κ³Ό μ–‘λ°©ν–₯ λ§€ν•‘μœΌλ‘œ λ‚˜λ‰©λ‹ˆλ‹€. 단방ν–₯은 말 κ·Έλž˜λ„ ν•œ μͺ½μ—μ„œ λ‹€λ₯Έ 관계λ₯Ό μ°Έμ‘°ν•˜λŠ” 것이고, μ–‘λ°©ν–₯은 μ–‘μͺ½μ—μ„œ μ°Έμ‘°κ°€ κ°€λŠ₯ν•©λ‹ˆλ‹€. μ—°κ΄€κ΄€κ³„μ—μ„œλŠ” 주인을 μ •ν•˜κ³  주인이 μ•„λ‹Œ λ°©ν–₯μ—μ„œλŠ” λ‹¨μˆœ 읽기λ₯Ό μœ„ν•΄ 주둜 μ°Έμ‘°λ₯Ό κ±Έμ§€λ§Œ, μ‹€λ¬΄μ—μ„œλŠ” μ–‘μͺ½μ˜ 데이터λ₯Ό κ°„νŽΈν•˜κ²Œ 가지고 였기 μœ„ν•΄ μ–‘λ°©ν–₯ 맀핑이 많이 μ‚¬μš©λ©λ‹ˆλ‹€.
μ—°κ΄€κ΄€κ³„μ˜ 주인은 μ™Έλž˜ν‚€κ°€ μžˆλŠ” ν…Œμ΄λΈ”(N)κ³Ό λ§€ν•‘λ˜λŠ” entity에 μ„€μ •ν•˜λŠ” 것을 ꢌμž₯λ©λ‹ˆλ‹€. κ·Έ λ°˜λŒ€μͺ½μ—λŠ” mappedBy둜 관계λ₯Ό μ•Œ 수 μžˆλŠ” λ³€μˆ˜λͺ…을 맀핑해주어야 ν•©λ‹ˆλ‹€. μ™Έλž˜ν‚€κ°€ μžˆλŠ” 곳을 주인으둜 μ •ν•˜λŠ” μ΄μœ λŠ” λ°μ΄ν„°λ² μ΄μŠ€ μž‘μ—…μ„ ν•  λ•Œ, 주인인 entityλ₯Ό μ€‘μ‹¬μœΌλ‘œ JPAλŠ” μž‘μ—…μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€. μ™Έλž˜ν‚€ 값이 같이 μ—…λ°μ΄νŠΈν•˜λ©΄μ„œ 관리와 μœ μ§€λ³΄μˆ˜κ°€ 더 νŽΈν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.
연관관계λ₯Ό μ‚¬μš©ν•  λ•ŒλŠ” 순수 객체 μƒνƒœλ₯Ό κ³ λ €ν•΄μ„œ 항상 μ–‘μͺ½μ— 값을 μ„€μ •ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ ν•œ μͺ½ entity의 setter λ©”μ„œλ“œ λŒ€μ‹  μ—°κ΄€κ΄€κ³„μ˜ λ°˜λŒ€μ—λ„ μžλ™μœΌλ‘œ set이 될 수 μžˆλ„λ‘ μ•Œμ•„λ³΄κΈ° μ‰¬μš΄ λ©”μ„œλ“œλ₯Ό λ”°λ‘œ μ§€μ •ν•΄μ£ΌλŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. κ·Έλž˜μ•Ό μ–‘μͺ½ λ‹€ 값을 μžŠμ§€ μ•Šκ³  μ„€μ •ν•˜κΈ°μ— νŽΈλ¦¬ν•©λ‹ˆλ‹€.


연관관계 μ’…λ₯˜μ™€ μ „λž΅

λ‹€λŒ€μΌ [N:1]
λ§€ν•‘μœΌλ‘œλŠ” λ‹€λŒ€μΌ μ–‘λ°©ν–₯으둜 μ‚¬μš©ν•˜λŠ” 것을 μΆ”μ²œν•©λ‹ˆλ‹€. 맨 처음 μ˜ˆμ‹œλ‘œ λ“€μ—ˆλ˜ 학생과 λ°˜μ— λŒ€ν•œ 연관관계 entityλ₯Ό μ½”λ“œλ‘œ μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€. μ•„λž˜ μ½”λ“œμ—μ„œλŠ” λ‹€λŒ€μΌ μ–‘λ°©ν–₯ 맀핑이 된 μƒνƒœμž…λ‹ˆλ‹€.

// 학생 entity
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;

@JoinColumn에 λŒ€ν•˜μ—¬
μ™Έλž˜ν‚€λ₯Ό 맀핑할 λ•Œ μ‚¬μš©ν•©λ‹ˆλ‹€. @JoinColumn을 쓰지 μ•ŠμœΌλ©΄ @JoinTable μ „λž΅μœΌλ‘œ λ™μž‘ν•˜κΈ° λ•Œλ¬Έμ— λΆˆν•„μš”ν•œ ν…Œμ΄λΈ”μ΄ ν•˜λ‚˜ 더 생기고 κ΄€λ¦¬ν•˜κΈ°μ— λΉ„νš¨μœ¨μ μž…λ‹ˆλ‹€.

μΌλŒ€λ‹€ [1:N]
λ‹€λŒ€μΌκ³Ό 달리 mappedBy 속성이 μžˆμŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ„œ μ—°κ²°ν•΄μ£ΌλŠ” λŒ€μƒμ€ μΌλŒ€λ‹€μ™€ 묢인 μƒλŒ€ 즉, μ—¬κΈ°μ„œλŠ” @ManyToOne으둜 μ„€μ •ν•œ entity μ†μ„±μ˜ λ³€μˆ˜λͺ…을 μž‘μ„±ν•΄μ£Όλ©΄ λ©λ‹ˆλ‹€.

// νŒ€ entity
@OneToMany(mappedBy="student")
@JoinColumn(name="STUDENT_ID")
private List<Student> students = new ArrayList<>();

μΌλŒ€μΌ [1:1]
μΌλŒ€μΌ μ–‘λ°©ν–₯은 λ‹€λŒ€μΌ μ–‘λ°©ν–₯ 맀핑과 μœ μ‚¬ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ μΌλŒ€μΌμ΄κΈ° λ•Œλ¬Έμ— μ–΄λŠ λ°©ν–₯에 μ™Έλž˜ν‚€λ₯Ό 넣어도 λ˜μ§€λ§Œ, ν–₯ν›„ κΈ°λŠ₯ 좔가와 DBAμ™€μ˜ ν˜‘μ—…μœΌλ‘œ μ–΄λ–»κ²Œ 주인을 섀정할지 고민해봐야 ν•©λ‹ˆλ‹€. 일반적으둜 전톡적인 λ°μ΄ν„°λ² μ΄μŠ€ 섀계와 λ™μΌν•˜κ²Œ λŒ€μƒν…Œμ΄λΈ”μ— μ™Έλž˜ ν‚€λ₯Ό λ„£λŠ” λ°©μ‹μœΌλ‘œ ν•˜λ©΄, λ‚˜μ€‘μ— μΌλŒ€λ‹€ λ§€ν•‘μœΌλ‘œ λ³€κ²½μ‹œμ—λ„ ν…Œμ΄λΈ” ꡬ쑰가 μœ μ§€λœλ‹€λŠ” μž₯점이 μžˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ, ν”„λ‘μ‹œ κΈ°λŠ₯의 ν•œκ³„λ‘œ μ§€μ—°λ‘œλ”©μœΌλ‘œ 섀정해도 항상 μ¦‰μ‹œλ‘œλ”©λœλ‹€λŠ” 단점이 μžˆμŠ΅λ‹ˆλ‹€.

// νŒ€ entity
@OneToOne(mappedBy="student")
@JoinColumn(name="STUDENT_ID")
private Student student;

// 학생 entity
@OneToOne
@JoinColumn(name="TEAM_ID")
private Team team;

λ‹€λŒ€λ‹€ [N:M]
κ΄€κ³„ν˜• λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œλŠ” ν‘œν˜„ν•  수 μ—†λŠ” λ°©μ‹μœΌλ‘œ μ‹€λ¬΄μ—μ„œ μ‚¬μš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 쀑간 ν…Œμ΄λΈ”μ„ λ§Œλ“€μ–΄ μΌλŒ€λ‹€ λ˜λŠ” λ‹€λŒ€μΌ κ΄€κ³„λ‘œ λ§Œλ“€μ–΄μ•Ό ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ μ€‘κ°„ν…Œμ΄λΈ”μ„ λ”°λ‘œ κ΄€λ¦¬ν•˜λŠ” 것보닀 차라리 μ€‘κ°„ν…Œμ΄λΈ”μ„ entity둜 μŠΉκ²©ν•΄μ„œ κ΄€λ¦¬ν•˜λŠ” 것을 ꢌμž₯λ©λ‹ˆλ‹€.

@ManyToMany(fetch=LAZY)
@JoinTable(name="CATEGORY_ITEM",
  joinColumn=@JoinColumn(name="CATEGORY_ID"),
  inverseJoinColumns=@JoinColumn(name="ITEM_ID")      
)
private List<Item> items = new ArrayList<>();


μ¦‰μ‹œλ‘œλ”©κ³Ό μ§€μ—°λ‘œλ”©
연관관계 μ†μ„±μ—μ„œ fetch μ „λž΅μ„ fetchType.LAZY으둜 μ§€μ—°λ‘œλ”©μœΌλ‘œ μ„ νƒν•˜κ±°λ‚˜, fetchType.EAGER으둜 μ¦‰μ‹œλ‘œλ”©μ„ μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ μ¦‰μ‹œλ‘œλ”©μ„ ν•  경우 μ„±λŠ₯이 λ‚˜λΉ μ§€κ±°λ‚˜ μ˜ˆμƒμΉ˜ λͺ»ν•œ 쿼리듀이 λ°œμƒν•˜κΈ° λ•Œλ¬Έμ— μ‚¬μš©μ„ ν”Όν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. @ManyToOne, @OneToOne은 기본값이 μ¦‰μ‹œλ‘œλ”©μ΄λ―€λ‘œ @ManyToOne(fetch=fetchType.LAZY)와 같이 λ°˜λ“œμ‹œ μ§€μ—°λ‘œλ”© μ„€μ •ν•΄μ„œ μœ„ν—˜μ„ 방지해야 ν•©λ‹ˆλ‹€.
λŒ€μ‹  JPQL fetch μ‘°μΈμ΄λ‚˜ μ—”ν‹°ν‹° κ·Έλž˜ν”„ κΈ°λŠ₯을 μ‚¬μš©ν•΄μ„œ 이 문제λ₯Ό ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


μ˜μ†μ„± 전이 CASCADE
νŠΉμ • μ—”ν‹°ν‹°λ₯Ό μ˜μ† μƒνƒœλ‘œ λ§Œλ“€ λ•Œ μ—°κ΄€λœ 엔티티도 ν•¨κ»˜ μ˜μ† μƒνƒœλ‘œ λ§Œλ“€κ³  싢을 λ•Œ μ‚¬μš©λ©λ‹ˆλ‹€. 연관관계 λ§€ν•‘κ³ΌλŠ” μ•„λ¬΄λŸ° 관련이 μ—†μ§€λ§Œ cascade=CascadeType.PERSIST와 같이 μ–΄λ…Έν…Œμ΄μ…˜ μ „λž΅μœΌλ‘œ μž‘μ„±λ˜κΈ° λ•Œλ¬Έμ— 여기에 λΆ„λ₯˜ν–ˆμŠ΅λ‹ˆλ‹€. CASECADE μ’…λ₯˜μ—λŠ” ALL, PERSIST, REMOVE, MERGE, REFRESH, DETACHκ°€ μžˆμŠ΅λ‹ˆλ‹€. μ˜μ†μ„± 전이가 λ‹€λ₯Έ λΆ€λͺ¨μ™€ 연관이 μ—†λŠ” μžμ‹ μ—”ν‹°ν‹°μ—λ§Œ μ μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.


고아객체
κ³ μ•„κ°μ²΄λŠ” λΆ€λͺ¨ 엔티티와 관계가 λŠμ–΄μ§„ μžμ‹ μ—”ν‹°ν‹°λ₯Ό λ§ν•©λ‹ˆλ‹€. orphanRemoval = true와 같이 μ‚¬μš©λ˜λŠ”λ° μ„€μ • ν›„ λΆ€λͺ¨ μ—”ν‹°ν‹°μ˜ μžμ‹ λ¦¬μŠ€νŠΈμ—μ„œ ν•˜λ‚˜λ₯Ό μ‚­μ œν•˜λ©΄, κ·Έ μžμ‹ μ—”ν‹°ν‹°μ™€λŠ” 관계λ₯Ό 맺지 μ•ŠμŠ΅λ‹ˆλ‹€. νŠΉμ • μ—”ν‹°ν‹°κ°€ 개인 μ†Œμœ ν•  λ•Œλ§Œ μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. CascadeType.REMOVE와 같이 λΆ€λͺ¨ μ—”ν‹°ν‹°κ°€ μ‚­μ œλ˜λŠ” 경우, μžμ‹ 엔티티도 같이 μ œκ±°λ©λ‹ˆλ‹€.
CASCADE와 고아객체λ₯Ό λͺ¨λ‘ μ‚¬μš©ν•˜λ©΄ μžμ‹ μ—”ν‹°ν‹°μ˜ 생λͺ…μ£ΌκΈ°λ₯Ό λͺ¨λ‘ 관리할 수 μžˆμŠ΅λ‹ˆλ‹€.(DDD의 Aggregate Root κ°œλ… κ΅¬ν˜„μ‹œ 유용)


상속관계 맀핑

@Inheritance
상속관계가 μžˆλŠ” entityλ₯Ό λ§Œλ“€λ•Œ, JPAλŠ” extends된 μ—”ν‹°ν‹°λ₯Ό λͺ¨λ‘ ν•˜λ‚˜μ˜ 단일 ν…Œμ΄λΈ”λ‘œ ν•˜λŠ” μ „λž΅μ„ 기본으둜 ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ λ‹€λ₯Έ λ°©μ‹μœΌλ‘œ 상속맀핑을 μ²˜λ¦¬ν•˜κ³  싢은 κ²½μš°μ—λŠ” @Inheritance의 λ‹€λ₯Έ μ „λž΅μ„ μ‚¬μš©ν•΄μ£Όλ©΄ λ©λ‹ˆλ‹€.

@Inheritance(strategy=InherianceType.JOINED)          // μ‘°μΈμ „λž΅
@Inheritance(strategy=InherianceType.SINGLE_TABLE)    // 단일 ν…Œμ΄λΈ” μ „λž΅
@Inheritance(strategy=InherianceType.TABLE_PER_CLASS) // κ΅¬ν˜„ ν΄λž˜μŠ€λ§ˆλ‹€ ν…Œμ΄λΈ” μ „λž΅

단일 ν…Œμ΄λΈ” μ „λž΅μ—μ„œλŠ” μƒμ†λœ ν…Œμ΄λΈ”μ˜ λͺ¨λ“  μ»¬λŸΌμ„ 가지고 있기 λ•Œλ¬Έμ— null을 ν—ˆμš©ν•΄μ•Ό ν•˜λŠ” 뢀뢄이 λ§Žλ‹€λŠ” 단점이 μžˆμŠ΅λ‹ˆλ‹€. ν…Œμ΄λΈ” κ°„μ˜ κ΄€κ³„λ‚˜ ν™•μž₯을 κ³ λ €ν•΄μ„œ μ€‘μš”ν•œ 섀계가 ν•„μš”ν•œ κ²½μš°μ—λŠ” JOIN μ „λž΅μ„ μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. TABLE_PER_CLASS μ „λž΅μ€ 상속 맀핑을 νŒŒμ•…ν•˜κΈ°μ— 쒋지 μ•ŠκΈ° λ•Œλ¬Έμ— μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

@DiscriminatorColumn
λΆ€λͺ¨ ν΄λž˜μŠ€μ—μ„œ ꡬ뢄을 μœ„ν•΄μ„œ μ‚¬μš©ν•©λ‹ˆλ‹€. 단일 ν…Œμ΄λΈ” μ „λž΅μ—μ„œλŠ” μžλ™μœΌλ‘œ @DiscriminatorColumnλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šμ•„λ„ ꡬ뢄이 λ˜λŠ” 컬럼으둜 DTYPE이 μƒμ„±λ©λ‹ˆλ‹€. ν•˜μ§€λ§Œ JOIN μ „λž΅μ—μ„œλŠ” DTYPE이 μƒμ„±λ˜μ§€ μ•Šκ³  ν•˜μœ„ ν…Œμ΄λΈ”μ— μ œμ•½μ‘°κ±΄μ΄ μƒμ„±λ˜κΈ° λ•Œλ¬Έμ— λ³„λ„μ˜ κ΅¬λΆ„μš© 컬럼이 ν•„μš”ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

@DiscriminatorValue
μžμ‹ ν΄λž˜μŠ€μ—μ„œ λΆ€λͺ¨ν΄λž˜μŠ€ @DiscriminatorColumn용 μ»¬λŸΌμ— λ“€μ–΄κ°ˆ κ°’μœΌλ‘œ μƒμ†κ΄€κ³„μ˜ 맀핑 ν…Œμ΄λΈ”μ„ κ΅¬λΆ„ν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•©λ‹ˆλ‹€. μžμ‹ν΄λž˜μŠ€μ— DTYPE에 λ“€μ–΄κ°ˆ 값을 지정할 수 μžˆμŠ΅λ‹ˆλ‹€.

@MappedSupperclass
곡톡 맀핑 속성정보λ₯Ό μ‚¬μš©ν•˜κ³  싢을 λ•Œλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. λͺ¨λ“  ν…Œμ΄λΈ”μ— λ™μΌν•˜κ²Œ λˆ„κ°€ μˆ˜μ •ν•˜κ³ , μˆ˜μ •μΌμ‹œκ°€ μ–Έμ œμΈμ§€μ— λŒ€ν•œ 정보가 ν•„μš”ν•˜λ‹€κ³  ν•œλ‹€λ©΄, BaseEntityλ₯Ό λ§Œλ“€μ–΄ λ§€ν•‘μ •λ³΄λ§Œ μ œκ³΅ν•  수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€. 이 classλŠ” entityλŠ” μ•„λ‹ˆκ³  λ‹¨μˆœνžˆ 곡톡 속성을 λΆ€μ—¬ν•˜κΈ° μœ„ν•œ κ²ƒμœΌλ‘œ 상속관계 ν…Œμ΄λΈ”μ΄ μƒμ„±λ˜λŠ” κ°œλ…μ΄ μ•„λ‹™λ‹ˆλ‹€.


μ½”λ“œλ‘œ μ‚΄νŽ΄λ³΄κΈ°
상속 κ΄€κ³„μ˜ λΆ€λͺ¨ν΄λž˜μŠ€μ— @Inheritance와 @DiscriminatorColumnλ₯Ό μ§€μ •ν•΄μ„œ μžμ‹ ν΄λž˜μŠ€κ°€ 상속될 수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€. 이 λ•Œ, μžμ‹ ν΄λž˜μŠ€μ—λŠ” @DiscriminatorValue둜 DTYPE에 λ“€μ–΄κ°ˆ 값을 μ •ν•΄μ£Όκ³ , extendsλ₯Ό ν‘œκΈ°ν•΄ μƒμ†μž„μ„ λ‚˜νƒ€λƒ…λ‹ˆλ‹€.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@Getter
@Setter
@Table(name = "ITEM")
public class Item {
  @Id @GeneratedValue
  @Column(name="item_id")
  private Long id;

  private String name;
  private int price;
  private int stockQuantity;

  @ManyToMany(mappedBy = "items")
  private List<Category> categories = new ArrayList<>();
}

@Entity
@DiscriminatorValue(value = "M")
@Getter
@Setter
public class Movie extends Item{
    private String director;
    private String actor;
}


ν”„λ‘μ‹œμ™€ 연관관계

μ‹€μ œ 클래슀λ₯Ό μƒμ†λ°›μ•„μ„œ λ§Œλ“€μ–΄μ§„ κ²ƒμœΌλ‘œ ν”„λ‘μ‹œλ₯Ό μ΄μš©ν•΄μ„œ μ‘°νšŒν•˜λŠ” κΈ°λŠ₯인 em.getReference()λ₯Ό μ‚¬μš©ν•˜λŠ” 경우 ν”„λ‘μ‹œ 객체λ₯Ό μ‘°νšŒν•˜λ©°, μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— 이미 entityκ°€ μžˆλ‹€λ©΄ μ‹€μ œ entityλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. 참고둜, em.find()λŠ” μ‹€μ œ 객체λ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€.


ν”„λ‘μ‹œ 객체와 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ
μ—¬κΈ°μ„œ ν”„λ‘μ‹œ κ°μ²΄λž€ JPAμ—μ„œ μ‹€μ œ λ°μ΄ν„°λ² μ΄μŠ€ 쑰회λ₯Ό 지연할 수 있게 ν•˜λŠ” κ°€μ§œ κ°μ²΄μž…λ‹ˆλ‹€. ν”„λ‘μ‹œ κ°μ²΄λŠ” μ‹€μ œ 객체λ₯Ό target λ³€μˆ˜λ‘œ 가지고 있고, 쑰회λ₯Ό ν•˜λ©΄ κ·Έ μ°Έμ‘°κ°’μœΌλ‘œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—κ²Œ μ΄ˆκΈ°ν™” μš”μ²­μ„ λ³΄λ‚΄μ„œ μ‹€μ œ 객체λ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€. ν•œ 번 쑰회된 μ΄ν›„μ—λŠ” target μ°Έμ‘°κ°€ 걸리기 λ•Œλ¬Έμ— 두 λ²ˆμ§ΈλΆ€ν„°λŠ” μ΄ˆκΈ°ν™” μš”μ²­μ„ ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
μ •λ¦¬ν•˜λ©΄, ν”„λ‘μ‹œ 객체λ₯Ό μ΄ˆκΈ°ν™” ν•  λ•Œ, ν”„λ‘μ‹œ 객체가 μ‹€μ œ μ—”ν‹°ν‹°λ‘œ λ°”λ€ŒλŠ” 것은 μ•„λ‹ˆλ©°, μ΄ˆκΈ°ν™”λ˜λ©΄ ν”„λ‘μ‹œ 객체λ₯Ό ν†΅ν•΄μ„œ μ‹€μ œ 엔티티에 μ ‘κ·Ό κ°€λŠ₯이 κ°€λŠ₯ν•©λ‹ˆλ‹€.


μ½”λ“œλ‘œ μ‚΄νŽ΄λ³΄κΈ°
μ—¬κΈ°μ„œ m1, m2λŠ” λͺ¨λ‘ 같은 ν”„λ‘μ‹œ κ°μ²΄μ—μ„œ μ‘°νšŒν•΄ 온 것을 μ˜λ―Έν•©λ‹ˆλ‹€. λ”°λΌμ„œ μ—¬κΈ°μ„œλŠ” 동등비ꡐ(==)λ₯Ό 해도 trueκ°€ λ‚˜μ˜€λŠ” 것을 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. ν”„λ‘μ‹œ 객체 νƒ€μž…μ„ 비ꡐ할 λ•ŒλŠ” instanceOfλ₯Ό μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Member m1 = em.getReference(Member.class, "m1Id");
Member m2 = em.getReference(Member.class, "m1Id");
System.out.println(m1 == m2); // true

μ²˜μŒμ—λŠ” ν”„λ‘μ‹œ 객체λ₯Ό μƒμ„±ν•˜κ³ , getName() 쑰회λ₯Ό ν•˜λŠ” μˆœκ°„, μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ΄ˆκΈ°ν™” μš”μ²­μœΌλ‘œ μ‹€μ œ 객체λ₯Ό μ°Έμ‘°ν•˜κ²Œ λ©λ‹ˆλ‹€. 이후 같은 객체λ₯Ό μ„ μ–Έν•˜λŠ” m2μ—λŠ” μ‹€μ œ 객체λ₯Ό λ°”λ‘œ μ‘°νšŒν•˜κ²Œ λ©λ‹ˆλ‹€. 이미 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œ κ΄€λ¦¬ν•˜λŠ” 객체이기 λ•Œλ¬Έμž…λ‹ˆλ‹€.

Member m1 = em.getReference(Member.class, "m1Id");  // ν”„λ‘μ‹œ 객체
m1.getName(); // μ΄ˆκΈ°ν™” μš”μ²­
Member m2 = em.getReference(Member.class, "m1Id");  // μ‹€μ œ 객체

μžλ°” ORM ν‘œμ€€ JPA ν”„λ‘œκ·Έλž˜λ° - 기본편
Proxy Objects and Eager & Lazy Fetch Types in Hibernate
[JPA] μ˜μ†μ„± 전이, κ³ μ•„ 객체 (cascade λ²”μœ„)



OSIV

μŠ€ν”„λ§λΆ€νŠΈμ—μ„œλŠ” Open Session In Viewλ₯Ό κΈ°λ³Έκ°’μœΌλ‘œ ν•˜κ³  있기 λ•Œλ¬Έμ— μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό λ·° λ Œλ”λ§μ΄ λλ‚˜λŠ” μ‹œμ κΉŒμ§€ κ°œλ°©ν•œ μƒνƒœλ‘œ μœ μ§€ν•©λ‹ˆλ‹€.
μ€€μ˜μ† μƒνƒœμ—μ„œλŠ” ν”„λ‘μ‹œλ₯Ό μ΄ˆκΈ°ν™”ν•˜λ©΄ hibernateλŠ” org.hibernate.LazyInitializationException μ˜ˆμ™Έκ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이 λ¬Έμ œλŠ” μŠ€ν”„λ§ @Transaction을 μ‚¬μš©ν•˜λŠ” κ³Όμ •μ—μ„œ λ°œμƒν•˜λŠ”λ°, μ„œλΉ„μŠ€λ‹¨μ—μ„œ 받은 entityλ₯Ό μ»¨νŠΈλ‘€λŸ¬μ—μ„œ mapper둜 DTO μ²˜λ¦¬ν•˜λŠ” κ³Όμ •μ—μ„œ ν”νžˆ λ³Ό 수 μžˆλŠ” λ¬Έμ œμž…λ‹ˆλ‹€. @Transaction이 λͺ…μ‹œλœ μ„œλΉ„μŠ€λ‹¨ λ©”μ„œλ“œκ°€ μ’…λ£Œλ˜λ©΄, μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œ κ·Έ 객체λ₯Ό κ΄€λ¦¬ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— μ‹€μ œ 객체둜 mapper 처리λ₯Ό μ œλŒ€λ‘œ ν•˜μ§€ λͺ»ν•˜λŠ” λ¬Έμ œκ°€ λ°œμƒν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.


μ‹€μ „! μŠ€ν”„λ§ λΆ€νŠΈμ™€ JPA ν™œμš©2 - API 개발과 μ„±λŠ₯ μ΅œμ ν™”



데이터 νƒ€μž…

JPA νƒ€μž…μ€ μ—”ν‹°ν‹° νƒ€μž…(@Entity μ‹λ³„μžλ‘œ 인식)κ³Ό κ°’ νƒ€μž…(μ‹λ³„μž 없이 κ°’λ§Œ μžˆλŠ” 좔적 λΆˆκ°€λŠ₯ν•œ νƒ€μž…)으둜 λΆ„λ₯˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€. κ°’ νƒ€μž…μ€ 좔적이 λΆˆκ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ— μ ˆλŒ€ 곡유되면 μ•ˆλ©λ‹ˆλ‹€. 즉, Interger, String 같은 κ³΅μœ κ°€λŠ₯ν•œ μ£Όμ†Œκ°’λ§Œ λ„˜μ–΄κ°€κΈ° λ•Œλ¬Έμ— 변경이 λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€. JPAμ—μ„œ κ°’ νƒ€μž…μ˜ μ’…λ₯˜λ‘œλŠ” κΈ°λ³Έκ°’ νƒ€μž…, μž„λ² λ””λ“œ νƒ€μž…, μ»¬λ ‰μ…˜ κ°’ νƒ€μž…μ΄ μžˆμŠ΅λ‹ˆλ‹€.
κ°’ νƒ€μž…μ„ μ›μ‹œνƒ€μž…μΈ κ²½μš°λŠ” 동일성 λΉ„κ΅λ‘œ ==λ₯Ό μ‚¬μš©ν•˜λ©΄ λ˜μ§€λ§Œ, κ·Έ 외에 μž„λ² λ””λ“œ νƒ€μž…κ³Ό 같은 값을 비ꡐ할 λ•ŒλŠ” equals()λ₯Ό μ‚¬μš©ν•΄μ„œ 동등성 비ꡐλ₯Ό ν•΄μ•Ό ν•©λ‹ˆλ‹€.
immutableν•œ 객체에 λŒ€ν•΄ 더 μžμ„Ένžˆ μ•Œμ•„λ³΄μž!
Javaμ—μ„œ Integerλ‚˜ String은 immutableν•œ κ°μ²΄μž…λ‹ˆλ‹€. μ΄λŸ¬ν•œ 객체듀은 곡유 κ°€λŠ₯ν•œ μ£Όμ†Œκ°’λ§Œ λ„˜μ–΄κ°€κΈ° λ•Œλ¬Έμ— 변경이 λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, Integer a = 5; 와 Integer b = a; λΌλŠ” μ½”λ“œκ°€ μžˆλ‹€λ©΄, a와 bλŠ” 같은 μ£Όμ†Œκ°’μ„ κ³΅μœ ν•˜κ²Œ λ©λ‹ˆλ‹€. λ”°λΌμ„œ aλ‚˜ b 쀑 ν•˜λ‚˜λ₯Ό λ³€κ²½ν•˜λ©΄ λ‹€λ₯Έ ν•˜λ‚˜λ„ ν•¨κ»˜ λ³€κ²½λ˜λŠ” 것이 μ•„λ‹ˆλΌ, μƒˆλ‘œμš΄ 객체가 μƒμ„±λ©λ‹ˆλ‹€. JPA의 μ—”ν‹°ν‹° μΌλΆ€λ‘œ μ‚¬μš©λ  수 μžˆλŠ” κ°’ νƒ€μž…μž…λ‹ˆλ‹€.


μž„λ² λ””λ“œ νƒ€μž…(볡합 κ°’ νƒ€μž…)
μž„λ² λ””λ“œ νƒ€μž…λ„ μ—”ν‹°ν‹° νƒ€μž…μ΄ μ•„λ‹ˆκΈ° λ•Œλ¬Έμ— 좔적이 λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€. Member μ—”ν‹°ν‹°μ—μ„œ Address 객체λ₯Ό κ°’ νƒ€μž…μ²˜λŸΌ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. @Embedded으둜 μž„λ² λ””λ“œ νƒ€μž…μ˜ κ°’μž„μ„ λͺ…μ‹œν•˜κ³ , @Embeddable둜 ν΄λž˜μŠ€κ°€ μž„λ² λ””λ“œ νƒ€μž…μž„μ„ λͺ…μ‹œν•©λ‹ˆλ‹€. ν˜„μ—…μ—μ„œλŠ” μ£Όμ†Œλ‚˜ μ—°λ½μ²˜μ™€ 같이 μ—”ν‹°ν‹°μ—μ„œ 자주 μ‚¬μš©λ˜λŠ” 값듀을 μž„λ² λ””λ“œ νƒ€μž…μœΌλ‘œ μ •μ˜ν•˜μ—¬ μ‚¬μš©ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, νšŒμ›(Member) μ—”ν‹°ν‹°μ—μ„œλŠ” νšŒμ›μ˜ 이름, λ‚˜μ΄, μ£Όμ†Œ, μ—°λ½μ²˜ 등이 자주 μ‚¬μš©λ˜λŠ”λ°, μ΄λŸ¬ν•œ 값듀을 μž„λ² λ””λ“œ νƒ€μž…μœΌλ‘œ μ •μ˜ν•˜μ—¬ Member μ—”ν‹°ν‹°μ—μ„œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. κ°’ νƒ€μž…μ„ λ³„λ„μ˜ ν…Œμ΄λΈ”λ‘œ λΆ„λ¦¬ν•˜μ—¬ μ €μž₯ν•˜λ©΄ 쑰인이 λ°œμƒν•˜λ―€λ‘œ μ„±λŠ₯이 μ €ν•˜λ  수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ κ²½μš°μ—λ„ μž„λ² λ””λ“œ νƒ€μž…μ„ μ‚¬μš©ν•˜λ©΄ Member ν…Œμ΄λΈ”κ³Ό ν•¨κ»˜ μ €μž₯λ˜λ―€λ‘œ μ„±λŠ₯상 이점이 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, μž„λ² λ””λ“œ νƒ€μž…μ„ μ‚¬μš©ν•˜λ©΄ μ½”λ“œμ˜ 가독성이 쒋아지고, 객체λ₯Ό λ‹€λ£¨λŠ” μ½”λ“œκ°€ κ°„κ²°ν•΄μ§‘λ‹ˆλ‹€. λ”°λΌμ„œ ν˜„μ—…μ—μ„œλ„ μž„λ² λ””λ“œ νƒ€μž…μ„ 자주 μ‚¬μš©ν•˜λŠ”λ°, μ΄λŠ” 객체지ν–₯적인 섀계λ₯Ό ν•  수 있게 ν•΄μ£ΌκΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.
μ•„λž˜ μ½”λ“œλ‘œ 보면, Address 객체λ₯Ό λ”°λ‘œ ν…Œμ΄λΈ”λ‘œ λ§Œλ“€μ§€ μ•Šκ³  Member ν…Œμ΄λΈ”κ³Ό ν•¨κ»˜ 개개의 컬럼으둜 μƒμ„±λ˜μ–΄ μ €μž₯λ©λ‹ˆλ‹€.

@Entity
public class Member {
    @Id
    private Long id;
    private String name;
    @Embedded
    private Address address;
    // ...
}

@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;
    // ...
}

μž„λ² λ””λ“œ νƒ€μž…μ—μ„œ @Embeddable 객체λ₯Ό 곡유 μ°Έμ‘°ν•˜λŠ” 경우, κ·Έ 객체λ₯Ό μ‚¬μš©ν•˜λŠ” entityλ“€μ—μ„œ λͺ¨λ‘ 변경이 λ°œμƒν•˜λŠ” λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이 경우 좔적이 μ–΄λ ΅κΈ° λ•Œλ¬Έμ— λ°˜λ“œμ‹œ 곡유 μ°Έμ‘°λ₯Ό ν•˜λ €λŠ” λŒ€μƒμ„ λΆˆλ³€κ°μ²΄λ‘œ 생성해야 ν•©λ‹ˆλ‹€. setter λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šμ•„μ•Ό ν•˜λ©°, λ³€κ²½ν•  ν•„μš”κ°€ μžˆλŠ” κ²½μš°μ—λŠ” μƒˆλ‘œμš΄ 객체λ₯Ό λ‹€μ‹œ λ§Œλ“€μ–΄μ„œ 값을 μˆ˜μ •ν•˜λŠ” 방식이 μ•ˆμ „ν•©λ‹ˆλ‹€.

[λΆˆλ³€κ°μ²΄λ‘œ μ„€μ •ν•˜μ§€ μ•Šμ€ 경우의 문제점]

Address address = new Address("city", "street", "10000");

Member member1 = new Member();
member1.setName("member1");
member1.setHomeAddress(address);

Member member2 = new Member();
member2.setName("member2");
member2.setHomeAddress(address);

em.persist(member1);
em.persist(member2);

address.setCity("seoul"); 
// member1κ³Ό member2 λͺ¨λ‘ address의 cityκ°€ 'seoul'둜 λ³€κ²½λ©λ‹ˆλ‹€.

[λΆˆλ³€κ°μ²΄λ‘œ μ„€μ •ν–ˆμ„ λ•Œμ˜ μž„λ² λ””λ“œ νƒ€μž…μ˜ κ°’ λ³€κ²½]

Address address = new Address("city", "street", "10000");

Member member1 = new Member();
member1.setName("member1");
member1.setHomeAddress(address);
em.persist(member1);
        
// μˆ˜μ •μ΄ ν•„μš”ν•œ 경우
Address newAddress = new Address("seoul", address.getStreet(), address.getZipcode());
member1.setHomeAddress(newAddress);


BaseEntityλ₯Ό μ‚¬μš©ν•˜λ©΄ λ˜μ§€ μ•Šμ„κΉŒ?
μ—¬κΈ°μ„œ 이런 의문이 λ“€μ—ˆμŠ΅λ‹ˆλ‹€. 곡톡 속성이라면 BaseEntity둜 κ΄€λ¦¬ν•˜λŠ” 방법도 μžˆλŠ”λ°, μ»¬λŸΌμ— λŒ€ν•œ μž„λ² λ””λ“œ κ°’ νƒ€μž…μœΌλ‘œ μ„€μ •ν•˜λŠ”κ²Œ μ–΄λ–€ λΆ€λΆ„μ—μ„œ 크게 λ‹€λ₯Έμ§€ μƒκ°ν•΄λ΄€μŠ΅λ‹ˆλ‹€.
@Embedded와 @Embeddable을 μ‚¬μš©ν•˜λ©΄ ν•˜λ‚˜μ˜ μ—”ν‹°ν‹°μ—μ„œ μ—¬λŸ¬ 개의 값을 κ·Έλ£Ήν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ κ·Έλ£Ήν™”λœ 값은 λ‹€λ₯Έ μ—”ν‹°ν‹°μ—μ„œλ„ μž¬μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, κ°’ νƒ€μž…μ„ λ³„λ„μ˜ ν…Œμ΄λΈ”λ‘œ λΆ„λ¦¬ν•˜μ—¬ 관리할 수 있기 λ•Œλ¬Έμ— 데이터 일관성과 쀑볡성을 쀄일 수 μžˆμŠ΅λ‹ˆλ‹€.
κ°œλ…μ μΈ λΆ€λΆ„μ—μ„œλ„ BaseEntityλŠ” 상속을 ν•˜λŠ” κ°œλ…μœΌλ‘œ 곡톡 μ»¬λŸΌλ“€μ„ μ •μ˜ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ @Embeddedλ₯Ό μ΄μš©ν•˜λŠ”κ±΄ μ£Όμ†ŒλΌλŠ” 컬럼 내에 우편번호, κΈ°λ³Έμ£Όμ†Œ, μƒμ„Έμ£Όμ†Œμ™€ 같은 ν•˜λ‚˜μ˜ κ·Έλ£Ήν™”λœ 속성듀을 μ§€μ •ν•œλ‹€λŠ” μ μ—μ„œ 차이가 μžˆμŠ΅λ‹ˆλ‹€. 결둠적으둜 μ—¬λŸ¬ μ—”ν‹°ν‹°μ˜ 곡톡 속성을 λ¬Άμ—μ„œ κ΄€λ¦¬ν•˜κΈ° μœ„ν•΄μ„œλŠ” 상속 κ°œλ…μ„ μ΄μš©ν•˜λŠ” 것이 λ°”λžŒμ§ν•˜κ³ , ν•˜λ‚˜μ˜ 속성을 μ„ΈλΆ€ 속성듀을 κ·Έλ£Ήν™”ν•΄μ„œ μ‚¬μš©ν•˜κΈ° 쒋은 것은 μž„λ² λ””λ“œ κ°’ νƒ€μž…μ„ μ‚¬μš©ν•˜λŠ” 것이 μ μ ˆν•©λ‹ˆλ‹€.


μ»¬λ ‰μ…˜ κ°’ νƒ€μž…
κ°’ νƒ€μž…μ„ ν•˜λ‚˜ 이상 μ €μž₯ν•  λ•Œ μ‚¬μš©λ˜λ©°, μžλ°”μ˜ μ»¬λ ‰μ…˜μ„ μ‚¬μš©ν•©λ‹ˆλ‹€. κ°’ νƒ€μž… μ»¬λ ‰μ…˜μ€ CASCADEλ₯Ό κΈ°λ³Έκ°’μœΌλ‘œ κ°–κ³  있으며, μ˜μ†μ„± 전에(Cascade) + κ³ μ•„ 객체 제거 κΈ°λŠ₯을 ν•„μˆ˜λ‘œ 가진닀고 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. λ§Œμ•½ κ°’ νƒ€μž… μ»¬λ ‰μ…˜μ— λ³€κ²½ 사항이 λ°œμƒν•˜λ©΄, 주인 엔티티와 μ—°κ΄€λœ λͺ¨λ“  데이터λ₯Ό μ‚­μ œν•˜κ³ , κ°’ νƒ€μž… μ»¬λ ‰μ…˜μ— μžˆλŠ” ν˜„μž¬ 값을 λͺ¨λ‘ λ‹€μ‹œ μ €μž₯ν•©λ‹ˆλ‹€.
@ElementCollection을 μ‚¬μš©ν•΄μ„œ μ»¬λ ‰μ…˜ νƒ€μž…μ„ μ‚¬μš©ν•  수 μžˆλŠ”λ°, μžλ™μœΌλ‘œ λΆ€λͺ¨ idλ₯Ό κΈ°λ³Έν‚€λ‘œ κ°–λŠ” ν…Œμ΄λΈ”μ΄ member_rolesλΌλŠ” ν…Œμ΄λΈ”μ΄ μžλ™μœΌλ‘œ μƒμ„±λ©λ‹ˆλ‹€. @CollectionTable으둜 μ»¬λ ‰μ…˜ κ°’ νƒ€μž…μ— μ‚¬μš©λ  ν…Œμ΄λΈ” 속성을 λͺ…μ‹œμ μœΌλ‘œ 지정해 쀄 수 μžˆμŠ΅λ‹ˆλ‹€.

@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    @Setter
    private Long id;
  
    @Column
    @ElementCollection
    private List<String> roles = new ArrayList<>();
    
    @ElementCollection 
    @CollectionTable(name="OPTIONS", joinColumns=@JoinColumn(name="MEMBER_ID")) 
    private List<String> options = new ArrayList<>();
}

μ»¬λ ‰μ…˜ κ°’ νƒ€μž…μ€ μ§€μ—°λ‘œλ”© μ „λž΅μ„ μ‚¬μš©ν•˜κΈ° λ•Œλ¬Έμ— μ»¬λ ‰μ…˜ κ°’ νƒ€μž…μ„ μ‘°νšŒν•˜λŠ” λ“± ν•„μš”ν•œ μˆœκ°„μ— 쿼리가 μƒμ„±λ©λ‹ˆλ‹€.

Membmer member = em.find(Member.class, member.getId());  // member만 쑰회
member.getRols();  // μ»¬λ ‰μ…˜ κ°’ νƒ€μž… 쑰회


JPA νƒ€μž…μ— λŒ€ν•΄ μ΄κ²ƒλ§Œμ€ μ•Œκ³ κ°€μž!

  1. μž„λ² λ””λ“œ νƒ€μž…μ„ μ»¬λ ‰μ…˜ κ°’ νƒ€μž…μœΌλ‘œ ν•¨κ»˜ 혼용 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  2. ν˜„μ—…μ—μ„œλŠ” μ»¬λ ‰μ…˜ κ°’ νƒ€μž…μ΄ μ•„μ£Ό κ°„λ‹¨ν•œ μ˜μ—­μ΄ μ•„λ‹Œ 경우, μΌλŒ€λ‹€ λ˜λŠ” λ‹€λŒ€μΌ μ—°κ΄€κ΄€κ³„λ‘œ λ³€κ²½ν•΄μ„œ μ‚¬μš©ν•΄ μ—”ν‹°ν‹° 좔적을 μš©μ΄ν•˜κ²Œ μ‚¬μš©ν•©λ‹ˆλ‹€.
  3. 좔적이 λΆˆκ°€λŠ₯ν•œ μž„λ² λ””λ“œλ‚˜ μ»¬λ ‰μ…˜ κ°’ νƒ€μž…μ„ μ‚¬μš©ν•  경우 λ°˜λ“œμ‹œ λΆˆλ³€κ°μ²΄λ‘œ λ§Œλ“€μ–΄μ„œ μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

μžλ°” ORM ν‘œμ€€ JPA ν”„λ‘œκ·Έλž˜λ° - 기본편
[JPA] κ°’ νƒ€μž…κ³Ό λΆˆλ³€ 객체 - κ°’ νƒ€μž… (2)
[JPA] κ°’ νƒ€μž… μ»¬λ ‰μ…˜ : @ElementCollection, @CollectionTable
[Spring JPA] @Embedded, @Embeddable



객체지ν–₯ 쿼리 μ–Έμ–΄

JPQL, JPA Criteria, Query DSL, λ„€μ΄ν‹°λΈŒ SQL, JDBC APIλ₯Ό μ‚¬μš©ν•˜λŠ” λ“± λ‹€μ–‘ν•œ 쿼리방법을 μ§€μ›ν•©λ‹ˆλ‹€.


μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ 동기화가 ν•„μš”ν•œ 경우
JPA와 ν•¨κ»˜ JDBC APIλ‚˜ MyBatisλ₯Ό μ‚¬μš©ν•˜λŠ” κ²½μš°μ—λŠ” μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό λͺ…μ‹œμ μœΌλ‘œ flush()ν•΄μ€˜μ•Ό ν•©λ‹ˆλ‹€. μ™œλƒν•˜λ©΄, 아직 DB에 λ°˜μ˜λ˜μ§€ μ•Šμ€ 데이터λ₯Ό JPAλ₯Ό μš°νšŒν•΄μ„œ μ‘°νšŒν•˜κΈ° λ•Œλ¬Έμ— 무결성 λ¬Έμ œκ°€ λ°œμƒν•©λ‹ˆλ‹€. λ”°λΌμ„œ 우회 μ ‘κ·Ό μ „ λ°˜λ“œμ‹œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ 동기화 μ²˜λ¦¬κ°€ ν•„μš”ν•©λ‹ˆλ‹€.


JPQL

Java Persistence Query Language

JPAμ—μ„œ μ‚¬μš©ν•˜λŠ” 쿼리 μ–Έμ–΄λ‘œ μ—”ν‹°ν‹° 객체λ₯Ό λŒ€μƒμœΌλ‘œ 쿼리λ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€. SQLκ³Ό μœ μ‚¬ν•˜μ§€λ§Œ entity 객체λ₯Ό λŒ€μƒμœΌλ‘œ μΏΌλ¦¬ν•œλ‹€λŠ” νŠΉμ§•μ΄ μžˆμŠ΅λ‹ˆλ‹€. JPQL은 SQL을 μΆ”μƒν™”ν•΄μ„œ νŠΉμ •λ°μ΄ν„°λ² μ΄μŠ€ SQL에 μ˜μ‘΄ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
TypedQueryλŠ” λ°˜ν™˜νƒ€μž…μ΄ λͺ…ν™•ν•œ κ²½μš°μ— μ‚¬μš©λ˜κ³ , λͺ…ν™•ν•˜μ§€ μ•Šμ€ κ²½μš°λŠ” κ·Έλƒ₯ Query둜 μž‘μ„±ν•©λ‹ˆλ‹€.

// νŒŒλΌλ―Έν„° 이름을 μ΄μš©ν•œ 바인딩
TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u WHERE u.age > :age ORDER BY u.name DESC", User.class);
query.setParameter("age", 20);
List<User> users = query.getResultList();

// μœ„μΉ˜ 기반 νŒŒλΌλ―Έν„°λ₯Ό μ‚¬μš©ν•œ 바인딩
Query<Member> query = entityManager.createQuery("SELECT m FROM Member m WHERE age=?1");
query.setParameter(1, 20);


ν”„λ‘œμ μ…˜
ν”„λ‘œμ μ…˜μ€ JPQLμ—μ„œ SELECTμ ˆμ—μ„œ μ–΄λ–€ 데이터λ₯Ό μ‘°νšŒν•  것인가λ₯Ό λ§ν•©λ‹ˆλ‹€. μ‘°νšŒν•˜λŠ” 데이터 속성에 따라 객체 ν”„λ‘œμ μ…˜, Embedded ν”„λ‘œμ μ…˜, 슀칼라 ν”„λ‘œμ μ…˜μœΌλ‘œ κ΅¬λΆ„λ©λ‹ˆλ‹€.

SELECT m.name FROM Member m


νŽ˜μ΄μ§•
쿼리λ₯Ό 생성할 λ•Œ κ²°κ³Ό κ°’μ˜ 처음 setFirstResultκ³Ό λ§ˆμ§€λ§‰ setMaxResults을 μ„€μ •ν•˜κ³  νŽ˜μ΄μ§•μ²˜λ¦¬ν•΄μ„œ List둜 νŽ˜μ΄μ§• 처리된 값을 가지고 올 수 μžˆμŠ΅λ‹ˆλ‹€.

List<Member> query = entityManager.createQuery("SELECT m FROM Member m WHERE age=:age")
                        .setFirstResult(0)
                        .setMaxResults(10)
                        .getResultList();
query.setParameter("age", 20);


쑰인
μ‘°μΈμ—λŠ” 내뢀쑰인, 외뢀쑰인, 세타쑰인이 μžˆμŠ΅λ‹ˆλ‹€. 세타쑰인은 연관관계 μ—†λŠ” 두 ν…Œμ΄λΈ”μ„ μ‘°μΈν•˜κ³  싢을 λ•Œ μ‚¬μš©ν•©λ‹ˆλ‹€. 이 λ•Œ 쑰인의 값은 FULL JOIN처럼 쑰인되고 κ±°κΈ°μ„œ μ‘°κ±΄μ ˆμ— ν•΄λ‹Ήν•˜λŠ” 값을 λ„μΆœν•©λ‹ˆλ‹€.


μ„œλΈŒμΏΌλ¦¬
EXISTS, ALL, ANY, INκ³Ό 같은 κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€. ν•˜μ΄λ²„λ„€μ΄νŠΈλŠ” SELECTμ—μ„œλ„ μ„œλΈŒμΏΌλ¦¬λ₯Ό μ‚¬μš©ν•  수 μžˆλ„λ‘ μ§€μ›ν•©λ‹ˆλ‹€. JPA ν‘œμ€€ μŠ€νŽ™μ‚¬ν•­μ€ WHEREκ³Ό HAVING μ ˆμ—μ„œλ§Œ μ‚¬μš©μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ FROMμ ˆμ—μ„œ μ„œλΈŒμΏΌλ¦¬κ°€ λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.


ENUM νƒ€μž…
νŒ¨ν‚€μ§€λͺ…을 ν¬ν•¨ν•΄μ„œ jpql.MemberType.ADMIN와 같이 λŒ€μž…ν•˜κ±°λ‚˜ νŒŒλΌλ―Έν„° λ°”μΈλ”©μœΌλ‘œ 쑰건절 μΆ”κ°€κ°€ κ°€λŠ₯ν•©λ‹ˆλ‹€.


쑰건문
COALESCEλ₯Ό μ‚¬μš©ν•˜λ©΄ NULL이 μ•„λ‹Œ 것을 λ°˜ν™˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€. SELECT COALESCE(m.name, '이름이 μ—†λ‹€') FROM Member m와 같이 μ‚¬μš©λ©λ‹ˆλ‹€. 이름이 μ—†λŠ” κ²½μš°λŠ” β€˜μ΄λ¦„μ΄ μ—†λ‹€β€™λ‘œ 좜λ ₯λ©λ‹ˆλ‹€.
NULLIFλŠ” μ›ν•˜λŠ” 값인 κ²½μš°λŠ” NULL을 λ°˜ν™˜ν•©λ‹ˆλ‹€. SELECT NULLIF(m.name, '홍길동') FROM Member m μ—¬κΈ°μ„œ 이름이 홍길동일 경우 NULL을 λ°˜ν™˜ν•©λ‹ˆλ‹€.


μ‚¬μš©μž μ •μ˜ ν•¨μˆ˜
μ‚¬μš© μ „ μ‚¬μš©ν•˜λŠ” DB에 상속받고 등둝해야 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. JPQL둜 MySQL의 μ‚¬μš©μž μ •μ˜ ν•¨μˆ˜λ₯Ό μ–΄λ–»κ²Œ μ‚¬μš©ν•˜λŠ”μ§€ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€. λ¨Όμ €, MySQL에 μ‚¬μš©μž μ •μ˜ν•¨μˆ˜λ₯Ό λ“±λ‘ν•˜κΈ° μœ„ν•΄μ„œλŠ” λ‹€μŒκ³Ό 같이 ν•¨μˆ˜λ₯Ό μƒμ„±ν•΄μ€λ‹ˆλ‹€.

CREATE FUNCTION 'ν•¨μˆ˜λͺ…' (
νŒŒλΌλ―Έν„°
) RETURNS λ°˜ν™˜ν•  λ°μ΄ν„°νƒ€μž…
BEGIN
	μˆ˜ν–‰ν•  쿼리
	RETURN λ°˜ν™˜ν•  κ°’
END

μ‚¬μš©μž μ •μ˜ ν•¨μˆ˜λŠ” μ‚¬μš©ν•˜κΈ° μœ„ν•΄ com.example.MySqlFunctions μ‚¬μš©μž μ •μ˜ ν•¨μˆ˜κ°€ ν¬ν•¨λœ νŒ¨ν‚€μ§€λ₯Ό 가지고 λ“±λ‘ν•œ ν•¨μˆ˜λͺ…κ³Ό νŒŒλΌλ―Έν„°λ₯Ό λ„£μ–΄μ„œ 쿼리λ₯Ό μž‘μ„±ν•˜λ©΄ λ©λ‹ˆλ‹€.

TypedQuery<Employee> query = entityManager.createQuery(
    "SELECT e FROM Employee e WHERE FUNCTION('com.example.MySqlFunctions.my_custom_function', e.salary) > 50000",
    Employee.class
);
List<Employee> employees = query.getResultList();


κ²½λ‘œν‘œν˜„μ‹
κ²½λ‘œν‘œν˜„μ‹μ€ 객체 κ·Έλž˜ν”„ 탐색을 ν•˜κΈ° μœ„ν•œ ν‘œν˜„μ‹μž…λ‹ˆλ‹€. μƒνƒœν•„λ“œλŠ” 값을 μ €μž₯ν•˜κΈ° μœ„ν•œ ν•„λ“œλ₯Ό λ§ν•©λ‹ˆλ‹€. μ—°κ΄€ν•„λ“œλŠ” 단일 κ°’ μ—°κ΄€ ν•„λ“œλ‘œ λ¬΅μ‹œμ  λ‚΄λΆ€ 쑰인이 λ°œμƒν•˜κΈ° λ•Œλ¬Έμ— μ‹€λ¬΄μ—μ„œ λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ˜λ„λ‘ λͺ…μ‹œμ  쑰인으둜 관리할 수 μžˆλŠ” 쿼리둜 λ§Œλ“œλŠ” 것이 μ€‘μš”ν•©λ‹ˆλ‹€.

SELECT o.customer.name, oi.quantity FROM Order o JOIN o.orderItems oi WHERE o.id = :orderId


패치 쑰인(fetch join)
JPQLμ—μ„œ μ„±λŠ₯ μ΅œμ ν™”λ₯Ό μœ„ν•΄ μ œκ³΅ν•˜λŠ” κΈ°λŠ₯으둜 JPA μ¦‰μ‹œλ‘œλ”©μ²˜λŸΌ 객체 κ·Έλž˜ν”„λ₯Ό SQL ν•œ λ²ˆμ— μ‘°νšŒν•˜λŠ” κ°œλ…μž…λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ νšŒμ›μ„ μ‘°νšŒν•  λ•Œ, μ—°κ΄€λœ νŒ€μ„ 같이 μ‘°νšŒν•˜κ³  싢은 경우 λ‹€μŒκ³Ό 같이 쿼리λ₯Ό μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

select m from Member m join fetch m.team
  1. 패치쑰인의 λŒ€μƒμΈ team을 λ‹€ κ°€μ Έμ˜€λŠ”λ°, 별칭을 μ΄μš©ν•΄μ„œ μ›ν•˜λŠ” κ°’λ§Œ 가지고 올 μˆ˜κ°€ μ—†λ‹€λŠ” 단점이 μžˆμŠ΅λ‹ˆλ‹€.
  2. λ‘˜ μ΄μƒμ˜ μ»¬λ ‰μ…˜μ€ Cartesian Product을 λ§Œλ“€μ–΄ λ‚΄κΈ° λ•Œλ¬Έμ— μ€‘λ³΅μš”μ†Œκ°€ 많이 λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„œλŠ” LEFT JOIN FETCH둜 패치 μ‘°μΈν•˜λŠ” λ°©λ²•μœΌλ‘œ 쀑볡 데이터λ₯Ό μ œκ±°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  3. μ»¬λ ‰μ…˜μ„ 패치 μ‘°μΈν•˜λ©΄ νŽ˜μ΄μ§• APIλ₯Ό μ‚¬μš©ν•  수 μ—†λ‹€λŠ” 단점이 μžˆμŠ΅λ‹ˆλ‹€. λͺ¨λ“  데이터λ₯Ό κ°€μ Έμ˜€κ²Œ λ˜λ―€λ‘œ, 이 데이터λ₯Ό APIμ—μ„œ νŽ˜μ΄μ§€ λ‹¨μœ„λ‘œ μ²˜λ¦¬ν•˜κΈ° μ–΄λ €μšΈ 수 μžˆμŠ΅λ‹ˆλ‹€.


λ‹€ν˜•μ„± 쿼리
λΆ€λͺ¨ 클래슀 νƒ€μž…μœΌλ‘œ μ„ μ–Έλœ λ³€μˆ˜μ— λŒ€ν•΄μ„œλ„ μžμ‹ 클래슀의 데이터λ₯Ό 검색할 수 μžˆμŠ΅λ‹ˆλ‹€. 상속관계λ₯Ό 미리 @DiscriminatorColumn둜 지정해 λ‘” μƒνƒœμ΄κΈ° λ•Œλ¬Έμ— TYPE을 μ΄μš©ν•΄μ„œ νŠΉμ • μžμ‹ νƒ€μž…μ— μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€.

SELECT a FROM Animal a WHERE TYPE(a) IN (Dog, Cat)

TREAT을 μ‚¬μš©ν•˜λ©΄ λΆ€λͺ¨νƒ€μž…을 νŠΉμ • μžμ‹ νƒ€μž…μœΌλ‘œ λ‹€λ£¨λŠ” λ°©λ²•μœΌλ‘œ μ‚¬μš©μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€. μ•„λž˜ μ½”λ“œμ—μ„œ 보면 Animal 별칭을 μ΄μš©ν•΄μ„œ Dog의 λ‚˜μ΄κ°€ 2살보닀 λ§Žμ€ 객체λ₯Ό ꡬ할 수 μžˆμŠ΅λ‹ˆλ‹€.

SELECT a FROM Animal a WHERE TREAT(a as Dog).age > 2


Named 쿼리
μ •μ μΏΌλ¦¬λ‘œ 미리 μ •μ˜ν•΄λ‘κ³  이름을 가지고 μ‚¬μš©ν•  수 μžˆλŠ” μΏΌλ¦¬μž…λ‹ˆλ‹€. μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‘œλ”© μ‹œμ μ— μ΄ˆκΈ°ν™” ν›„ μž¬μ‚¬μš©ν•˜κ³  λ‘œλ”©μ‹œμ μ— 쿼리λ₯Ό 검증할 수 μžˆλ‹€λŠ” μž₯점이 μžˆμŠ΅λ‹ˆλ‹€. 클래슀 μžμ²΄μ— μ •μ˜ν•  μˆ˜λ„ 있고 XML에 μ •μ˜ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

@Entity
@NamedQuery(name = "Person.findByAge", query = "SELECT p FROM Person p WHERE p.age = :age")
public class Person {
    @Id
    private Long id;
    private String name;
    private int age;
    // getters, setters, constructors
}
TypedQuery<Person> query = em.createNamedQuery("Person.findByAge", Person.class);
query.setParameter("age", 30);
List<Person> persons = query.getResultList();


λ²Œν¬μ—°μ‚°
λ²Œν¬μ—°μ‚°μ„ ν•˜κ²Œ 되면 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό λ¬΄μ‹œν•˜κ³  λ°μ΄ν„°λ² μ΄μŠ€μ— 직접 μΏΌλ¦¬ν•˜κΈ° λ•Œλ¬Έμ— λ²Œν¬μ—°μ‚° ν›„ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό μ΄ˆκΈ°ν™”ν•˜λŠ” 것이 μ€‘μš”ν•©λ‹ˆλ‹€.

Query query = em.createQuery("UPDATE Person p SET p.age = :newAge WHERE p.age < :oldAge");
query.setParameter("newAge", 40);
query.setParameter("oldAge", 30);
int updatedCount = query.executeUpdate();


Querydsl

Querydsl의 μ‹œμž‘μ€ HQL의 도메인 νƒ€μž…κ³Ό λ¬Έμžμ—΄ νƒ€μž…μ•ˆμ „μ„± 이슈λ₯Ό ν•΄κ²°ν•˜λŠ” κ²ƒμ—μ„œ λΉ„λ‘―λ˜μ–΄ μ§€κΈˆμ€ JPA, JDO, JDBC, MongoDB λ“± λ°±μ—”λ“œ 지원을 μœ„ν•œ 기술둜 λ°œμ „ν–ˆμŠ΅λ‹ˆλ‹€. νƒ€μž… μ•ˆμ •μ„±κ³Ό 일관성이 Querydsl의 μ€‘μš”ν•œ μ›λ¦¬μž…λ‹ˆλ‹€.
QueryDSL은 λ‹€μŒκ³Ό 같이 μžλ°” μ½”λ“œλ‘œ 쿼리λ₯Ό μž‘μ„±ν•˜κΈ° λ•Œλ¬Έμ— 컴파일 νƒ€μž„μ— 였λ₯˜λ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ λŸ°νƒ€μž„ 였λ₯˜κ°€ λ°œμƒν•  κ°€λŠ₯성이 μ μŠ΅λ‹ˆλ‹€. 그리고 JPQL에 λΉ„ν•΄ 가독성이 λ†’κ³ , μ—”ν‹°ν‹° 별칭과 속성 등을 μžλ™μœΌλ‘œ μƒμ„±ν•΄μ€€λ‹€λŠ” μž₯점이 μžˆμŠ΅λ‹ˆλ‹€. 동적 쿼리 μ‚¬μš©μ—λ„ μ ν•©ν•©λ‹ˆλ‹€.


Querydsl μ€€λΉ„ν•˜κΈ°
μ‹œμž‘ν•˜κΈ° μ•žμ— gradle 섀정이 ν•„μš”ν•©λ‹ˆλ‹€. 기쑴에 spring web ν”„λ‘œμ νŠΈλ₯Ό jpaλ₯Ό μ΄μš©ν•΄μ„œ μž‘μ—…ν•˜κ³  μžˆμ—ˆλ˜ 것을 κΈ°μ€€μœΌλ‘œ μΆ”κ°€ν•΄μ•Όν•  λ‚΄μš©μ€ μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€. 이 μ½”λ“œλŠ” Querydsl을 μ‚¬μš©ν•˜κΈ° μœ„ν•œ ν”ŒλŸ¬κ·ΈμΈ, μ˜μ‘΄μ„± μΆ”κ°€ 라이브러리, λΉŒλ“œλ₯Ό μœ„ν•œ μ„€μ • λ‚΄μš©μ„ λ‹΄κ³  μžˆμŠ΅λ‹ˆλ‹€.

// build.gradle

plugins {
    id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
    id 'java'
}

dependencies {
    implementation 'com.querydsl:querydsl-jpa'
}

def querydslDir = "$buildDir/generated/querydsl"

querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}
sourceSets {
    main.java.srcDir querydslDir
}

configurations {
    querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}

μ—¬κΈ°μ„œ κ²€μ¦μš© Qνƒ€μž…μ„ μƒμ„±ν•˜κΈ° μœ„ν•΄μ„œλŠ” Gradle-Task-other-compileQuerydsl μ‹€ν–‰ ν›„ build-generated-querydsl μ•ˆμ— μƒμ„±λœ 것을 확인할 수 μžˆλ‹€. κ²€μ¦μš© Qνƒ€μž…μ€ μ˜€λ¦¬μ§€λ„ νƒ€μž…μ˜ public 속성을 λ‹΄κ³  μžˆλŠ” 쿼리용 νƒ€μž…μœΌλ‘œ λ‹€μŒκ³Ό 같이 μ‚¬μš©λ©λ‹ˆλ‹€.
Querydslμ—μ„œ μ‚¬μš©λ˜λŠ” queryλ₯Ό 보기 μœ„ν•΄μ„œλŠ” application.yml μ„€μ •μœΌλ‘œ 확인이 κ°€λŠ₯ν•©λ‹ˆλ‹€.

spring.jpa.properties.hibernate.use_sql_comments: true


Querydsl을 μ‚¬μš©ν•˜λŠ” 방법
Qνƒ€μž… 객체λ₯Ό μ‚¬μš©ν•˜λŠ” 방법은 별칭을 μ‚¬μš©ν•˜κ±°λ‚˜ κΈ°λ³Έ μΈμŠ€ν„΄μŠ€λ₯Ό μ‚¬μš©ν•˜λŠ” λ°©λ²•μœΌλ‘œ λ‚˜λ‰©λ‹ˆλ‹€. ν•˜μ§€λ§Œ μœ„μ˜ μ˜ˆμ‹œμ²˜λŸΌ μžλ™μœΌλ‘œ Querydslμ—μ„œ μ œκ³΅ν•˜λŠ” κΈ°λ³Έ μΈμŠ€ν„΄μŠ€λ₯Ό μ‚¬μš©ν•΄λ„ λ©λ‹ˆλ‹€.

QCustomer customer = new QCustomer("myCustomer");

μ—¬κΈ°μ„œ Qνƒ€μž… 객체인 QCustomerλŠ” 기본으둜 customer둜 제곡되며, 이후 Querydsl μ•ˆμ—μ„œλ„ λ™μΌν•˜κ²Œ μ‚¬μš©μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.

JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
List<Customer> customers = queryFactory.selectFrom(customer)
                                .where(customer.age.gt(20))
                                .orderBy(customer.name.desc())
                                .fetch();


Querydsl 핡심문법

Querydsl은 JPQLκ³Ό λ§ˆμ°¬κ°€μ§€λ‘œ SQL κΈ°λŠ₯ λŒ€λΆ€λΆ„μ„ μ œκ³΅ν•˜κ³  있고, 편의λ₯Ό μœ„ν•΄ μΆ”κ°€λ˜λŠ” κΈ°λŠ₯λ“€κ³Ό 문법이 μžˆλŠ”λ°, 기본적인 문법을 μ œμ™Έν•˜κ³  λͺ‡ 가지 λ¬Έλ²•λ§Œ 기얡을 ν•˜κΈ° μœ„ν•΄ μ •λ¦¬ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

검색 쑰건 쿼리

customer.age.goe(30) // age >= 30
customer.age.gt(30)  // age > 30
customer.age.loe(30) // age <= 30
customer.age.lt(30)  // age < 30

customer.username.like("jane%")       // like 검색
customer.username.contains("e")       // like %member% 검색
customer.username.startswith("j")     // like member% 검색

쑰인 쿼리
쑰인 μΏΌλ¦¬μ—μ„œλŠ” innerJoin(), leftJoin() λ“± λͺ¨λ‘ μ§€μ›ν•©λ‹ˆλ‹€. Querydsl 5.0λΆ€ν„°λŠ” fetchResult()와 fetchCount()κ°€ deprecated λ˜μ—ˆλŠ”λ°, λ³΅μž‘ν•œ λ‹€μ€‘μΏΌλ¦¬μ—μ„œ 두 쿼리가 μ •μƒμž‘λ™ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— λ‹¨μˆœνžˆ fetch() 처리 ν›„ java size() μ²˜λ¦¬ν•˜κ±°λ‚˜ νŽ˜μ΄μ§• μ²˜λ¦¬ν•˜λŠ” 것을 ꢌμž₯ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ‹€μ–‘ν•œ 쑰인 λ’€μ—λŠ” μ¦‰μ‹œλ‘œλ”© 처리λ₯Ό μœ„ν•΄ fetchJoin()을 μΆ”κ°€ν•΄μ„œ μ‚¬μš©μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.

// 연관관계 κ°μ²΄κΉŒμ§€ λͺ¨λ‘ 쑰인, μ¦‰μ‹œλ‘œλ”©
selctFrom(customer).leftJoin(customer.order, order).fetchJoin().fetch()
// μ¦‰μ‹œλ‘œλ”© κ²°κ³Ό 값을 ν•œ 개 리턴
selctFrom(customer).leftJoin(customer.order, order).fetchJoin().fetchOne()    

세타 쑰인은 연관관계가 없어도 데이터λ₯Ό λ‹€ 가지고 μ™€μ„œ 쑰인을 ν•˜λŠ” λ°©μ‹μœΌλ‘œ 카디널리티 곱을 λ°˜ν™˜ν•©λ‹ˆλ‹€. 연관관계가 μ—†λŠ” κ²½μš°μ—λŠ” onμ ˆμ„ μ‚¬μš©ν•΄ 외뢀쑰인을 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

List<Tuple> result = queryFactory
            .select(member,team)
            .from(member)
            .leftJoin(team).on(member.username.eq(team.name))
            .fetch();

β€˜fetchJoin().fetch()’은 N+1 μ΄μŠˆμ—μ„œ 자유둜울까?
JPAλ₯Ό κ³΅λΆ€ν•˜λ©΄μ„œ 쿼리 μ„±λŠ₯ μ΅œμ ν™”λ₯Ό μœ„ν•΄ κ°€μž₯ λŒ€λ‘λ˜μ—ˆλ˜ 뢀뢄이 N+1 μ΄μŠˆκ°€ λ°œμƒν•˜λŠ”μ§€μ— λŒ€ν•œ λΆ€λΆ„μ΄μ—ˆλ˜ 것 κ°™μŠ΅λ‹ˆλ‹€. Querydsl도 μ—­μ‹œ fetch 쑰인을 μ œκ³΅ν•˜λŠ”λ° λ¬Έμ œκ°€ μ—†μ„κΉŒ ꢁ금증이 μƒκ²ΌμŠ΅λ‹ˆλ‹€.
fetchJoin()으둜 μ¦‰μ‹œλ‘œλ”© 처리된 객체λ₯Ό 가지고 μ˜΅λ‹ˆλ‹€. μ—¬κΈ°μ„œ fetch()λŠ” fetchJoin()으둜 이미 λ‘œλ“œλœ μ—”ν‹°ν‹°λ₯Ό 좔가적인 쿼리 없이 ν•œ λ²ˆμ— μ‘°νšŒν•˜λ©°, 이 λ•Œ μ€‘λ³΅λœ μ—”ν‹°ν‹°λ₯Ό 가지고 μ˜€μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— N+1의 이슈λ₯Ό ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
μΆ”κ°€λ‘œ JPAμ—μ„œ N+1 이슈λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ distinctλŠ” 쑰회 λŒ€μƒ 객체의 쀑볡을 μ œκ±°ν•˜κ³  κ²°κ³Όλ₯Ό λ„μΆœν•œλ‹€λŠ” μ μ—μ„œ 차이가 μžˆμŠ΅λ‹ˆλ‹€.

κ·Έλ ‡λ‹€λ©΄ left outer join을 μ‚¬μš©ν•˜λŠ” 경우 Querydsl을 μ–΄λ–»κ²Œ μ΄μš©ν•΄μ•Ό ν• κΉŒμš”?
기본적으둜 Parent 객체λ₯Ό μ‘°νšŒν•˜κ³  Child 객체λ₯Ό left join()ν•˜λ©΄, left outer join으둜 Parent 객체λ₯Ό κΈ°μ€€μœΌλ‘œ ν•„μš”ν•œ Child만 λ½‘μ•„μ˜€κ²Œ λ©λ‹ˆλ‹€. Projection을 μ‚¬μš©ν•΄μ„œ ν•΄κ²°ν•˜κ±°λ‚˜ Result Aggregation으둜 Querydsl κ²°κ³Όλ₯Ό νŠΉμ • ν‚€λ₯Ό κΈ°μ€€ μ‚Όμ•„ κ·Έλ£Ήν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

// Result Aggression을 μ΄μš©ν•œ 경우
public List<Family> findFamily() {
    Map<Parent, List<Child>> transform = queryFactory
            .from(parent)
            .leftJoin(parent.children, child)
            .transform(groupBy(parent).as(list(child)));

    return transform.entrySet().stream()
            .map(entry -> new Family(entry.getKey().getName(), entry.getValue()))
            .collect(Collectors.toList());
}
Map<Integer, List<Comment>> results = query.from(post, comment)
    .where(comment.post.id.eq(post.id))
    .transform(groupBy(post.id).as(list(comment)));

Projection
κΈ°μ‘΄ JPA의 ν”„λ‘œμ μ…˜λ³΄λ‹€ 더 λ³΅μž‘ν•œ μ»¨νŠΈλ‘€μ„ κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€. ν”„λ‘œμ μ…˜ λŒ€μƒμ΄ λ‘˜ 이상인 κ²½μš°λŠ” Tupleμ΄λ‚˜ DTO 쑰회둜 κ²°κ³Όλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. Querydsl은 ν”„λ‘œνΌν‹° Setter, ν•„λ“œ 직접 μ ‘κ·Ό, μƒμ„±μžλ₯Ό μ΄μš©ν•œ μ ‘κ·Ό 방식을 μ œκ³΅ν•©λ‹ˆλ‹€.

[ν”„λ‘œνΌν‹° Setterλ₯Ό μ΄μš©ν•˜λŠ” 방식] 이 κ²½μš°μ—λŠ” dto에 각 ν”„λ‘œνΌν‹°μ— λŒ€ν•œ μƒμ„±μžκ°€ ν•„μš”ν•˜κΈ° λ•Œλ¬Έμ— λ³„λ„μ˜ μƒμ„±μžλ₯Ό μƒμ„±ν•˜κ±°λ‚˜ @NoArgsContructor둜 μƒμ„±μžλ₯Ό λ§Œλ“€μ–΄μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€.

List<MemberDto> result = queryFactory
        .select(Projections.bean(MemberDto.class,
                member.username,
                member.age))
        .from(member)
        .fetch();

[ν•„λ“œ 직접 μ ‘κ·Ό 방식] 별도 μƒμ„±μžλ₯Ό ν•„μš”λ‘œ ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 별칭이 λ‹€λ₯Έ κ²½μš°λŠ” .as()둜 ν•΄κ²°ν•˜κ³  μ„œλΈŒμΏΌλ¦¬λŠ” ExpressionUtils.as(sourse, alias) λ°©μ‹μœΌλ‘œ ν•΄κ²°ν•©λ‹ˆλ‹€.

List<MemberDto> result = queryFactory
        .select(Projections.fields(MemberDto.class,
                member.username,
                member.age))
        .from(member)
        .fetch();

[μƒμ„±μž μ ‘κ·Ό 방식] κ°’κ³Ό μƒμ„±μž μˆœμ„œκ°€ λ§žμ•„μ•Ό ν•˜λ©°, @AllArgsConstructorλ₯Ό μ‚¬μš©ν•˜κ³ , setterκ°€ ν•„μš”ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

List<MemberDto> result = queryFactory
        .select(Projections.constructor(MemberDto.class,
                member.username,
                member.age))
        .from(member)
        .fetch();

이 μƒμ„±μž 방식은 @QueryProjection을 μ§€μ›ν•©λ‹ˆλ‹€. @QueryProjection을 MemberDto에 μ‚¬μš©ν•˜λ©΄, μ•„λž˜μ™€ 같이 κ°„κ²°ν•œ μ½”λ“œ ν‘œν˜„μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ Querydsl에 λŒ€ν•œ μ˜μ‘΄μ„±μ΄ dto에 생기기 λ•Œλ¬Έμ— μœ μ§€λ³΄μˆ˜μ— μ ν•©ν•˜μ§€ μ•Šμ„ 수 μžˆλ‹€λŠ” 단점이 μžˆμŠ΅λ‹ˆλ‹€.

List<MemberDto> result = queryFactory
        .select(new QMemberDto(member.username, member.age))
        .from(member)
        .fetch();

BooleanExpressionλ₯Ό μ΄μš©ν•œ 동적 쿼리
BooleanExpressionλ₯Ό μ΄μš©ν•˜λ©΄ 볡합 쑰건을 μž‘μ„±ν•΄μ„œ select, where μ ˆμ—μ„œ μ‘°κ±΄μ‹μœΌλ‘œ μ‚¬μš©μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€. BooleanBuilderλŠ” μƒνƒœλ³€κ²½μ΄ 되고, null 쑰건은 λ¬΄μ‹œλ˜κ³ , λ‹€λ₯Έ μΏΌλ¦¬μ—μ„œλ„ μž¬ν™œμš©μ΄ κ°€λŠ₯ν•˜λ‹€λŠ” μž₯점이 μžˆμŠ΅λ‹ˆλ‹€.

private List<Member> searchMember(String usernameCond, Integer ageCond) {
  return queryFactory
    .selectFrom(member)
    .where(usernameEq(usernameCond), ageEq(ageCond))
    .fetch();
}

private BooleanExpression usernameEq(String usernameCond) {
  return usernameCond != null ? member.username.eq(usernameCond) : null;
}

private BooleanExpression ageEq(Integer ageCond) {
  return ageCond != null ? member.age.eq(ageCond) : null;
}

μˆ˜μ •, μ‚­μ œ λ²Œν¬μ—°μ‚°
μˆ˜μ •κ³Ό μ‚­μ œλ₯Ό ν•˜λŠ” λ²Œν¬μ—°μ‚°μ€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ μ—”ν‹°ν‹°λ₯Ό λ¬΄μ‹œν•˜κ³  λ°”λ‘œ DB에 execute() 처리λ₯Ό ν•˜κΈ° λ•Œλ¬Έμ— μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό μ΄ˆκΈ°ν™”ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

long count = queryFactory
  .delete(member)
  .where(member.age.gt(18))
  .execute();

em.flush();
em.clear();


μš°μ•„ν•œ ν˜•μ œλ“€μ˜ Querydsl μ‚¬μš©λ²•

extends / implements μ‚¬μš©ν•˜μ§€ μ•ŠκΈ°
맀 repositoryλ§ˆλ‹€ JpaRepositoryλ₯Ό 상속받지 μ•Šκ³ , JPAQueryFactory을 Bean으둜 λ“±λ‘ν•΄μ„œ μ‚¬μš©ν•©λ‹ˆλ‹€.

@Configuration
public class QuerydslConfiguration {
    @Autowired
    EntityManager em;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
       return new JPAQueryFactory(em);
    }
}
@Repository
@RequiredArgsConstructor 
public class MemberRepositoryCustom {
    private final JpaQueryFactory queryFactory; // λ¬Όλ‘  이λ₯Ό μœ„ν•΄μ„œλŠ” 빈으둜 등둝을 ν•΄μ€˜μ•Ό ν•œλ‹€. 
}

동적 μΏΌλ¦¬λŠ” BooleanExpression μ‚¬μš©ν•˜κΈ°
BooleanExpressionλ₯Ό μ‚¬μš©ν•˜λ©΄, null 값을 λ¬΄μ‹œν•˜κ³  단일 쑰건을 λ‚˜νƒ€λ‚΄λŠ” μΈν„°νŽ˜μ΄μŠ€λ‘œ μ‘°ν•©ν•΄ λ³΅μž‘ν•œ 동적쿼리λ₯Ό λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€. BooleanBuilderλŠ” BooleanExpression듀을 λͺ¨μ•„μ„œ μ‚¬μš©ν•  수 있게 ν•΄μ€λ‹ˆλ‹€. 상황에 따라 λ‹€λ₯΄κ² μ§€λ§Œ 톡상 BooleanBuilderλ₯Ό μ‚¬μš©ν•˜λŠ” 경우, μ—¬λŸ¬ ν‘œν˜„μ‹μ„ 가지고 μ‘°λ¦½ν•˜κΈ° λ•Œλ¬Έμ— ν•œ λˆˆμ— νŒŒμ•…ν•˜λŠ” 것이 μ–΄λ ΅μŠ΅λ‹ˆλ‹€. 그리고 μ€‘λ³΅λ˜λŠ” 쑰건식을 μž¬ν™œμš©ν•˜κΈ°μ— BooleanExpression으둜 λ©”μ„œλ“œν™”ν•˜λŠ” 것이 μœ λ¦¬ν•©λ‹ˆλ‹€.

exist λ©”μ†Œλ“œ μ‚¬μš©ν•˜μ§€ μ•ŠκΈ°
count 쿼리둜 λ™μž‘ν•˜κΈ° λ•Œλ¬Έμ— 전체 λ‹€ μ‘°νšŒν•˜λŠ” 경우 μ„±λŠ₯이 λ–¨μ–΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€. 그리고 자체적으둜 맀 μ‹€ν–‰μ‹œ μ„œλΈŒ 쿼리λ₯Ό μƒμ„±ν•˜κΈ° λ•Œλ¬Έμ— κ°œλ°œμžκ°€ 직접 μ΅œμ ν™”ν•˜κΈ° μ–΄λ ΅μŠ΅λ‹ˆλ‹€. λŒ€μ‹  join을 μ΄μš©ν•˜λŠ” 것이 μ„±λŠ₯을 μ΅œμ ν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Cross Join ν”Όν•˜κΈ°
쑰인을 λͺ…μ‹œν•˜μ§€ μ•ŠλŠ” λ¬΅μ‹œμ  쑰인은 크둜슀 쑰인을 ν•˜κ²Œ λ˜λ―€λ‘œ μ„±λŠ₯이 λ–¨μ–΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ λͺ…μ‹œμ  쑰인으둜 λΆˆν•„μš”ν•œ 쑰회λ₯Ό 쀄어야 ν•©λ‹ˆλ‹€.

μ‘°νšŒν• λ• Entity λ³΄λ‹€λŠ” DTOλ₯Ό μš°μ„ μ μœΌλ‘œ κ°€μ Έμ˜€κΈ°, Select μΉΌλŸΌμ— EntityλŠ” μžμ œν•˜κΈ°
Entity 자체λ₯Ό 가지고 μ˜€λŠ” 것은 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ 1μ°¨ μΊμ‹œ κΈ°λŠ₯을 μ‚¬μš©ν•˜κ³  λΆˆν•„μš”ν•œ μ»¬λŸΌμ„ μ‘°νšŒν•˜κ²Œ λ©λ‹ˆλ‹€. 그리고 OneToOne N+1 쿼리 λ°œμƒν•˜λŠ” λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ ν•„μš”ν•œ μ†μ„±λ§Œμ„ DTO둜 λ°›μ•„ λ¦¬ν„΄ν•˜λŠ” 것이 λ°”λžŒμ§ν•©λ‹ˆλ‹€.
νŠΉνžˆλ‚˜ distinctλ₯Ό μ‚¬μš©ν•˜λŠ” κ²½μš°λŠ” λͺ¨λ“  rowλ₯Ό ν™•μΈν•˜κΈ° λ•Œλ¬Έμ— λ°˜λ“œμ‹œ ν•„μš”ν•œ μ»¬λŸΌλ§Œμ„ μ‘°νšŒν•˜λŠ” 것이 λ°”λžŒμ§ν•©λ‹ˆλ‹€.

Group By μ΅œμ ν™”ν•˜κΈ°
Querydsl은 MySQLκ³Ό 달리 OrderByNull을 μ œκ³΅ν•˜κ³  μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 그리고 μΈλ±μŠ€κ°€ μ—†λ‹€λ©΄ μžλ™μ μœΌλ‘œ μ‹€ν–‰λ˜λŠ” μ •λ ¬ μ•Œκ³ λ¦¬μ¦˜ Filesort이 λ™μž‘ν•˜κ²Œ λ©λ‹ˆλ‹€.

public class OrderByNull extends OrderSpecifier {
    public static final OrderByNull DEFAULT = new OrderByNull();
    private OrderByNull(){
        super(Order.ASC, NullExpression.DEFAULT, NullHandling.Default);
    }
}

Querydsl μ—μ„œ 컀버링 인덱슀 μ‚¬μš©ν•˜κΈ°
인덱슀 κ²€μƒ‰μœΌλ‘œ λΉ λ₯΄κ²Œ μ²˜λ¦¬ν•˜κ³  κ±ΈλŸ¬μ§„ ν•­λͺ©μ— λŒ€ν•΄μ„œλ§Œ 데이터 블둝에 μ ‘κ·Όν•˜κΈ° λ•Œλ¬Έμ— μ„±λŠ₯의 이점을 μ–»κ²Œ λ©λ‹ˆλ‹€. ν•˜μ§€λ§Œ μΈλ±μŠ€κ°€ λ§Žμ•„μ§ˆ 수 μžˆλ‹€λŠ” 단점이 μžˆμŠ΅λ‹ˆλ‹€.

νŽ˜μ΄μ§• μ„±λŠ₯ κ°œμ„ μ„ μœ„ν•΄ No Offset μ‚¬μš©ν•˜κΈ°
데이터가 λ§Žμ•„μ§€λ©΄ offset+limitλ₯Ό μ‚¬μš©ν•œ 데이터 μ‘°νšŒμ‹œ μ„±λŠ₯상 쒋지 μ•ŠκΈ° λ•Œλ¬Έμ— No Offset을 μ‚¬μš©ν•΄μ„œ μ‹œμž‘ 지점을 인덱슀둜 μ°Ύμ•„ μ½λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

일괄 Update μ΅œμ ν™”ν•˜κΈ°
일괄 Updateλ₯Ό ν•  λ•ŒλŠ” Cache Eviction 처리λ₯Ό ν•΄μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€. μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ Dirty Checking을 μ‚¬μš©ν•˜λ©΄ 였히렀 λ§Žμ€ 쿼리가 λ°œμƒν•˜κ³  μ„±λŠ₯상 단점이 될 수 있음으둜 일괄 Updateλ₯Ό ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

JPA둜 Bulk InsertλŠ” μžμ œν•˜κΈ°
JPA μ—λŠ” auto_incrementμΌλ•Œ insert ν•©μΉ˜κΈ°κ°€ μ μš©λ˜μ§€ μ•ŠμœΌλ―€λ‘œ 이 κΈ°λŠ₯이 ν•„μš”ν•˜λ‹€λ©΄ JdbcTemplate λ₯Ό μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€.


Querydsl Doc
Querydsl GitHub
μ‹€μ „! Querydsl
μžλ°” ORM ν‘œμ€€ JPA ν”„λ‘œκ·Έλž˜λ° - 기본편
10μž₯ 객체지ν–₯ 쿼리언어
JPQL DOC
Querydsl μ—μ„œ OneToMany κ΄€κ³„μ—μ„œ Left Outer Join 이 ν•„μš”ν•  경우
JPAQuery.fetchResults() is deprecated, how should I replace it?
[μš°μ•„μ½˜2020] μˆ˜μ‹­μ–΅κ±΄μ—μ„œ QUERYDSL μ‚¬μš©ν•˜κΈ°
1. 컀버링 인덱슀 (κΈ°λ³Έ 지식 / WHERE / GROUP BY)



Hibernate

JPA κ΅¬ν˜„μ²΄λŠ” Hibernate 외에도 EclipseLink, OpenJPA, DataNucleus 등이 μžˆμŠ΅λ‹ˆλ‹€. κ·Έ μ€‘μ—μ„œλ„ κ°€μž₯ 많이 μ‚¬μš©λ˜λŠ” Hibernate에 λŒ€ν•΄ μ•Œμ•„λ³΄λ €κ³  ν•©λ‹ˆλ‹€. HibernateλŠ” μžλ°” μ–Έμ–΄λ₯Ό μœ„ν•œ 객체 관계 맀칭 ν”„λ ˆμž„μ›Œν¬μž…λ‹ˆλ‹€. μ‹€μ œ Spring ν”„λ‘œμ νŠΈμ—μ„œ JPAλ₯Ό μ‚¬μš©ν•˜λŠ” 경우 Hibernateκ°€ λΌμ΄λΈŒλŸ¬λ¦¬κ°€ ν¬ν•¨λ˜μ–΄ μžˆλŠ” 것을 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

hibernate_lib.png

HibernateλŠ” JPA κ΅¬ν˜„μœΌλ‘œ SessionFactory, Session, Transaction으둜 상속받고 각각의 Impl을 κ΅¬ν˜„ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. JDBC APIλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³ , Hibernateμ—μ„œ μ œκ³΅ν•˜λŠ” λ©”μ„œλ“œλ§ŒμœΌλ‘œλ„ SQL을 λŒ€μ²΄ν•  수 μžˆλ‹€λŠ” μž₯점이 μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ— 집쀑할 수 있게 되고, 객체지ν–₯적 개발이 κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.

hibernate_layer.png


Hibernate ORM 5.4.33.Final User Guide
[JPA] JPA와 Hibernate 그리고 Spring Data JPA

results matching ""

    No results matching ""