본문 바로가기
Back-end/JPA 개념

18. 값 타입(1) - 개념

by devraphy 2022. 4. 14.

0. 개요

- 이번 포스팅에서는 JPA의 데이터 타입에 대해서 알아보자. 

 

1. 값 타입

- JPA는 두 가지 데이터 타입을 사용한다.

 

a) Entity 타입

- JPA의 최상위 타입이다.

 

- @Entity로 정의된 클래스 객체를 의미한다.

 

- Entity 타입은 값이 변경되어도 식별자(= id, PK)를 이용하여 추적이 가능하다.

   ex) 게시판의 1번 글(= 객체)의 내용이 수정 또는 삭제되어도 1번 아이디를 이용하여 객체의 상태를 추적할 수 있다. 

 

b) 값 타입

- int, String, Integer와 같이 자바의 Primitive 또는 Wrapper 클래스를 이용하여 생성된 객체

 

- 식별자가 없으므로 값의 변경 시 추적이 불가능하다. 

   ex) int a = 100이라면, 이를 a = 200으로 변경해도 변수 a의 값이 과거에 100이었다는 것을 알 수 없다.

   ex) 게시판의 1번 글의 내용이 변경되면, 과거에 1번 글의 내용이 무엇이었는지 알 수 없다. 

 

c) 값 타입의 분류

- 값 타입은 3가지 종류로 구분된다.

  → 기본 값 타입(primitive, wrapper, String)

  → 임베디드 타입(embedded type, 복합 값 타입, 기본 값을 묶어서 하나의 값으로 사용)

  → 컬렉션 타입(collection value type, 자바 collections를 이용한 값)

 

2. 기본 값 타입

- 기본 값 타입은 primitive 자료형 또는 wrapper 클래스를 통해 정의되는 값을 말한다.

 

- 기본 값 타입의 생명 주기는 Entity에 의존한다. (Entity에 포함되는 개념이기 때문이다.)

   ex) 회원 Entity를 삭제하면, 회원 Entity를 구성하는 속성 값(= 기본 값 타입) 또한 삭제된다.

 

- 기본 값 타입은 공유되지 않고, 공유해서도 안 된다. 

   ex) 회원 A의 String name 속성을 변경했는데 회원 B의 String name 속성이 변경되면 안 된다.  

 

- 애초에 자바의 기본(primitive) 타입은 공유가 되지 않는다.

    → 아래 사진을 보면 자바의 기본 타입은 값이 복사가 되어 넘어가는 방식이지 공유되는 것이 아니다. 

    → 즉, 메모리 상에서 변수 a와 b는 서로 다른 메모리 공간을 할당받고 있는 것이다. 

 

- Wrapper 클래스를 사용하는 경우, 값은 공유되지만 값을 변경할 수 있는 방법이 없다. 

  → 아래 예시를 보면, 객체 b는 객체 a의 메모리 주소 값을 참조(= 공유)하지만 객체 a의 값을 변경할 방법이 없다.

  → 즉, 정의된 객체의 값을 변경할 수 있는 메서드가 존재하지 않는다. 

 

3. 임베디드 타입(Embedded)

a) 개념

- 값 타입의 한 종류로, 기본 값 타입을 조합하여 새로운 값 타입을 정의하는 방식으로 사용하는 타입이다.

- 다른 이름으로 복합 값 타입이라고 부르기도 한다.

 

- 기본 값 타입뿐만 아니라, Entity와 함께 조합할 수도 있다.

- 즉, 공통된 속성을 묶어 클래스로 만든다는 것이 임베디드 타입의 핵심이다. 

 

- 값 타입의 종류이므로, 임베디드 타입 또한 Entity의 생명주기에 종속적이다. 

- JPA는 임베디드(Embedded) 타입이라고 말할 수 있다.

 

b) 추상화를 통한 클래스 생성

- 임베디드 타입은 쉽게 말해서 추상화를 통해 묶어낼 수 있는 기본 값 타입을 하나의 클래스로 만드는 것이다.

- 예를 들어, 회원은 다음과 같이 이름, 나이, 도시, 상세 주소, 우편번호를 가진다고 해보자.

public class Member {
   String name;
   int age;
   
   String city;
   String street;
   String zipCode;
}

 

- 그러나 이를 추상화 하면, 회원은 개인 정보와 주소 정보를 가진다고 표현할 수 있다.

- 더불어, 추상화를 통해 묶은 속성 정보를 클래스로 다음과 같이 뽑아낼 수도 있다.

public class Member {
   PersonalInfo info;
   Address address;
}

 

- 이처럼 기본 값 타입을 클래스 단위로 묶어 추출하여 사용하는 것을 임베디드 타입이라고 한다.

 

c) 임베디드 타입을 사용하는 이유 

- 임베디드 타입을 사용하면 코드의 재사용성이 증가한다.

  → 회원 객체는 개인 정보와 주소 정보를 포함하고 있기 때문이다.

 

- 특정 임베디드 타입을 고정적으로 사용하는 객체가 존재한다. 즉, 코드의 응집도가 증가한다. 

  ex) 회원 객체는 반드시 개인 정보와 주소 정보를 갖는다. 

 

- 더불어, 임베디드 타입은 클래스의 형태로 구현되기 때문에 공통된 속성을 이용한 메서드를 구현할 수 있다.

   ex) address.setCity(), address.setStreet();

 

d) @Embeddable ,@Embedded

- 임베디드 타입을 구현하는 방법에 대해서 알아보자. 

 

- 임베디드 값 타입을 정의한 클래스에는 @Embeddable 어노테이션을 부착한다.

- 참고로, 임베디드 값 타입을 정의할 때에는 반드시 기본 생성자를 포함해야한다. 

@Embeddable // 임베디드 값 타입을 정의한다는 어노테이션
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;
    }
    
    // Getter & Setter 생략
}

 

- 임베디드 값 타입을 호출하여 사용하는 필드에는 @Embedded 어노테이션을 부착한다.

@Entity
public class Member extends BaseEntity {

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

    @Embedded // 임베디드 값 타입을 사용한다는 어노테이션
    private Address address;
    
    // Getter & Setter 생략
}

 

e) 코드 실행

- 다음 코드를 사용해서 실행해보자. 

 

- DB를 확인해보면 다음과 같이 값이 잘 들어간 것을 확인할 수 있다.

 

f) @AttributeOverrides, @AttributeOverride

- 만약 하나의 Entity안에서 동일한 임베디드 타입을 중복적으로 사용한다면 어떻게 될까?

- 예를 들어, 회원 객체가 2가지 주소(= 집 주소, 회사 주소)를 가진다면 어떻게 구현할까?

- 이때 사용할 수 있는 어노테이션이 @AttributeOverrides다. 

 

- 다음 예시 코드를 통해 사용법을 익혀보자. 

@Entity
public class Member extends BaseEntity {

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

    @Embedded
    private Address homeAddress;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "city", column = @Column(name = "WORK_CITY")),
            @AttributeOverride(name = "street", column = @Column(name = "WORK_STREET")),
            @AttributeOverride(name = "zipcode", column = @Column(name = "WORK_ZIP")),})
    private Address workAddress;

    @OneToMany(mappedBy = "member")
    private List<Order> order = new ArrayList<>();
    
    // Getter & Setter 생략

}

 

- 위의 코드처럼, 중복된 칼럼 명을 일일이 재정의 해야한다.

 

- 다음 코드를 실행하면 쿼리 문과 DB를 통해 올바르게 칼럼이 들어간 것을 확인할 수 있다. 

 

g) 결론 

- 임베디드 타입은 Entity의 값일 뿐이다.

- 임베디드 타입을 사용하든 안 하든, 매핑하는 테이블은 동일하다. 

- 다만, 객체와 테이블을 세밀하게 매핑하는 것이 가능하다는 장점이 있다.

- 더불어, 조금 더 객체지향스럽게 개발할 수 있다는 장점이 있다. 

- 잘 설계한 ORM 애플리케이션은 이처럼 매핑한 테이블의 수보다 클래스의 수가 더 많다.

댓글