[JPA] 값타입(JPA 기본편 by 김영한)

su_y2on·2022년 2월 1일
1

JPA

목록 보기
12/17
post-thumbnail

값타입


엔티티 타입 vs 값 타입

엔티티 타입은 이제까지 @Entity를 붙여서 관리하던 클래스들입니다. 이 타입들은 pk값으로 관리가 되기때문에 데이터가 변해도 쉽게 추적이 가능하고 관리도 편리합니다.

그에비해 갑타입은 int, String과 같은 단순히 값으로 사용하는 자바 기본타입이나 객체를 말합니다. 이런 타입들은 식별자가 없기 때문에 값이 바뀌면 추적하기가 어려워집니다.




값 타입

값타입에도 여러가지 하위 타입들이 있습니다. 저는 이부분을 신경쓰지 않았더니 나중에 굉장히 헷갈리더라구요! 그래서 한번 깔끔히 정리해보도록 하겠습니다:)

1. 기본값 타입

기본값 타입에는 자바 기본타입(int, double..), 래퍼클래스(Integer, Long..), String이 있습니다. 이런 기본 값 타입들은 생명주기가 속해있는 엔티티와 같습니다. 예를 들어 Student라는 엔티티에 있는 int age, String name은 student1객체가 삭제 되면 같이 삭제 됩니다.

2. 임베디드 타입

복합 값타입으로 새로운 값타입을 직접 정의할 수 있습니다. 예를들어 회원 정보에서 비슷한 정보끼리 묶어 관리하고 싶다면 그런 묶음을 임베디드 타입으로 만들어 주고 사용하면 됩니다.

지금 같은 경우 회원 정보중 startDate와 endDate를 묶어 Periodcity, street, zipcode를 묶어 Address로 관리하려는 것입니다. 사용 방법은 아래와 같이 Address와 Period 클래스에 @Embeddable을 붙여주고 Member에는 @Embedded를 붙여주면 됩니다. 이때 임베디드타입들은 기본생성자가 필수적으로 있어야합니다.

@Embeddable
public class Address {

    private String city;
    private String street;
    private String zipcode;

    public Address() { // 기본생성자 필수 
    }
    
}
@Embeddable
public class Period {

    private LocalDateTime startDate;
    private LocalDateTime endDate;

    public Period() { // 기본생성자 필수 
    }
    
}
public class Member {

	@Id @GeneratedValue
    	@Column(name = "MEMBER_ID")
    	private Long id;

    	@Column(name = "USERNAME")
    	private String username;
        
        //period
    	@Embedded
    	private Period period;

    	//address
    	@Embedded
    	private Address homeAddress;
        
 }

장점🍒

  • 재사용성
  • 높은 응집도
  • 임베디드 타입에 사용할 특정 메서드를 따로 관리가능

임베디드 타입으로 빼서 관리를 해도 결국 Member 테이블에 변화는 없습니다. 임베디드 타입으로 만들어준 Period와 Address가 모두 Member테이블에 들어가있습니다. 또 임베디드 타입에 값이 null이면 즉 넣어주지 않으면 해당하는 컬럼들은 모두 null로 채워집니다.




3. 컬렉션 값 타입

컬렉션 값 타입은 다음 포스트에 정리하도록 하겠습니다 🤗





모든 값타입은 생명주기가 속해있는 엔티티에 의존합니다!



값타입과 불변객체

값타입들은 기본적으로 공유를 하면 안됩니다. 예를들어 다른 회원의 나이가 변경되었다고 다른 회원의 이름도 변경되면 안된다는 말입니다.. 기본값 타입기본 타입은 애초에 값을 복사하기 때문에 공유를 할 수 없습니다. 하지만 래퍼클래스나 String은 참조값을 복사하기 때문에 공유가 가능하지만 수정이 불가능하기때문에 괜찮습니다.

임베디드타입은 직접정의한 객체타입이기 때문에 기본값 타입과는 다르게 공유가가능하고 수정 또한 가능해서 유의해야합니다. 아래와 같이 회원1과 회원2의 주소가 정말 우연히 같아서 하나의 Address객체를 만든 뒤 같이 넣어주면 공유하게됩니다!! 회원1이나 2가 이후 주소를 변경해도 함께 변경되기 때문에 유의해야합니다.

Address address = new Address("city","street","zipcode");

Member member1 = new Member();
member1.setUsername("A");
member1.setAddress(address);

Member member2 = new Member();
member2.setUsername("B");
member2.setAddress(member1.getAddress());

이런 side effect는 오류를 찾기도 매우 까다롭기때문에 같은 값이라도 임베디드 타입의 각각 기본값타입들을 복사해서 넘겨주는 방식을 취해야합니다.

Address copyAddress = member1.getAddress();
member1.setAddress( new Address(
	copyAddress.getCity(), copyAddress.getStreet(), copyAddress.getZipCode() 
	))

또 다른 해결방법은 불변객체로 만드는 것입니다. 이는 생성시점이후에 변경할 수 없는 객체로 String등이 이에 속합니다. 불변객체로 만드는 방법은 setter를 정의하지 않거나 private로 정의하면 됩니다.

    public String getCity() {
        return city;
    }

    private void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    private void setStreet(String street) {
        this.street = street;
    }

    public String getZipcode() {
        return zipcode;
    }

    private void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }




값타입의 비교


1. 동일성 비교

== 을 이용해서 비교하는 방법이며 인스턴스의 참조 값을 비교합니다. 자바의 기본타입값이 같으면 같은 공간을 쓰기 때문에 아래와 같은 경우 최종적으로 b와 c는 같은 값, 같은 주소를 갖게 됩니다.

int a = 10;
int b = a; // < 기본타입 >공유 되는 것 아님 : 값만 넘어감
int c = 10;

a = 20;

System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
System.out.println("b == c : " + (b == c));
System.out.println("b = " + System.identityHashCode(b));
System.out.println("c = " + System.identityHashCode(c));

출력

a = 20
b = 10
c = 10
b == c : true
b = 157627094
c = 157627094




2. 동등성 비교

동등성 비교는 인스턴스의 값을 비교하는 것이며 equals()사용해서 판별합니다. 자바의 기본타입을 제외하고는 동등성 비교를 해줘야 값만 비교가 가능합니다.

따라서 값 타입은 자바의 기본타입을 제외하고는 모두 equals연산을 사용해야하며 오브젝트타입 같이 따로 정의해준 타입은 equals를 따로 정의해줘야합니다.

Address같은 경우 이렇게 equals를 override해서 각각의 요소마다 비교를 할 수 있도록 해야합니다.(단축키 ctrl + enter -> equals and 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);
    }

0개의 댓글