JiwonDev

JPA #9 ๊ฐ’ ํƒ€์ž…, ์ปฌ๋ ‰์…˜, ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…

by JiwonDev

JPA์—์„œ๋Š” 2๊ฐ€์ง€ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์ œ๊ณตํ•œ๋‹ค.

@Entity ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€ํ•ด๋„ ์‹๋ณ„์ž๋กœ ๊ณ„์† ์ถ”์ ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด ์‹๋ณ„์ž๋กœ ์ธ์‹ ๊ฐ€๋Šฅํ•˜๋‹ค.

๊ธฐ๋ณธ ๊ฐ’ ํƒ€์ž…์€ @Entity ์™€ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๊ณต์œ ํ•˜๋ฉฐ, String, Integer, int ๋“ฑ์„ ์˜๋ฏธํ•œ๋‹ค.

 

๋‹จ, ๊ฐ’ ํƒ€์ž…์— ๋Œ€ํ•œ ์‹๋ณ„์ž๋Š” ๋”ฐ๋กœ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ JPA์˜ ๋ณ€๊ฒฝ์ถ”์ ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

์ด๊ฒŒ ๋ฌด์Šจ๋ง์ด๋ƒ๋ฉด, ์™ธ๋ถ€์— ๊ฐ’ํƒ€์ž…์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ์ฐธ์กฐ๊ฐ€ ์žˆ๋‹ค๋ฉด Side-Effect๊ฐ€ ๋ฐœ์ƒํ•  ์œ„ํ—˜์ด ์žˆ๋‹ค๋Š” ๋ง.

  • ๋ญ ์‚ฌ์‹ค ์ž๋ฐ”์—์„œ Primitive Type (int, long)์€ ์ฐธ์กฐ๊ฐ’ ๊ณต์œ ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•ด์„œ ๋ณ„ ์ƒ๊ด€์ด ์—†๋‹ค.
  • Integer๋‚˜ String์€ ๋ž˜ํผํƒ€์ž…์€ ์ฐธ์กฐ๊ฐ’์€ ๊ณต์œ ๊ฐ€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๋ณ€๊ฒฝ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋ง‰ํ˜€์žˆ๋‹ค. 

 

๊ทธ๋ž˜์„œ ์ด๊ฑธ ์„ค๋ช…ํ•˜๋Š” ์ด์œ ๊ฐ€ ๋ญ์ฃ ? ๋‹น์—ฐํ•œ๊ฑฐ ์•„๋‹ˆ์—์š”?

JPA์—์„œ ๊ธฐ๋ณธ๊ฐ’ ํƒ€์ž…๋งŒ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์•„๋ฌด๋Ÿฐ ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค.

๊ฐ’ ํƒ€์ž…์ค‘์—๋Š” Embedded Type, Collection Type์ด ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด ์ฐจ์ด๋ฅผ ๊ผญ ์•Œ์•„์•ผํ•œ๋‹ค.

 

๐Ÿ“Œ r๊ฐ’ ํƒ€์ž… - Embedded

๊ฐ’ ํƒ€์ž…์€ @Entity์˜ ์ƒ๋ช…์ฃผ๊ธฐ์— ์˜์กดํ•œ๋‹ค. ์ฆ‰ Entity๊ฐ€ ์‚ญ์ œ๋˜๋ฉด ๊ฐ’๋„ ์‚ญ์ œ๋œ๋‹ค. ๋‹น์—ฐํ•œ ์ด์•ผ๊ธฐ

๊ฐ’ ํƒ€์ž…์€ JPA์—์„œ ๋ณ€๊ฒฝ ์ถ”์ ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. ์ฆ‰ ์•„๋ž˜์™€ ๊ฐ™์€ Side-Effect๋ฅผ ์กฐ์‹ฌํ•ด์•ผํ•œ๋‹ค.

์ด๋Ÿฐ ๊ฒฝ์šฐ ํฐ์ผ๋‚  ์ˆ˜ ์žˆ๋‹ค.

* ์ฐธ๊ณ ๋กœ EmbeddedType ๋˜ํ•œ ํ”„๋ก์‹œ๋ฅผ ์ด์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ์ƒ์„ฑ์ž๊ฐ€ ํ•„์ˆ˜์ ์ด๋‹ค.

์—†์œผ๋ฉด JpaSystemException: No default constructor for entity ์˜ˆ์™ธ ๋ฐœ์ƒ

 

 

EmbeddedType์ด ๋ญ์˜€์ฃ ?

๋”๋ณด๊ธฐ

๐Ÿ“Œ @Embedded (์ปดํฌ๋„ŒํŠธ)

์šฐ๋ฆฌ๊ฐ€ @Entity๋ฅผ ๋ฌด์ง€์„ฑ์œผ๋กœ ์„ค๊ณ„ํ•˜๋ฉด ์•„๋ž˜์™€ ๋น„์Šทํ•˜๊ฒŒ ๋‚˜์˜จ๋‹ค.

// ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์„ ๋•Œ
@Entity
public class Member {
  
  @Id @GeneratedValue
  private Long id;
  
  private String name;
  
  // ๊ทผ๋ฌด ๊ธฐ๊ฐ„
  LocalDate startDate;
  LocalDate endDate;
  
  // ์ง‘ ์ฃผ์†Œ ํ‘œํ˜„
  private String city;
  private String street;
  private String zipcode;
  // ...
}

ํšŒ์›์ด ์œ„์™€ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง„ ์ด์œ ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค๊ณ„ํ•˜๊ณ ์ž ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

"ํšŒ์› ์—”ํ‹ฐํ‹ฐ๋Š” ์ด๋ฆ„, [ ๊ทผ๋ฌด ๊ธฐ๊ฐ„ ], [ ์ง‘ ์ฃผ์†Œ ]๋ฅผ ๊ฐ€์ง„๋‹ค."

 

์‹ค์ œ ๋ฐ์ดํ„ฐ๋Š” '๊ทผ๋ฌด๊ธฐ๊ฐ„', '์ง‘ ์ฃผ์†Œ'๋ผ๋Š” ํ•˜๋‚˜์˜ ๊ฐ’์œผ๋กœ ๋‹ค๋ฃจ๊ธฐ์—๋Š” ๊นŒ๋‹ค๋กญ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋Ÿฐ Entity๊ฐ€ ๋งŒ๋“ค์–ด์ง„๋‹ค.

 

"ํšŒ์› ์—”ํ‹ฐํ‹ฐ๋Š” ์ด๋ฆ„, [๊ทผ๋ฌด ์‹œ์ž‘์ผ, ๊ทผ๋ฌด ์ข…๋ฃŒ์ผ], [์ฃผ์†Œ ๋„์‹œ, ์ฃผ์†Œ ๋™ํ˜ธ์ˆ˜, ์šฐํŽธ ๋ฒˆํ˜ธ]๋ฅผ ๊ฐ€์ง„๋‹ค."

 

๋ฌด์–ธ๊ฐ€ ์–ด์ƒ‰ํ•˜์ง€ ์•Š์€๊ฐ€? ํšŒ์›์ด '์ง‘ ์ฃผ์†Œ'๊ฐ€ ํ•„์š”ํ•œ๊ฑฐ์ง€ '์„œ์šธ', '906ํ˜ธ' ์ด๋Ÿฐ ๊ฐ’์ด ๋‹จ๋…์œผ๋กœ ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„๊นŒ?

 

์•„๋‹ˆ ์• ์ดˆ์—, [ ์ง‘ ์ฃผ์†Œ ] ๋Š” ํ•˜๋‚˜์˜ ๋ฐ์ดํ„ฐ๋กœ ๋ด์•ผํ• ๊ฑฐ ๊ฐ™์€๋ฐ, ์—ฌ๊ธฐ์— ํฌํ•จ๋œ ์„ธ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ํšŒ์›์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๊ฒŒ ๊ฐ์ฒด์ง€ํ–ฅ์ ์œผ๋กœ ์ข‹์€ ์„ค๊ณ„์ธ์ง€ ๊ณ ๋ฏผํ•ด๋ณผ ํ•„์š”๊ฐ€ ์žˆ๋‹ค. ์ด๋Š” ์‘์ง‘๋ ฅ์„ ๋–จ์–ด๋œจ๋ฆฌ๊ณ , ์œ ์ง€๋ณด์ˆ˜๋ฅผ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ ๋‹ค.

 

 

โœจ ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด JPA์—์„œ๋Š” @Embedded ํƒ€์ž… (์ปดํฌ๋„ŒํŠธ)๋ฅผ ์ œ๊ณตํ•ด์ค€๋‹ค.

  • ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ํ•ด์„œ DB ํ…Œ์ด๋ธ”์ด ๋” ์ƒ์„ฑ๋˜์ง€๋Š” ์•Š๋Š”๋‹ค. ๊ธฐ์กด๊ณผ ๋™์ผํ•˜๊ฒŒ ๋งคํ•‘ํ•ด์„œ ์‚ฌ์šฉ.
  • ํ•œ๋ฒˆ ๋งŒ๋“ค์–ด๋‘๋ฉด ๋‹ค๋ฅธ ๊ณณ์—์„œ ์žฌ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. (+ ๋ฐ์ดํ„ฐ์˜ ์‘์ง‘๋„๊ฐ€ ๋†’์•„์ง„๋‹ค)
  • Embedded ํƒ€์ž… ๋‚ด๋ถ€์—์„œ ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์˜๋ฏธ์žˆ๋Š” ์ƒˆ๋กœ์šด ๊ฐ’ ( isMan() )์„ ๋ฝ‘์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
// ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ์‚ฌ์šฉ
@Entity
public class Member {
  
  @Id @GeneratedVAlue
  private Long id;
  private String name;
  
  @Embedded
  private Period workPeriod;	// ๊ทผ๋ฌด ๊ธฐ๊ฐ„
  
  @Embedded
  private Address homeAddress;	// ์ง‘ ์ฃผ์†Œ
}

 

 

์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์€ ์•„๋ž˜์™€ ๊ฐ™์ด ์ •์˜ ๊ฐ€๋Šฅํ•˜๋‹ค.

@Embeddable
public class Address {
  
  @Column(name="city") // ๋งคํ•‘ํ•  ์ปฌ๋Ÿผ ์ •์˜ ๊ฐ€๋Šฅ
  private String city;
  
  private String street;
  private String zipcode;
  
  public boolean isMetropolitanCity (City city) {
  // .. ๊ฐ’ ํƒ€์ž…์„ ์œ„ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค
  }
}

 

๋‹น์—ฐํžˆ ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ์•ˆ์—์„œ ๋‹ค๋ฅธ ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ž˜ ์„ค๊ณ„๋œ JPA Entity๋Š” ๋ณดํ†ต DB ํ…Œ์ด๋ธ”๋ณด๋‹ค ๋” ๋งŽ์€ ์ˆ˜์˜ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ง€๋Š”๊ฒŒ ์ผ๋ฐ˜์ ์ด๋‹ค.

 

์žฌ๋ฐŒ๋Š”๊ฑด Embedded ํƒ€์ž…์ด Entity๋ฅผ ๊ฐ€์งˆ ์ˆ˜๋„ ์žˆ๋‹ค. (๊ทธ๋ƒฅ ์“ฐ๋Š” ๊ฑฐ๋ž‘ ๋˜‘๊ฐ™๋‹ค.)

์‹ค์ œ ํ…Œ์ด๋ธ”์€ Member ํ•˜๋‚˜์ด๋‹ค.

// ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ์‚ฌ์šฉ
@Entity
public class Member {
  
  @Id @GeneratedVAlue
  private Long id;
  private String name;
  
  @Embedded // @Embedded ์ƒ๋žตํ•˜๋”๋ผ๋„ JPA์—์„œ ์•Œ์•„์„œ ์ฐพ์•„์ฃผ์ง€๋งŒ, ์“ฐ๋Š” ๊ฑธ ๊ถŒ์žฅํ•œ๋‹ค.
  private Period workPeriod;	// ๊ทผ๋ฌด ๊ธฐ๊ฐ„
  
  @Embedded
  private Address homeAddress;	// ์ง‘ ์ฃผ์†Œ
}
@Embeddable
public class Address {
  
  @Column(name="city") // ๋งคํ•‘ํ•  ์ปฌ๋Ÿผ ์ •์˜ ๊ฐ€๋Šฅ
  private String city;
  
  private String street;
  private String zipcode;
  
  public boolean isMetropolitanCity (City city) {
  // .. DB์— ์กด์žฌํ•˜์ง€์•Š๋Š” ์˜๋ฏธ์žˆ๋Š” ์ƒˆ๋กœ์šด ๊ฐ’์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
  }
}

 

ํ•œ๋ฒˆ ๋” ๋งํ•˜์ง€๋งŒ, Embedded ํƒ€์ž…์€ ๊ฐ์ฒด์—์„œ๋งŒ ์กด์žฌํ•  ๋ฟ ํ…Œ์ด๋ธ”์—๋Š” ์•„๋ฌด๋Ÿฐ ์˜ํ–ฅ์ด ์—†๋‹ค.

DB๋Š” ๋˜‘๊ฐ™๋‹ค.

์ฐธ๊ณ ๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด @Embedded ํƒ€์ž… ์ž์ฒด๊ฐ€ null์ด๋ฉด, ๋‚ด๋ถ€์—๋„ ์ „๋ถ€ null์ด ๋“ค์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค.

@Embedded
private Address homeAddress = null;
// Address ๊ฐ์ฒด์•ˆ์˜ ์žˆ๋Š” ์นผ๋Ÿผ ๊ฐ’์ด ์ „๋ถ€ null๋กœ ๋งคํ•‘๋จ

 

 

โœ” ํ•œ Entity์—์„œ ๊ฐ™์€ ๊ฐ’ ํƒ€์ž…์„ ์—ฌ๋Ÿฌ๊ฐœ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?

Embedded ํƒ€์ž…์€ ๊ฐ์ฒด์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ผ ๋ฟ, ๋งคํ•‘ํ•˜๋Š” ํ…Œ์ด๋ธ”์€ ๋ณ€ํ™”๊ฐ€ ์—†๋‹ค๊ณ  ํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ฐ™์€ Embedded ํƒ€์ž…์„ ์—ฌ๋Ÿฌ๋ฒˆ ์‚ฌ์šฉํ•˜๋ฉด DB ์นผ๋Ÿผ๋ช…์ด ์ค‘๋ณต๋˜์–ด ๋งคํ•‘์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. (์˜ˆ์™ธ ๋ฐœ์ƒ)

@Entity
public class Member {

    @Embedded
    private Address homeAddress;

    @Embedded
    private Address companyAddress;

}

์ด ๊ฒฝ์šฐ์—๋Š” @AttributeOverrides๋ฅผ ์ด์šฉํ•ด์„œ ์ง์ ‘ ํ…Œ์ด๋ธ”๊ณผ ์ˆ˜๋™์œผ๋กœ ๋งคํ•‘์‹œ์ผœ์ฃผ๋ฉด ๋œ๋‹ค.

@Embedded
@AttributeOverrides({
    @AttributeOverride(name="city", column=@Column("WORK_CITY"))
    @AttributeOverride(name="street", column=@Column("WORK_STREET")),
    @AttributeOverride(name="zipcode", column=@Column("WORK_ZIPCODE")),
})
private Address homeAddress;

@Embedded
private Address companyAddress;

 

 

๐Ÿ“Œ ๊ฐ’ ํƒ€์ž…๊ณผ ๋ถˆ๋ณ€ ๊ฐ์ฒด(final)

๊ฐ’ ํƒ€์ž…์˜ ์‹ค์ œ ์ธ์Šคํ„ด์Šค (๊ฐ’ ๊ทธ ์ž์ฒด)๋ฅผ ๊ณต์œ ํ•˜๋Š”๊ฑด ๋งค์šฐ ์œ„ํ—˜ํ•˜๋‹ค.

๊ฐ’ ํƒ€์ž…์€ ๋ฐ˜๋“œ์‹œ ๋ณต์‚ฌํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๊ฐ์ฒด๋Š” ์ฐธ์กฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š”๊ฑด ์‹ค์ˆ˜ํ•˜๊ธฐ ์‰ฝ๋‹ค. ๊ณต์œ  ์ฐธ์กฐ ์ž์ฒด๋ฅผ ๋ง‰๋Š” ๋ฐฉ๋ฒ•์€ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

int a = 5;
int b = a; // ๊ฐ’ ๋ณต์‚ฌ
b = 4 // ์•„๋ฌด๋Ÿฐ ๋ฌธ์ œ ์—†์Œ.

Address a = new Address();
Address b = a; // ๐Ÿงจ ์ฐธ์กฐ๋ฅผ ์ „๋‹ฌํ•จ. ๊ฐ’ ๋ณต์‚ฌ๊ฐ€ ์•„๋‹˜
b.setCity("New") // ๐Ÿงจ a๋„ ํ•จ๊ป˜ ๋ณ€๊ฒฝ๋จ!

 

์ด๋ ‡๊ฒŒ ์™ธ๋ถ€์˜ Side-Effect๋กœ ๋ฐœ์ƒํ•˜๋Š” ๋ฒ„๊ทธ๋Š” ์ฐพ๊ธฐ ์ •๋ง ์–ด๋ ต๋‹ค.

์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ฐ’ ํƒ€์ž…์„ ๋ถˆ๋ณ€ ๊ฐ์ฒด (immutable object)๋กœ ์„ค๊ณ„ํ•ด์•ผ ํ•œ๋‹ค.

 

์ฆ‰ ์ƒ์„ฑ์ž๋กœ๋งŒ ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ , Setter๋ฅผ ๋‹ค ์—†์• ๋ฒ„๋ ค์•ผํ•œ๋‹ค.

 

 

๐Ÿ“Œ ๊ฐ’ ํƒ€์ž… - Collection

๊ฐ์ฒด์—๋Š” ์ปฌ๋ ‰์…˜์ด ์žˆ์ง€๋งŒ, DB์—๋Š” ์ปฌ๋ ‰์…˜์ด๋ผ๋Š” ๊ฐœ๋…์ด ์—†๋‹ค. ์ปฌ๋ ‰์…˜์„ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ๋ณ„๋„์˜ ํ…Œ์ด๋ธ”์ด ํ•„์š”ํ•˜๋‹ค.

๋˜ํ•œ Entity๊ฐ€ ์•„๋‹Œ [๊ฐ’ ํƒ€์ž…]์€ PK๋ฅผ ๋”ฐ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ๋ชจ๋“  ์นผ๋Ÿผ์„ ๋ฌถ์–ด PK๋กœ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.

null ์ž…๋ ฅ์„ ๋ง‰๊ณ  ์ค‘๋ณต ์ €์žฅ์„ ๋ง‰์•„์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ.

MEMBER_ID๋ฅผ ์™ธ๋ž˜ํ‚ค๋กœ ๊ฐ€์ง€๊ณ  [๋ชจ๋“  ๊ฐ’์„ PK]๋กœ ์‚ฌ์šฉํ•จ.

 

@Entity์˜ ๊ฒฝ์šฐ 1:N ๊ด€๊ณ„๋กœ ์ง€์ •ํ•ด์ฃผ๋ฉด ๋์—ˆ๋‹ค.

@Entity
public class Member{ ... }


@OneToMany(mappedBy="team")
private List<Member> members = nwe ArrayList<>();

 

๊ฐ’ ํƒ€์ž…์˜ ๊ฒฝ์šฐ @ElementCollection, @CollectionTable์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

@ElementCollection // ๊ธฐ๋ณธ ๊ฐ’ ํƒ€์ž…
@CollectionTable (name = "FAVORITE_FOOD", joinColumns = @JoinColumn(name="MEMBER_ID") )
private Set<String> favoriteFoods = new HashSet<>();


@ElementCollection // ์ž„๋ฒ ๋””๋“œ ๊ฐ’ ํƒ€์ž…
@CollectionTable (name = "FAVORITE_FOOD", joinColumns = @JoinColumn(name="MEMBER_ID") )
private List<Address> favoriteFoods = new ArrayList<>();

 ์ฐธ๊ณ ๋กœ @ElementCollection(fetch = LAZY)๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์ด๋‹ค. ๊ฐ’ ํƒ€์ž…์„ ์ฆ‰์‹œ๋กœ๋”ฉ์œผ๋กœ ์“ฐ๋ฉด ์ •๋ง ํฐ์ผ๋‚œ๋‹ค.

 

 

โœ” ๊ฐ’ ํƒ€์ž… ์ปฌ๋ ‰์…˜์˜ ํŠน์ง•

  • ๊ฐ’ ํƒ€์ž…์€ Entity์™€ ๋‹ค๋ฅด๊ฒŒ ์‹๋ณ„์ž ๊ฐœ๋…์ด ์—†๋‹ค.
  • ์ƒ๋ช…์ฃผ๊ธฐ๊ฐ€ Entity์— ๋ฌถ์—ฌ์žˆ๋‹ค. ๋”ฐ๋กœ ์˜์†ํ™” ํ•  ์ˆ˜ ์—†๊ณ  ๋ถ€๋ชจ Entity๋ฅผ persist()ํ•˜๋ฉด ํ•จ๊ป˜ ๋ฐ˜์˜๋œ๋‹ค.
  • ๊ฐ’ ํƒ€์ž… ์ปฌ๋ ‰์…˜์— ์ผ๋ถ€๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด, ์ˆ˜์ •์ด ์•„๋‹ˆ๋ผ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•˜๊ณ  ๋‹ค์‹œ ์ €์žฅํ•ด์•ผํ•œ๋‹ค.
    ๊ทธ๋ ‡๊ฒŒ ํ•˜์ง€ ์•Š๊ณ  ๊ฐ’ํƒ€์ž…์„ ๋ถˆ๋ณ€ํ•˜์ง€ ์•Š๊ฒŒ ๋งŒ๋“ค๊ณ  ์ˆ˜์ •ํ•œ๋‹ค๋ฉด Side-effect๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
Member member = em.find(Member.class, 1L);

// ๐Ÿงจ SideEffect ๋ฐœ์ƒ!! ๋ณ€๊ฒฝ ์ถ”์ ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.
member.getHomeAddress().setCity("new city"); โŒ

// ์ด๋ ‡๊ฒŒ ๊ฐ’ ๊ฐ์ฒด๋ฅผ ์•„์˜ˆ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด ๊ต์ฒดํ•ด์•ผํ•œ๋‹ค. ์ค‘๋ณต๋˜๋”๋ผ๋„ ์–ด์ฉ” ์ˆ˜ ์—†๋‹ค.
member.setHomeAddress( new Address("new city","street","postcode") ); โญ•

em.persist(member);
Member member = em.find(Member.class, 1L);

// ์ปฌ๋ ‰์…˜๋„ ๋งˆ์ฐฌ๊ฐ€์ง€. ๊ฐ’์„ ์ˆ˜์ •ํ•˜๋Š” ๊ฐœ๋…์€ ์—†๋‹ค. ์ƒˆ๋กœ์šด ๊ฐ’ ์ถ”๊ฐ€ or ๊ธฐ์กด ๊ฐ’ ์‚ญ์ œ.
member.getFavoriteFoods().remove("์น˜ํ‚จ")
member.getFavoriteFoods().add("ํ•œ์‹")

em.persist(member);

 

 

โœ” DB ์ฟผ๋ฆฌ๋„ ๊ทธ๋ ‡๋‹ค. ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ๊ต์ฒด๋ฅผ ํ•ด๋ฒ„๋ฆฐ๋‹ค.

@OrderColumn ์„ ์ด์šฉํ•ด์„œ ํ…Œ์ด๋ธ” ์ดˆ๊ธฐํ™”๊ฐ€ ์•„๋‹Œ ์—…๋ฐ์ดํŠธ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๊ฒŒ ํ•  ์ˆ˜๋Š” ์žˆ์ง€๋งŒ.. null์ด ์œ„ํ—˜ํ•˜๊ณ  ๋ฒˆ๊ฑฐ๋กญ๋‹ค.

์ฐจ๋ผ๋ฆฌ Entity (AddressEntity)๋กœ ๋ฐ”๊ฟ”์„œ ์‚ฌ์šฉํ•˜์ž.

member.getAddresshistory().add( new Address( "old1", "city" ) );
member.getAddresshistory().remove( new Address( "old1", "city" ) );
member.getAddresshistory().add( new Address( "new1", "citty" ) );

// ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋‚จ์•„์žˆ๋Š” Address = ์ด 2๊ฐœ
// ์‹ค์ œ ์ฟผ๋ฆฌ๋„ ํ…Œ์ด๋ธ”์„ ๋‚ ๋ ค๋ฒ„๋ฆฌ๊ณ , ์ƒˆ๋กœ์šด Address๋ฅผ 2๊ฐœ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

๊ธฐ๋Œ€ํ•œ๋Œ€๋กœ ๋™์ž‘์€ ํ•˜์ง€๋งŒ.. Address ํ…Œ์ด๋ธ”์„ ๋‚ ๋ ค๋ฒ„๋ฆฌ๊ณ  ๋‹ค ์ƒˆ๋กญ๊ฒŒ ๋„ฃ์—ˆ๋‹ค. (์‹๋ณ„์ž๊ฐ€ ์—†์–ด์„œ ๋ณ€๊ฒฝ์ถ”์ ์ด ์•ˆ๋จ)

 

 

โœ” ๊ฐ’ ํƒ€์ž…์€ ๋ฐ˜๋“œ์‹œ equals()์™€ hashcode()๋ฅผ ์ ์ ˆํ•˜๊ฒŒ ์ •์˜ํ•ด์ฃผ์ž

JPA๋Š” ๊ฐ’ ํƒ€์ž…์˜ equals()์™€ hashcode() ๋ฅผ ์ด์šฉํ•ด์„œ ํŠน์ • ๊ฐ’์„ ์ฐพ์•„์„œ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค.

member.getValues().remove(3) // ์ด๋Ÿฐ ๊ฑด ๋‹น์—ฐํžˆ ๋œ๋‹ค.
member.getValues().add(4)

// ์ด๋Ÿฐ ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค. JPA๊ฐ€ equals์™€ hashcode ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ๋น„๊ต ํ›„ ์‚ญ์ œํ•œ๋‹ค.
member.getValues().remove( new Address("old1", "street", "10000") );

๊ทธ๋ž˜์„œ ์•„๋ž˜์™€ ๊ฐ™์ด, ๊ฐ’ ํƒ€์ž…์„ ๋งŒ๋“ค ๋• equals์™€ hashcode๋ฅผ ์ œ๋Œ€๋กœ ๊ตฌํ˜„ํ•˜๋„๋ก ํ•˜์ž.

์ด๋ ‡๊ฒŒ ๊ฐ’ ํƒ€์ž…(Address)์— ์ œ๋Œ€๋กœ ์ •์˜๊ฐ€ ๋˜์–ด์žˆ์ง€ ์•Š์œผ๋ฉด, remove๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.

 

โžก ๊ทธ๋ž˜์„œ ๊ตณ์ด ๊ฐ’ ํƒ€์ž… Collection ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ๋ณด๋‹ค Entity๋กœ ์ „ํ™˜ํ•˜๋Š”๊ฒŒ ๋” ๋‚˜์„ ์ˆ˜ ์žˆ๋‹ค.

์•ž์—์„œ ๋งํ•œ Cascade ALL + Orphan Removal ์„ ์ด์šฉํ•˜๋ฉด Entity๋ฅผ ๊ฐ’ ํƒ€์ž… ์ปฌ๋ ‰์…˜์ฒ˜๋Ÿผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

 

๐Ÿ“Œ ๊ฐ’ ํƒ€์ž…์€, ์ •๋ง '๊ฐ’'์ผ ๋•Œ์—๋งŒ ์‚ฌ์šฉํ•˜์ž.

โžก ๊ฐ’ ํƒ€์ž… Collection ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ๋ณด๋‹ค @OneToMany Entity๋กœ ์ „ํ™˜ํ•˜๋Š”๊ฒŒ ๋” ๋‚˜์„ ์ˆ˜ ์žˆ๋‹ค.

์•ž์—์„œ ๋งํ•œ Cascade ALL + Orphan Removal ์„ ์ด์šฉํ•˜๋ฉด Entity๋ฅผ ๊ฐ’ ํƒ€์ž… ์ปฌ๋ ‰์…˜์ฒ˜๋Ÿผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์‹๋ณ„์ž๊ฐ€ ํ•„์š”ํ•˜๊ณ  ์ง€์†ํ•ด์„œ ๋ณ€๊ฒฝ์„ ์ถ”์ ํ•ด์•ผํ•œ๋‹ค๋ฉด (๊ต์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ ์ผ๋ถ€ ์ˆ˜์ •๋จ์„ ์ถ”์ ํ•ด์•ผ ํ•œ๋‹ค๋ฉด) ๊ทธ๊ฑด ๊ฐ’์ด ์•„๋‹Œ ์—”ํ‹ฐํ‹ฐ์ž„์„ ๋ช…์‹ฌํ•˜์ž.

 

 

 

์ฐธ๊ณ ๋กœ ๊ฐ’ ํƒ€์ž…์€ ์—”ํ‹ฐํ‹ฐ ๋„๋ฉ”์ธ ๋ชจ๋ธ์—์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋‹ค.

Address <<Value Type>>

 

๋ธ”๋กœ๊ทธ์˜ ์ •๋ณด

JiwonDev

JiwonDev

ํ™œ๋™ํ•˜๊ธฐ