JPA를 공부하자 03 - 값타입
값타입
JPA의 값타입
JPA의 데이터타입은 Entity타입
과 값 타입
두가지로 나눌 수있다.
Entity타입
은 @Entity
로 정의된 객체이고,
데이터가 변해도 식별자를 통해서 추적가능하다.
EX) 회원 엔티티의 키나 값을 변경해도 @Id
식별자로 인식 가능
값 타입
은 entity
가 가지고 있는, int
, String
, 처럼 단순한 값이다.
값 타입
은 entity
와 라이프 사이클을 같이한다.
식별자가 없고 값만 있어서 변경시 추적이 불가능하다.
<br>
<br>
이 값 타입
을 세가지로 구분 할 수가 있다
- 기본값 타입 (
int
,Integer
,String
) - 엠베디드 타입
- 컬렉션 타입
값 타입
을 사용함에 있어서 중요한 점이 있다.
바로 공유
의 문제이다
자바의 기본타입(int
, Integer
) 는 주소 복사가 아니라 값 복사이기 때문에 신경쓸 필요가 없다.
또한, 래퍼클래스(Integer
, Double
)도 불변객체로 정의되어있기때문에 신경 쓸 필요가 없을 것이다.
그리고 embedded
가 있다.
<br>
<br>
Embeded
임베디드는 Entity
가 가지는 값 타입
을 묶어 하나의 값으로 만드는 것을 말한다.
예를 들어,
public class Member{
private String name;
private String zipcode;
private String city;
}
이라면, zipcode
, city
는 주소
라는 공통된 개념이기 때문에
좀 더 객체지향적인 방법으로 정의 할 수 있다.
예를 들어 Address
라고 정의된 클래스를 다른 entity
가 재사용 할 수도 있다.
또한, Adrress
에 고유의 책임을 가지는 메소드를 정의 할 수 도 있다.
public class Member{
private String name;
@Embbeded
private Address address;
}
@Embeddable
public class Adderss{
private String zipcode;
private String city;
}
embedded
는 결국 값타입
의 하나인 것임을 명심하자.
embedded
는 entity
의 라이프 사이클에 의존한다.
embedded
를 정의한다고, 새로운 테이블이 새롭게 정의된것이 아니다.
embedded
에는 기본 생성자를 꼭 정의해주자.
embedded
안에 @Column
어노테이션을 사용하서 필드명을, 정의 할 수 도있을 것이다.
하지만, 여러 entity
가 같은 embedded
를 사용한다면,
entity
에 따라 별도로 필드명을 정의가 필요할 때도 있을 것이다.
그럴 경우 다음 코드를 참고하자.
public class Member{
private String name;
@Embbeded
@AttributeOverrides({
@AttributeOverride(name="city", column=@coulun("my_city")),
@AttributeOverride(name="zipcode", column=@coulun("my_zipcode"))
})
private Address address;
}
<br>
<br>
Embedded는 불변객체로 정의하자.
embedded
는 공유의 문제를 가지고 있다.
결국 embedded
를 정의하는 것은 ,
java
관점에서는 하나의 Object
를 정의하는 것이다.
java
는 기본값 타입
이 아닐 경우, Call By Reference
이다.
즉, 참조하는 값이 주소값
임을 주의해야한다.
예를 들어 다음 상황을 보자.
public void test(){
Address address = new Address("my_city", "my_zipcode");
Member m1 = new Member();
m1.setAddress(address);
Member m2 = new Member();
m2.setAddress(m1.getAddress());
}
<br>
다음 코드에서 m1
이 가진 address
의 city
를 바꾸면 어떻게 될까?
자바는 주소
를 참조하기 때문에, m2
가 가진 address
의 값도 바뀌게 되버리는 것이다.
즉, 의도치않게 m2
의 city
필드 값도 newCity
로 변경되는 것이다.
트랜잭션이 끝나면 이 값 변경이 데이터베이스에 그대로 반영되버린다.
public void test(){
m1.getAddress().setCity("newCity");
System.out.println(m2.getAddress().getCity()); // newCity
}
<br>
따라서 embedded
는 불변객체
로 정의해야한다.
대표적인 불변객체
는 String
이 있다.
다음 코드를 참고하자.
class MyString {
public void concat(String a){
return new String(this.value + a); //
}
}
<br>
<br>
값 타입 컬렉션
값 타입
을 컬렉션으로 사용할 수 도 있다.
컬렉션이라는 것은 1:N
의 관계인것이고,
때문에 별도의 테이블
을 정의 할 수 밖에 없다.
다음 키워드를 사용한다.
@ElementCollection
@CollectionTable
<br>
다음 코드를 보자
public class Member{
@ElementCollction
@CollectionTable(name = "member_address_history",
joinColumns = @JoinColumn(name="member_id")
private List<Address> addressHistory;
}
이렇게 값 타입
을 정의하면, 별도의 테이블 생성되고
1:N
관계의 조인이 생긴다.
여기서 중요한것은 식별자
를 가지고 있지 않다는 것이다.
테이블은 다음과 같이 생성된다.
create Table {
member_id // PK, FK
city varchar // PK
zipcode varchar // PK
}
(member_id, city, zipcode)
가 하나의 복합기본키로 정의되고,
member_id
가 외래키로 정의된다.
<br>
여기서 재밌는 부분의 삽입 삭제가 이루어질 때이다.
다음 코드를 보자.
public void test(){
EntityManger em;
Member m1 = new Member();
Address a1 = new Address();
Address a2 = new Address();
m1.addAddressHistory(a1);
m1.addAddressHistory(a2);
em.pesrsist(m1);
em.flush(); // SQL 쿼리 요청
//다음을 보자.
m1.removeAddressHistory(a1); // a1 삭제
Address a3 = new Address();
m1.addAddressHistory(a3); // a3 추가
em.flush() // 쿼리가 어떻게 나갈까??
}
컬렉션에서 a1
은 삭제되고, a3
는 추가 되었다.
당연히 생각하기에, a1
을 삭제하는 delete
쿼리 한번, a3
를 삭제하는 insert
쿼리를 한번 호출 할 것이라 생각된다.
하지만 그렇게 동작하지 않는다.
embedded
는 값 타입
이며 식별자를 가지고 있지 않는다.
따라서 JPA
는 m1.id
를 가진 모든 address
를 삭제한다.
그리고 기존의 a2
와, 새로추가된 a3
의 insert
쿼리를 요청하는 것이다.
delete from member_address_history where membe_id = m1.id
insert member_addres_history value a2
insert member_addres_history value a3
<br>
이 문제의 해결책으로 다음과같이 사용할 수있다.
public class Member{
@OneToMany
@JoinColumn
private List<AddressEntity> addressHistory;
}
public class AddresEntity{
@id
private Long id;
@Embedded
private Adress address;
}
<br>
<br>
의문점.
// 둘은 무슨 차이가 있지?
@OneToMany(cacasde.All, orphanremoval = true) //
@ElementCollction ///
embedded
컬렉션은 특정 entity
에 라이프 사이클 종속된다.
그리고 이전에 비슷한 내용을 기억한다.
이전에 배운 @OneToMany(casecade.ALL, orphanremoval=true)
인 연관관계이다.
이 관계에서의 child entity
또한, parent entity
에 라이프 사이클이 종속된다.
둘의 차이점을 명심하고 사용하자.
embedded
컬렉션은 정말 단순할때만 사용하는 것이다.
예를 들어 address
만을 조회하는 경우가 코드에 포함되고, 앞으로 포함될 예정이라고 가정해보자.
그러면 address
는 embedded
가 아닌 entity
이어야 할 것이다.