기본값 타입
엔티티 타입
•
@Entity로 정의하는 객체
•
데이터가 변해도 식별자로 지속해서 추적 가능
⇒ 엔티티 내부의 모든 값들을 바꿔도 식별자만 유지되면 추적이 가능하다는 의미
•
Ex: 회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능
값 타입
•
int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
•
식별자가 없고 값만 있으므로 변경시 추적 불가
•
Ex: 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체
값 타입 분류
1.
기본값 타입
•
자바 기본 타입(int, double)
•
래퍼 클래스(Integer, Long)
•
String
2.
임베디드 타입(embedded type, 복합 값 타입)
•
Ex: 우편번호 , 좌표같은 복합 값을 Position클래스로 만들어 쓰려고하는 것을 임베디드 타입
3.
컬렉션 값 타입(collection value type)
•
Java collection(Array, Map, Set)에 값을 넣을수 있는 것을 컬렉션 값 타입이라 한다.
값 타입 : 기본값 타입
•
Ex: String name, int age
•
생명주기를 엔티티에 의존
◦
회원을 삭제하면 이름, 나이 필드도 함께 삭제
•
값 타입은 공유하면 안된다.
◦
Side Effect → 회원 이름 변경시 다른 회원의 이름도 함께 변경되면 안된다.
참고: 자바의 기본 타입은 절대 공유가 되지 않는다.
•
int, double 같은 기본 타입(primitive type)은 절대 공유되지 않는다.
•
기본 타입은 항상 값을 복사한다.
•
Integer같은 래퍼 클래스나 String 같은 특수한 클래스는 공유 가능한 객체이지만 변경할 수 없다.
임베디드 타입
개요
•
새로운 값 타입을 직접 정의할 수 있다.
•
JPA는 임베디드 타입(embedded type)이라 한다.
•
주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 한다.
•
int, String과 같은 값 타입
임베디드 타입 사용법
•
@Embeddable: 값 타입을 정의하는 곳에 표시
•
@Embedded: 값타입을 사용하는 곳에 표시
•
기본 생성자 필수
Example
: 예제를 통해 알아보는게 이해하기 더 쉽다.
1.
회원 엔티티는 이름, 근무 시작일, 근무 종료일, 주소 도시, 주소 번지, 주소 우편번호를 가진다.
•
city, street, zipcode는 주소로 합칠 수 있을 것 같다.
•
근무 시작일, 근무 종료일은 근무시간으로 합칠 수 있을 것 같다.
2. 회원 엔티티의 몇몇 값을 주소, 근무시간으로 합치면 어떻게 될까.
자료1. workPeriod와 homeAddress로 몇몇 요소들이 합쳐졌다.
자료2. workPeriod와 homeAddress는 자료와 같다.
3. 코드를 통해 좀 더 자세히 알아보자.
@Embeddable //값 타입이 정의되는 곳에 @Embeddable 사용
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
public Period() { }
}
@Embeddable //값 타입이 정의되는 곳에 @Embeddable 사용
public class Address {
private String city;
private String street;
private String zipcode;
public Address() { }
}
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
/*
//임베디드 타입을 사용하지 않으면 주석 내의 기존 형태로 값 타입으로 선언해줘야 한다.
//Period
private LocalDateTime startDate;
private LocalDateTime endDate;
//Address
private String city;
private String street;
private String zipcode;
*/
@Embedded //값 타입이 사용되는 곳에 @Embedded 사용
private Period workPeriod;
@Embedded //값 타입이 사용되는 곳에 @Embedded 사용
private Address homeAddress;
}
Java
복사
임베디드 타입의 장점
•
재사용
◦
Period나 Address는 다른 객체에서도 사용 할 수 있어 재사용성을 높힌다.
•
높은 응집도
•
Period.isWork()처럼 해당 값 타입만 사용하는 의미있는 메소드를 만들 수 있음.
private boolean isWork(){
...
}
Java
복사
•
임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티티에 생명주기를 의존한다.
임베디드 타입을 통해 객체를 분리하더라도 테이블은 하나만 매핑된다.
자료3. Period, Address, Member객체는 모두 Member테이블에 매핑된다.
임베디드 타입과 테이블 매핑
•
임베디드 타입은 엔티티의 값일 뿐이다.
•
임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다. (자료3 참고)
•
객체와 테이블을 아주 세밀하게(find-grained) 매핑하는 것이 가능하다.
•
잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많음.
임베디드 타입과 테이블 매핑
•
Member Entity는 Address라는 임베디드 타입을 가질 수 있고, Address라는 임베디드 타입 역시 zipcode라는 값 타입을 가질 수 있다.
•
Member Entity는 PhoneNumber라는 임베디드 타입을 가질 수 있고,
PhoneNumber라는 임베디드 타입은 PhoneEntity라는 Entity를 가질 수 있다.
임베디드 타입 클래스 안에서 Column도 사용 가능하다
@Embeddable
public class Address {
private String city;
private String street;
@Column(name = "ZIPCODE") // 이것 역시 가능하다.
private String zipcode;
private Member member; //가능하다.
public Address() { }
}
Java
복사
@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;
//error MappingException: Repeated column in mapping for entity
}
Java
복사
@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_STREET"))
})
private Address workAddress;
Java
복사
•
위와 같이 @AttributeOverrides를 이용하면 Address의 field명들이 재정의 되어 정상 동작한다.
값 타입과 불변 객체
:값 타입은 복잡한 객체 세상을 조금이라도 단순화 하려고 만든 개념. 따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 한다.
값 타입 공유 참조
•
임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.
city값을 NewCity로 바꿔버린다면 부작용(Side Effect) 발생
1.
시나리오1 - member 와 member2가 같은 address를 바라보고 있다.
Address address = new Address("city", "street", "10000");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);
Member member2 = new Member();
member.setUsername("member2");
member.setHomeAddress(address);
em.persist(member);
Java
복사
2. 시나리오2 - 시나리오1에서 member가 주소지를 변경하고싶어서 Address를 수정한다.
member.getHomeAddress().setCity("newCity");
Java
복사
3. SideEffect 발생 - member2의 Address정보까지 바뀌어 버린다.
값 타입 복사
값(인스턴스)을 복사해서 사용한다.
Address address = new Address("city", "street", "10000");
Address copyAddress = new Address(address.getCity(),
address.getStreet(),
address.getZipcode());
Java
복사
누군가 실수로 값 복사가 아닌 기존 값을 넣는다면 막을 수 있을까?
객체 타입의 한계
•
항상 값을 복사해서 사용하면 공유참조로 인해 발생하는 부작용을 피할 수 있다.
•
문제는 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본타입이 아닌 객체 타입이다.
•
자바 기본 타입에 값을 대입하면 값을 복사한다.
•
객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다.
•
객체의 공유 참조는 피할 수 없다.
Address address = new Address("city", "street", "10000");
Address copyAddress = new Address(address.getCity(),
address.getStreet(),
address.getZipcode());
...
member2.setHomAddress(member.getHomeAddress()); //막을 수 없다.
Java
복사
•
기본 타입(primitive type)은 '='으로 값을 복사한다.
하지만, 객체 타입에서 '='을 통한 대입은 참조를 전달한다.
→ 인스턴스가 하나이기에 같이 변경된다.
불변 객체
•
객체 타입을 수정할 수 없도록 부작용을 원천 차단한다.
•
값 타입은 불변 객체(immutable object)로 설계해야 한다.
•
불변 객체: 생성 시점 이후 절대 값을 변경할 수 없는 객체
•
생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 된다.
•
참고: Integer, String은 자바가 제공하는 대표적인 불변 객체
•
Example
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;
}
public String getCity() {
return city;
}
public String getStreet() {
return street;
}
public String getZipcode() {
return zipcode;
}
private void setCity(String city) {
this.city = city;
}
private void setStreet(String street) {
this.street = street;
}
private void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
}
Java
복사
•
값을 변경해야 하는 경우에는 어떻게 하나요?
◦
새로 만들어 준다.(ex: city가 바뀌게 된다.)
Address newAddress = new Address(newCity, address.getStreet(), address.getZipCode())
member.setHomeAddress(newAddress);
Java
복사