[κΉμν_μλ° ORM νμ€ JPA νλ‘κ·Έλλ° - κΈ°λ³ΈνΈ] 8. κ° νμ
π κ° νμ
JPAμ λ°μ΄ν° νμ λΆλ₯
- μν°ν° νμ (@Entityλ‘ μ μνλ κ°μ²΄ → λ°μ΄ν°κ° λ³ν΄λ μλ³μλ‘ μ§μν΄μ μΆμ κ°λ₯)
- κ° νμ
κ° νμ λΆλ₯
- κΈ°λ³Έκ° νμ (Ex. int, double, Integer, Long, String, ···)
- μλ² λλ νμ (λ³΅ν© κ° νμ → μ§μ μ μ κ°λ₯)
- 컬λ μ κ° νμ (Ex. Set, List, ···)
π μλ² λλ νμ
μλ² λλ νμ μ μλ‘μ΄ κ° νμ μ μ§μ μ μν μ μλ€. μ£Όλ‘ κΈ°λ³Έ κ° νμ μ λͺ¨μμ λ§λ€κΈ° λλ¬Έμ 'λ³΅ν© κ° νμ 'μ΄λΌκ³ λ νλ€. μλ² λλ νμ μ int, Stringκ³Ό κ°μ 'κ° νμ 'μΌλ‘μ¨ μν°ν°κ° μλκ³ , νλ² κ°μ λ³κ²½νλ©΄ μΆμ μ΄ λΆκ°λ₯νλ€.
μλ² λλ νμ μ μΈμ μ¬μ©ν κΉ?
DBμ νμ ν μ΄λΈμ 근무 μμμΌ, 근무 μ’ λ£μΌ, μ£Όμμ λμ, μ£Όμμ λλ‘λͺ , μ£Όμμ μ°νΈλ²νΈκ° μλ μν©μ μκ°ν΄λ³΄μ.
κ°μ²΄ κ΄μ μμ 보μμ λ, 근무 μμμΌκ³Ό μ’ λ£μΌμ Periodλ‘, μ£Όμ κ΄λ ¨ μμ±λ€μ Addressλ‘ λ¬Άμ΄μ λ³λμ ν΄λμ€λ₯Ό λ§λ€λ©΄ μ’μ§ μμκΉ? μ΄λ κ² νλ©΄ ν΄λμ€ μ¬μ¬μ©μ΄ κ°λ₯ν΄μ§κ³ Period.isWork() κ°μ μλ―Έμλ λ©μλλ₯Ό λ§λ€ μ μκ² λλ€.
μλ² λλ νμ μ¬μ© λ°©λ²μ μμ보μ.
@Embeddableμ κ°νμ μ μ μνλ κ³³μ νμνκ³ , @Embeddedλ κ°νμ μ μ¬μ©νλ κ³³μ νμνλ€. κ·Έλ¦¬κ³ κ°νμ μ μ μνλ κ³³μ λ°λμ κΈ°λ³Έμμ±μλ₯Ό λ§λ€μ΄μΌ νλ€.
μ½λ μμ:
@Entity
public class Member {
@Embedded
private Period workPeriod;
@Embedded
private Address homeAddress;
@Embeddable
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
public Period() {
}
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
public Address() {
}
μ€ν κ²°κ³Ό:
Member member = new Member();
member.setUsername("shj");
member.setHomeAddress(new Address("μμΈ", "μμ°μ°λ‘", "94"));
member.setWorkPeriod(new Period(LocalDateTime.now(), LocalDateTime.now().plusYears(1L)));
em.persist(member);
transaction.commit(); // 컀λ°
μ°Έκ³ λ‘ μλ² λλ νμ μμ μν°ν°λ₯Ό λ£λ κ²λ κ°λ₯νλ€. (Ex. Memberκ° Address μλ² λλ νμ μ μ°Έμ‘°νκ³ Address μλ² λλ νμ μ΄ House μν°ν°λ₯Ό μ°Έμ‘°νλ κ²μ΄ κ°λ₯)
λ§μ½, ν μν°ν°μμ κ°μ κ°νμ μ μ¬λ¬λ² μ¬μ©νκ³ μΆμΌλ©΄ μ΄λ»κ² ν΄μΌν κΉ? μλμ κ°μ΄ μ¬μ©νκ³ μΆμ μ μμ κ²μ΄λ€. κ·Έλ΄ λλ @AttributeOverrides, @AttributeOverrideλ₯Ό μ°λ©΄ λλ€.
@Entity
public class Member extends BaseEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "MEMBER_ID")
private Long id;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "HOME_CITY")),
@AttributeOverride(name = "street", column = @Column(name = "HOME_STREET")),
@AttributeOverride(name = "zipcode.zip", column = @Column(name = "HOME_ZIP")),
@AttributeOverride(name = "zipcode.plusFour", column = @Column(name = "HOME_PLUS_FOUR")),
})
private Address homeAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "COMPANY_CITY")),
@AttributeOverride(name = "street", column = @Column(name = "COMPANY_STREET")),
@AttributeOverride(name = "zipcode.zip", column = @Column(name = "COMPANY_ZIP")),
@AttributeOverride(name = "zipcode.plusFour", column = @Column(name = "COMPANY_PLUS_FOUR")),
})
private Address companyAddress;
}
@Embeddable
public class Address {
private String city;
private String street;
@Embedded
private Zipcode zipcode;
}
@Embeddable
public class Zipcode {
private String zip;
private String plusFour;
}
π κ°μ²΄ νμ μ νκ³, λΆλ³ κ°μ²΄
μλ² λλ νμ μ κ° νμ μ΄κ³ κ°μ²΄ νμ μ΄λ€. κ°μ²΄ νμ μ μλ°μ κΈ°λ³Έ νμ (Ex. int, double)κ³Ό λ€λ₯΄κ² '=' μ°μ°μ ν λ κ°μ 볡μ¬νλ κ²μ΄ μλλΌ μ°Έμ‘°λ₯Ό μ λ¬νλ€.
λ°λΌμ, μλμ κ°μ μ½λλ₯Ό μμ±ν κ²½μ°, bμ μ£Όμλ§ λ°κΎΈλ €λ μλμ μλμ λ€λ₯΄κ² aμ μ£ΌμκΉμ§ λ°λκ² λλ€.
Address a = new Address(“Old”);
Address b = a; // κ°μ²΄ νμ
μ μ°Έμ‘°λ₯Ό μ λ¬
b.setCity(“New”);
μ΄λ° λΆμμ©μ λ§κΈ° μν΄μλ βοΈκ° νμ μ λΆλ³ κ°μ²΄λ‘ μ€κ³βοΈν΄μΌ νλ€. λΆλ³ κ°μ²΄λ‘ λ§λλ κ°μ₯ μ¬μ΄ λ°©λ²μ setter()λ₯Ό λ§λ€μ§ μλ κ²μ΄λ€. βοΈμμ±μλ‘λ§ κ°μ μ€μ βοΈν μ μκ² λ§λ€λ©΄ λλ€. κ°μ μμ νκ³ μ ν κ²½μ° μλ‘μ΄ κ°μ²΄λ₯Ό μμ±νλ©΄ λλ€. μ°Έκ³ λ‘ Integer, Stringμ μλ°κ° μ 곡νλ λνμ μΈ λΆλ³ κ°μ²΄μ΄λ€.
π κ° νμ μ λΉκ΅
κ° νμ μ λΉκ΅λ == μΌλ‘ νλ©΄ μλκ³ , equals() λ©μλλ₯Ό μ μ νκ² μ¬μ μν΄μ μ¬μ©ν΄μΌ νλ€. (μ£Όλ‘ λͺ¨λ νλλ₯Ό λΉκ΅ν¨) μ°Έκ³ λ‘ μΈν 리μ μ΄λ 'Generate equals() and hashCode()' κΈ°λ₯μ μ 곡νκ³ μλ€.
π κ° νμ 컬λ μ
κ° νμ 컬λ μ μ κ° νμ μ 컬λ μ μ λ΄μμ μ°λ κ²μ λ§νλ€.
κ·Έλ°λ° μΌλ°μ μΌλ‘ DBμλ 컬λ μ μ λ΄μ μ μλ νμ μ΄ μλ€. λ°λΌμ, μΌλλ€ κ°λ μ²λΌ λ³λμ ν μ΄λΈλ‘ λ§λ€μ΄μΌ νλ€.
μλ₯Ό λ€μ΄, Set<String> favoriteFoods 컬λ μ μ Member ν μ΄λΈμ μ μ₯λλ κ²μ΄ μλλΌ FAVORITE_FOODλΌλ λ³λμ ν μ΄λΈμ λ§λ λ€.
κ·Έλ¦¬κ³ FAVORITE_FOODμ PKλ λͺ¨λ 컬λΌλ€μ μ‘°ν©μ΄λ€. μ¦, FKμΈ MEMBER_ID + FOOD_NAME λͺ¨λμ μ‘°ν©μ΄ PKκ° λλ€.
λ€μ λ§νμλ©΄, κ° νμ 컬λ μ μ λ³λμ ν μ΄λΈλ‘ λ§λ€λ©°, λ³λμ μλ³μ μμ΄ ν μ΄λΈμ μΈλ ν€μ λ€λ₯Έ 컬λΌλ€μ μ‘°ν©ν΄ PKλ‘ μ΄λ€. (λ³λμ μλ³μκ° μ‘΄μ¬νλ€λ©΄ μν°ν°μ λ€λ₯Ό κ² μμΌλκΉ.)
μ½λ μμ:
@Entity
public class Member {
...
@ElementCollection
@CollectionTable(
name = "FAVORITE_FOOD",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME") // 컬λΌλͺ
μ§μ (μμΈ)
private Set<String> favoriteFoods = new HashSet<>();
@ElementCollection
@CollectionTable(
name = "ADDRESS",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();
...
}
κ° νμ 컬λ μ μ μ₯
μ΄μ κ° νμ 컬λ μ μ μ μ₯ν΄λ³΄μ.
μλ μ½λμμ λ³Ό μ μλ―μ΄ FavoriteFoodsμ AddressHistoryμ λν΄μλ em.persist();λ₯Ό νμ§ μκ³ Memberμ λν΄μλ§ em.persists(member);λ₯Ό ν΄λ DBμ 6κ°μ ν λͺ¨λκ° μ μ₯λλ€. (Memberμ λΌμ΄νμ¬μ΄ν΄μ ν¨κ»νλ€.)
βοΈμ¦, κ° νμ 컬λ μ μ μμμ± μ μ΄(cascade=CascadeType.ALL)μ κ³ μ κ°μ²΄ μ κ±° κΈ°λ₯(orphanRemoval=true)μ νμλ‘ κ°μ§λ€.βοΈ
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity", "street", "10000"));
member.getFavoriteFoods().add("μΉν¨");
member.getFavoriteFoods().add("μ‘±λ°");
member.getFavoriteFoods().add("νΌμ");
member.getAddressHistory().add(new Address("old1", "street1", "10001"));
member.getAddressHistory().add(new Address("old2", "street2", "10002"));
em.persist(member);
κ° νμ 컬λ μ μ‘°ν
λν, κ° νμ 컬λ μ μ λν΄νΈλ‘ μ§μ°λ‘λ© μ λ΅μ μ¬μ©νλ€. λ°λΌμ Memberλ₯Ό μ‘°ννλ©΄ FavoriteFoodsμ AddressHistory ν μ΄λΈκΉμ§ λΆλ¬μ€λ κ²μ΄ μλλΌ, μ΄ν FavoriteFoodsμ AddressHistoryλ₯Ό μ‘°νν λ μΏΌλ¦¬λ¬Έμ΄ λκ°λ€.
κ° νμ 컬λ μ μμ
μμμ μΈκΈνλ€μνΌ κ° νμ μ λΆλ³μ΄λ€. λ°λΌμ κ° νμ 컬λ μ μ μμ νκ³ μ νλ€λ©΄, μλ‘μ΄ κ°μ²΄λ₯Ό μμ±ν΄μΌ νλ€. (set ν¨μ μ¬μ© X)
μμ 1(κ° νμ ):
Member findMember = em.find(Member.class, member.getId());
// homeCity -> newCity
// findMember.getHomeAddress().setCity("newCity"); // νλ¦° λ°©λ²
Address address = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity", address.getStreet(), address.getZipCode())); // μλ‘ μμ±
μμ2(κ° νμ 컬λ μ ):
Member findMember = em.find(Member.class, member.getId());
// μ‘±λ° -> νλ²κ±°
findMember.getFavoriteFoods().remove("μ‘±λ°");
findMember.getFavoriteFoods().add("νλ²κ±°");
+κ° νμ 컬λ μ μμλ₯Ό μ§μΈ λ equals ν¨μκ° μ λλ‘ κ΅¬νλμ΄ μλ κ²μ΄ μ€μνλ€.
κ° νμ 컬λ μ μ μ μ½μ¬ν
βοΈκ° νμ 컬λ μ μ λ³κ²½μ¬νμ΄ λ°μνλ©΄, μ£ΌμΈ μν°ν°μ κ΄λ ¨λ λͺ¨λ λ°μ΄ν°λ₯Ό μμ νκ³ , κ° νμ 컬λ μ μ μλ νμ¬κ°μ λͺ¨λ λ€μ μ μ₯νλ€.βοΈ
μλ₯Ό λ€μ΄ μλμ μμμμλ ADDRESS ν μ΄λΈμ μλ κΈ°μ‘΄μ λͺ¨λ λ°μ΄ν°("another city", "old city")λ₯Ό μμ νκ³ , κΈ°μ‘΄μ μλ "another city"λ₯Ό λ€μ μ μ₯νκ³ "new city"λ₯Ό μ μ₯νλ€.
Member findMember = em.find(Member.class, member.getId());
// νμ¬ ADDRESS ν
μ΄λΈμλ "another city", "old city" μ‘΄μ¬
// old city -> new city
findMember.getAddressHistory().remove(new Address("old city", "street", "12345"));
findMember.getAddressHistory().add(new Address("new city", "street", "12345"));
// μ΄μ "another city", "new city" μ‘΄μ¬
κ·Έλμ... κ²°λ‘ μ?
μ΄λ° μ λ° μ μ½μ¬νμ΄ λ§μ κ° νμ 컬λ μ μ μ€λ¬΄μμ μ¬μ©νμ§ μλλ€. (μ λ°μ΄νΈλ μΆμ ν νμ μλ λ¨μν μν©μΌ λλ§ μ¬μ©)
λμ μ, μν°ν°λ‘ κ°μΈμ(wrapping) μΌλλ€ κ΄κ³λ‘ μ°ννλ€.
μμ:
AddressEntity
@Entity
@Table(name = "ADDRESS")
public class AddressEntity {
@Id
@GeneratedValue
private Long id;
private Address address; // μν°ν°λ‘ κ°μΌ λͺ¨μ΅
}
Member
@Entity
public class Member {
...
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<>();
...
}