내가 공부한것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼겸 상세히 기록하고 얕은부분들은 가겹게 포스팅 하겠습니다.
1. 기본값 타입
JPA의 데이터 타입은 크게 2가지로 분류 가능하다. (엔티티 타입, 값 타입)
1. 엔티티 타입
- @Entity로 정의하는 객체이다.
- 데이터가 변해도 식별자로 지속적인 추적이 가능하다.
=> 예를 들어 회원 엔티티의 키나 나이값 을 변경해도 식별자로 인식 가능하다.
2. 값 타입
- int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
- 식별자가 없고 값만 있으므로 변경시 추적 불가
=> 예를 들어 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체된다.
값 타입은 다시 3가지 종류로 분류할수가 있다.
● 값 타입의 분류
1. 기본값 타입
- 자바 기본 타입(int, double)
- 래퍼 클래스(Integer, Long)
- String
2. 임베디드 타입(embedded type, 복합 값 타입)
=> 예를 들면 우편번호 , 또는 좌표(x, y)와 같은 복합 값을 Position클래스로 만들어 쓰려고하는 것을 임베디드 타입이라고 한다.
3. 컬렉션 값 타입(collection value type)
- Java collection(Array, Map, Set)에 값을 넣을수 있는 것을 컬렉션 값 타입이라 한다.
● 기본값 타입
예): String name, int age
- 생명주기를 엔티티에 의존한다.
=> 에를 들면, 회원을 삭제하면 회원의 이름, 나이 필드도 함께 값이 삭제 된다.
- 값 타입은 공유하면 안된다.
=> 회원 이름 변경시 다른 회원의 이름도 함께 변경되면 안됨
(참고. 자바의 기본 타입은 절대 공유되지 않는다)
2. 임베디드 타입(복합 값 타입)
JPA에서는 새로운 값 타입을 직접 정의할수 있습니다. 이를 임베디드 타입(embedded type)이라고 부릅니다.
주로 기본값 타입을 여러개 모아서 만들기 때문에 복합 값 타입이라고도 부릅니다.
int, String과 같은 값 타입이다. 엔티티가 아니다!
예시를 통해서 살펴보자.
1. 회원 엔티티에 이름, 근무 시작일, 근무 종료일, 주소 도시, 주소 번지, 주소 우편번호를 갖고있다고 해보자.
필드가 너무 복잡하지 않은가? (startDate + endDate) 를 한덩어리로 만들고, (city + street + zipcode)를 한 덩어리로 만들면 좀더 편하지 않을까?
2. 회원 엔티티가 이름, 근무 기간(workPeriod), 집 주소(homeAddress) 를 갖도록 변경
Period 와 Address 라는 타입을 만들어낸 것 이다. 따라서 다음과 같이 된다.
id, name, workPeriod, homeAddress 총 4가지 필드를 갖게 된다.
우선 코드를 보기전에 인베디드 타입을 사용하는 방법을 살펴보자.
● 입베디드 타입 사용법
- @Embeddable : 값 타입을 정의하는 곳에 표시
- @Embedded : 값 타입을 사용하는 곳에 표시
- 기본 생성자 필수
● 코드 예시
우선 임베디드 타입을 사용하지 않았을때의 코드부터 살펴보자.
- Before
Member
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
// Date
private LocalDateTime startDate;
private LocalDateTime endDate;
//Address
private String city;
private String street;
private String zipcode;
}
Date 와 Address 필드 부분에 값이 많은것을 확인할수가 있다.
이것을 내장타입으로 변경해 보면 다음과 같다.
- After
Period, Address (값 타입 정의하기)
@Embeddable // 값 타입이 정의되는 곳에 @Embeddable 사용
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
}
@Embeddable // 값 타입이 정의되는 곳에 @Embeddable 사용
public class Address {
private String city;
private String street;
private String zipcode;
}
Member
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@Embedded // 값 타입이 사용되는 곳에 @Embedded 사용
private Period workPeriod;
@Embedded // 값 타입이 사용되는 곳에 @Embedded 사용
private Address homeAddress;
}
결과는 다음과 같다.
테이블이 그대로 유지되고 있다.
● 임베디드 타입의 장점
- 재사용이 가능하다
- 높은 응집도를 갖는다.
- Period.isWork() 처럼 해당 값 타입만 사용하는 의미 있는 메소드를 만들수가 있다.
@Embeddable // 값 타입이 정의되는 곳에 @Embeddable 사용
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
private boolean isWork(){
...
}
}
- 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티 티에 생명주기를 의존한다.
=> 임베디드 타입도 값 타입이다. 따라서 엔티티가 죽으면 함께 사라지는 값이다.
● 임베디드 타입과 테이블 매핑
DB 입장에서는 바뀔점이 없다. 임베디드 타입을 사용하든 안하든 회원 테이블은 똑같다.
테이블은 DB가 데이터를 잘 관리하는것이 목적이기 때문에 위와 같이 구현되는것이 좋다.
하지만 객체는 데이터 뿐만 아니라 메서드(행위) 까지 갖고있기 때문에 묶어서 사용하면 이득을 얻을수가 있다.
- 임베디드 타입은 엔티티의 값일 뿐이다.
- 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.
- 객체와 테이블을 아주 세밀하게(find-grained) 매핑하는 것이 가능하다.
- 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많음.
● 임베디드 타입과 연관관계
- Address 라는 임베디드 타입은 Zipcode라는 임베디드 타입을 갖고있다.
@Embeddable
public class Address {
String street;
String city;
@Embedded
Zipcode zipcode; // 임베디드 타입 포함
}
- PhoneNumber 라는 임베디드 타입은 PhoneEntity라는 엔티티를 갖고 있다.
@Embeddable
public class PhoneNumber {
String areaCode;
String localNumber;
@ManyToOne
PhoneEntity provider; // 엔티티 참조
}
● @AttributeOverride: 속성 재정의
Member안에 동일한 임베디드 타입이 있다면 어떻게 될까?
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@Embedded
private Period workPeriod;
@Embedded
private Address homeAddress;
// workAddress라는 homeAddress와 동일한 타입이 추가된다면?
@Embedded
private Address workAddress;
}
위 코드를 보면 workAddress 와 homeAddress가 같은 Address 타입으로 중복이 된다.
실행하면 다음과 같은 결과가 나온다.
중복된 column이 있다는 경고가 나온다.
이럴때 @AttributeOverride를 사용하여 컬럼명 속성을 재정의 해준다.
@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_ZIPCODE"))
}
)
private Address workAddress;
실행결과는 다음과 같다.
파란 박스 안처럼 column이 추가된것을 확인할수가 있다.
● 임베디드 타입의 값이 null이면 매핑한 컬럼의 값은 모두 null이 된다.
3. 값 타입과 불변 객체
값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념이다.
따라서 값 타입은 단순하고 안전하게 다 룰 수 있어야 한다.
● 값 타입 공유 참조
- 임베디드 타입 같은 값타입을 여러 엔티티에서 공유하면 위험하다.
회원1 과 회원2가 같은 주소 값타입을 참족하고 있다. 여기서 주소 값타입을 한쪽에서 변경하면 회원1, 2 양쪽에서 모두 바뀌어 버린다.
코드로 살펴보자.
Address address = new Address("city", "street", "zipcode");
Member member1 = new Member();
member1.setUsername("member1");
member1.setHomeAddress(address);
entityManager.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(address);
entityManager.persist(member2);
위와같이 코드를 작성한후 DB를 살펴보면 다음과 같다.
member1, 2 모두 CITY 컬럼이 city값으로 되어있다.
이제 member1에서 CITY를 변경해 보자.
member1.getHomeAddress().setCity("newCity");
실행하면 결과는 다음과 같다.
이런 의도하지 않은 결과가 발생할수가 있다.
만약 이렇게 값이 공유되어 한쪽에서 변경하면 전부 변경되도록 의도한것 이라면, 값타입이 아니라 Entity를 사용해야 한다.
● 값 타입의 복사
위와같이 값 타입의 실제 인스턴스인 값을 공유하는 것은 위험하다. 대신 값(인스턴스)를 복사해서 사용해야 한다.
따라서 코드는 다음과 같이 변해야 한다.
Address address = new Address("city", "street", "zipcode");
Member member1 = new Member();
member1.setUsername("member1");
member1.setHomeAddress(address);
entityManager.persist(member1);
Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(copyAddress); // 복사한 copyAddress가 넘어감
entityManager.persist(member2);
member1.getHomeAddress().setCity("newCity");
● 객체 타입의 한계
- 항상 값을 복사해서 사용하면 공유참조로 인해 발생하는 부작용을 피할 수 있다.
문제는 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본타입이 아닌 객체 타입이다.
자바 기본 타입에 값을 대입하면 값을 복사해서 넘기기 때문에 문제가 되지 않는다.
하지만 객체 타입이 참조 값을 직접 대입하는 것을 막을 방법이 없다. => 객체의 공유 참조는 피할 수 없다.
기본 타입(primitive type)은 '='으로 값을 복사한다.
하지만, 객체 타입에서 '='을 통한 대입은 참조를 전달한다. => 인스턴스가 하나 이기에 같이 변경된다.
● 불변 객체
- 객체 타입을 수정할 수 없도록 부작용을 원천 차단한다.
- 값 타입은 불변 객체(immutable object)로 설계해야 한다. => 불변 객체 : 생성 시점 이후 절대 값을 변경할 수 없는 객체
생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 된다.
- 참고: Integer, String은 자바가 제공하는 대표적인 불변 객체
그럼 값을 변경해야하는 경우에는 어떻게 해야할까? setter가 사라졌으니 말이다!
=> 새로 만들어주면 된다.
Address newAddress = new Address(newCity, address.getStreet(), address.getZipCode())
member.setHomeAddress(newAddress); // 완전 새롭게 다시 설정해준다.
'BackEnd > JPA' 카테고리의 다른 글
[JPA] 객체지향 쿼리 언어 1 (기본 문법) (0) | 2022.04.08 |
---|---|
[JPA] 값 타입 - 2 (0) | 2022.04.07 |
[JPA] 영속성 전이와 고아 객체 (0) | 2022.04.06 |
[JPA] 프록시와 연관관계 관리 (0) | 2022.04.06 |
[JPA] 고급 매핑 (0) | 2022.04.05 |
댓글