1. 값 타입 공유
💡임베디드 값 타입의 실제 인스턴스인 값을 공유하면 위험하다.
1-1) 실제 인스턴스인 값을 공유했을 때
✅ Address(임베디드 값 타입)와 Member(엔티티) 코드
@Embeddable
@Getter
@Setter
public class Address {
private String city;
private String street;
private String zipcode;
public Address() {
}
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
}
@Entity
@Getter
@Setter
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@Embedded
private Period workPeriod;
@Embedded
private Address homeAddress;
}
✅ main 메서드
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Address address = new Address("Seoul","street","10000");
Member member1 = new Member();
member1.setUsername("member1");
member1.setHomeAddress(address);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(address);
em.persist(member2);
tx.commit();
}catch (Exception e){
tx.rollback();
e.printStackTrace(); //예외 출력
}finally {
em.close();
}
}
- member1과 member2가 address를 공유하고 있다.
- DB에 값이 잘 들어갔다. 그런데, member1의 값만 Incheon으로 바꾸고 싶으니 코드를 추가해보자.
main 메서드에 코드 한줄을 추가했다.
//의도: member1의 city필드만 Incheon으로 변경하고 싶음
member1.getHomeAddress().setCity("Incheon");
원래 의도는 member1의 city 필드만 'Incheon'으로 변경하는 것이다.
그런데, 이렇게 하면 의도와 달리 member2의 city 필드까지 같이 변경된다.
1-2) 인스턴스를 복사해서 사용했을 때(해결책)
✅main 메서드 코드 추가 및 수정
Address address = new Address("Seoul","street","10000");
//인스턴스 값을 복사
Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
Member member1 = new Member();
member1.setUsername("member1");
member1.setHomeAddress(address);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(copyAddress);//복사한 값을 사용
em.persist(member2);
//의도: member1의 city필드만 Incheon으로 변경하고 싶음
member1.getHomeAddress().setCity("Incheon");
tx.commit();
인스턴스를 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다.
1-3) 객체 타입의 한계
임베디드 값 타입은 직접 정의한 타입이라 자바 기본 타입이 아니고 객체 타입이다.
자바 기본 타입에 값을 대입하면 자바 내부에서 값을 복사하기 때문에 문제가 없다.
int a = 10;
int b = a;
b = 4;
System.out.println("a = " + a);
System.out.println("b = " + b);
//**출력
//a = 10
//b = 4
위와 같이 b에 a를 대입할 때 a의 값을 복사하기 때문에 메모리 주소값이 다르다.
그래서 a = 10, b = 4로 출력이 되는 것이다.
하지만, 임베디드 값 타입은 직접 정의한 객체 타입이기 때문에 참조 값을 직접 대입하는 걸 막을 방법이 없다.
무슨 말이냐면 IDE에서 컴파일 오류를 체크하지 못하고 실행을 해도 오류가 발생하지 않는 다는 것이다.
2. 불변 객체
- 불변 객체란, 생성 시점 이후 절대 값을 변경할 수 없는 객체다.
- 객체 타입을 수정할 수 없게 만들면 위와 같은 부작용을 원천 차단할 수 있다.
- 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 된다.
🤔Q. 그럼 값을 바꾸고 싶을 때는 어떻게 하지?
💡A1. 인스턴스를 복사해서 값을 바꾸고 임베디드 값 타입을 통째로 갈아 끼워야된다.
/*main 메서드*/
Address address = new Address("Seoul","street","10000");
Member member1 = new Member();
member1.setUsername("member1");
member1.setHomeAddress(address);
em.persist(member1);
//필드 값 바꾸기
Address newAddress = new Address("Incheon", address.getStreet(), address.getZipcode());
member1.setHomeAddress(newAddress);
Address 안에 특정 필드만 바꾼다 하더라도, Member에 있는 setter를 이용해서 address의 값을 통째로 바꿔야된다.
💡A2. Address 내부에 원본 객체 복사 메서드를 만들고 이것을 이용하는 방법도 있다.
/*Address에 메서드 추가*/
// 방어적 복사 : 원본 객체 상태를 보호
public Address copy() {
return new Address(this.city, this.street, this.zipcode);
}
/*Member에 메서드 추가(Address를 사용하는 엔티티)*/
public void setHomeAddress(Address homeAddress){
this.homeAddress = homeAddress.copy();
}
public void updateAddress(String city, String street, String zipcode){
this.homeAddress = new Address(city, street, zipcode);
}
방어적 복사를 통해 객체의 불변성을 유지할 수 있다.
'인프런 김영한 강의 정리 > 자바 ORM 표준 JPA 프로그래밍 기본편' 카테고리의 다른 글
JPA 기본 | 객체지향 쿼리 언어 알아보기(JPQL, QueryDSL 등) (0) | 2024.07.15 |
---|---|
JPA 기본 | 데이터 타입(3) - 값 타입 컬렉션 (0) | 2024.07.14 |
JPA 기본 | 데이터 타입(1) - 기본 값과 임베디드 값 타입 (0) | 2024.07.14 |
JPA 기본 | 영속성 전이(CASCADE)와 고아 객체 (0) | 2024.07.13 |
JPA 기본 | 즉시 로딩과 지연 로딩 (0) | 2024.07.13 |