Dev/JPA

JPA 데이터 타입 분류

린네의 2024. 2. 15. 00:48

 

1. 엔티티 타입이란?

  @Entity 로 정의 하는 객체를 의미하며 데이터가 변하더라도 식별자를 통해 지속적으로 추적할 수 있다.

 

 

2. 값 타입이란?

 int, Integer, String 처럼 단순히 값으로 사용하는 자바 기본타입( 래퍼클래스, String 등 .. ) 이나 객체를 의미한다. 식별자가 없고 값만 존재하므로 변경시 추적이 불가능하다.

 기본적으로 자반의 기본타입은 절대 공유 되지 않는다. 공유하지 않는다는 것의 의미를 코드로 설명하면 다음과 같다.

 

  •  기본타입이 공유되지 않을 경우
int a = 10;
int b = a; 
a = 20;


// a 는 20, b 는 10 으로 출력

 

위 코드에서  int b = a 로 선언했음에도 기본타입이 공유 되지 않고 각각 출력되는 것을 볼 수 있다.

 

  • 공유 될 경우 
Integer a = new Integer(10);
Integer b = a;

a.setValue(20);

// b 와 a 는 모두 20으로 출력

 

 Integer 라는 매퍼 클래스를 사용하 경우 공유 되어 동일하게 20 이 출력되는 것을 볼 수 있다. 
          
           

3. 임베디드 타입 이란?

   새로운 값 타입을 직접 정의할 수 있는 타입을 의미한다.  그런 의미에서,  JPA 는 임베디드 타입이라고 말할 수 있다.

기본 값 타입을 모아서 만들기 때문에 복합 값 타입이라고도 한다.

 

 

 간단한 예시를 들어보자.

 

회원이라는 엔티티는 회원식별자, 이름근무시작일, 근무종료일, 주소도시, 주소번지, 우편번호를 가진다.

 

이 때 근무시작일/종료일을 Period 로,  주소도시/주소번지/우편번호를 Address 라는 각각의 임베디드 타입으로 선언할 수 있다.     

 

 

 실제 코드로 작성할 경우 @Embeddable 과 @Embedded 를 사용할 수 있다.

 

  •  @Embeddable 애노테이션을 넣어줌 ( 값을 정의하는 클래스 위에 )
  •  @Embedded : 값을 사용하는 곳에 넣어 줌
        

코드 예시는 다음과 같다.

@Embeddable
@Getter
public class Address {
    private String city;
    private String street;
    private String zipcode;

    protected Address() {

    }

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
}

 

 

@Entity
@Getter @Setter
public class Member {

	...
    
    @Embedded
    private Address address;
    
    ...
     
}

 

 

 

  장점 

  •   재사용, 높은 응집도, Period.isWork() 처럼 해당 값 타임만 사용하는 의미 있는 메소드를 만들 수 있음.
  •   임베디드 타입을 포함한 모든 값 타입은 값타임을 소유한 엔티티에 생명주기를 의존함 

 

 

 주의 해야할 점은, 임베디드 타입은 엔티티의 값을 뿐이라는 것이다. 임베디드 타입을 사용한다고해서 Member 에 매핑되는 테이블의 속성이 달라지는 것은 아니다.  그렇기 때문에 잘 설계한 orm 애플리케이션은, 테이블 수보다 엔티티 클래스 수가 더 많다.

     

 

4. @AttributeOverrides, @AttributeOverride 

 한 엔티티안에서 동일한 값을 사용하고 싶다면 @AttributeOverrides 와 @AttributeOverrid 를 사용하면 된다.

 

예를 들어 Member 안에 동일한 Address 타입을 두번 사용하고 싶을 때 다음과 같이 사용할 수 있다.

  @Embedded
  @AttributedOverride(name = "city", column = @Column(name = "work_city"))
  private Address adress;

  @Embedded
  private Address adress2;

 

 

5.  값 타입과 불변객체 

 

기본적으로 값 타입은 복잡한 객체 세상을 조금이라도 단순화 하려고 만든 개념이므로, 단순하고 안전하게 다룰 수 있어야 한다.

 

 임베디드 타입과 같은 값 타입을 공유하게 되면 위험하므로 값을 복사해서 사용한다. 

 

 아래 코드는 두 객체 member1, member2 에 대하여 동일한 address 값 타입을 공유하는 경우에 해당한다.

Address address =  new Address( "city","street", "zipcode");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);

Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(address);
em.persist(member2);

member.getHomeAddress().setCity("newCity");

tx.commit();

 

   값 타입을 공유하게 되면 member 에 있는 city 값만 newCity 로 바뀌는게 아니라, member2 의 city 도 newCity 로 바뀐다는것을 알 수 있다.  이렇게 member 에 대한 newcity 만 바꾸길 원했지만 원하지 않는 결과 ( member2 의 city 도 newCity  로 바뀜 ) 가  나올 수 있기 때문에 값 타입의 공유는 처음부터 하지 않는게 좋다. 


 값타입을 공유하지 않고 복사를 사용하면 어떻게 될까? 다음 예제에서는 address 를 복사해서 작성했다.

 

Address address =  new Address( "city","street", "zipcode");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);

Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());

Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(copyAddress);
em.persist(member2);

member.getHomeAddress().setCity("newCity");

tx.commit();


 copyAddress  를 통해 address 를  복사 했다. 이렇게 사용하면 member 의 city 값에 대해서만 newCity 로 바꿀 수 있다.

그런데 이렇게 직접 정의한 값 타입은 자바의 기본타입이 아니라 객체 타입에 해당하므로, 근본적으로 공유 참조가 해결 된 것은 아니다. ( 객체는 참조가 공유 되는 것을 막을 수는 없다. ) 

 그렇다면 객체타입을 수정 할 수 없게 만들순 없을까 ? 그럼 부작용을 원천 차단 할 수 있으니까!  위에서 공유 참조가 발생한 원인이라고하면, newCity 라는 값을 변경하려고 한 것 부터 시작했기 때문에,  값 변경(수정) 자체를 막아 버리면 해결 될 것 처럼 보인다.

 

맞다. 값 타입은 불변 객체로 설계 해야한다.  같은 맥락에서 생성자로만 값을 설정하고 setter 를 따로 두지 않아야 한다.

 

참고로 Integer 과 String 은 자바가 제공하는 가장 대표적인 불변 객체 이다.

 

  • 불변 객체란?  생성 시점 이후 절대 값을 변경할 수 없는 객체를 의미한다.

  즉   member2.setHomeAddress(copyAddress); 에서 setHomeAddress 자체를 제거하거나 private 로 선언해서 사용하도록 하면 불변객체로 설계하게 되는 것이다. 이를 통해 공유참조 문제를 해결 할 수 있다.   

 


 번외로, 불변객체로 선언했는데 값을 바꾸고 싶을때는 다음 코드 처럼 생성자를 이용해서 바꿀 수 있다.

 Address address =  new Address( "city","street", "zipcode");
 Member member = new Member();
 member.setUsername("member1");
 member.setHomeAddress(address);
 em.persist(member);

 Address newAddress = new Address("newCity", address.getStreet(), address.getZipcode());
 member.setHomeAddress(newAddress);

 

 

5. 값 타입의 비교 

  • 동일성 : == 을 사용함 ( 인스턴스 참조값을 비교 ) 
  • 동등성 : equals 를 사용함  (  인스턴스 내의 실제 값을 비교 ) 


    값 타입의 비교는 인스턴스의 실제 값을 비교해야 한다. 따라서  equals 를 사용 해야한다. equals 구현시 hashcode() 도 함께 구현하도록 하자.

 

 

 @Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Address address = (Address) o;
    return Objects.equals(city, address.city) && Objects.equals(street, address.street) && Objects.equals(zipcode, address.zipcode);
}

@Override
public int hashCode() {
    return Objects.hash(city, street, zipcode);
}

 

 

 

6.  컬렉션 값 타입과 @ElementCollection, @CollectionTable

 

들어가기 전에,   무에서는 값 타입 컬랙션 대신에 일대다 관계를 고려하는게 낫다.  엄청 단순한걸 구현할 때나 사용한다고 보면 된다.

 

 

본론으로 돌아가서, 컬렉션 값 타입은 자바 컬렉션에 임베디드나 기본타입을 넣는것을 의미한다. 즉 값 타입을 하나이상 저장할 때 사용한다. 

 

실제 코드 작성시에는 @ElementCollection, @CollectionTable 을 사용할 수 있다.

 

 @Embeded 
 private Address homeAddress;

 @ElementCollection
 @CollectionTable(name = "favorite_foods", 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<>();

 

 값 타임 컬렉션은 영속성전에 cascade + 고아 객체 제거기능을 필수로 가진다 .  또한 지연 로딩에 해당한다.

 

  값 타입 컬렉션 제약 사항 

  • 값 타임 컬렉션에 변경 사항이 발생하면 주인 엔티티와 연관된 모든 데이터 값을 삭제하고 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
  • 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키를 구성해야 한다. ( null 입력과 중복저장을 하지않는다.)