값타입

JPA의 값타입

JPA의 데이터타입은 Entity타입값 타입 두가지로 나눌 수있다.

Entity타입@Entity로 정의된 객체이고,
데이터가 변해도 식별자를 통해서 추적가능하다.
EX) 회원 엔티티의 키나 값을 변경해도 @Id 식별자로 인식 가능

값 타입entity가 가지고 있는, int, String, 처럼 단순한 값이다.
값 타입entity와 라이프 사이클을 같이한다.
식별자가 없고 값만 있어서 변경시 추적이 불가능하다.

<br>
<br>

값 타입을 세가지로 구분 할 수가 있다

  1. 기본값 타입 (int, Integer, String)
  2. 엠베디드 타입
  3. 컬렉션 타입

값 타입을 사용함에 있어서 중요한 점이 있다.
바로 공유의 문제이다

자바의 기본타입(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는 결국 값타입의 하나인 것임을 명심하자.
embeddedentity의 라이프 사이클에 의존한다.
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이 가진 addresscity를 바꾸면 어떻게 될까?
자바는 주소를 참조하기 때문에, m2가 가진 address의 값도 바뀌게 되버리는 것이다.
즉, 의도치않게 m2city필드 값도 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값 타입이며 식별자를 가지고 있지 않는다.
따라서 JPAm1.id를 가진 모든 address를 삭제한다.
그리고 기존의 a2와, 새로추가된 a3insert쿼리를 요청하는 것이다.

    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만을 조회하는 경우가 코드에 포함되고, 앞으로 포함될 예정이라고 가정해보자.
그러면 addressembedded가 아닌 entity이어야 할 것이다.

0
이전 댓글 보기
등록
TOP