Embedded 타입
Embedded 타입이란?
새로운 값의 타입을 직접 정의하는 것이다.
가게에서 파는 물건을 생각해보자. 공급가 + 부가세를 합쳐서 나온 전체가격이 판매가격이 된다.
복잡한 판매품의 경우 공급가, 부가세 외에 바른 부차적인 요소들을 합쳐야 전체 판매가격이 나오는 경우도 있다.
입베디드타입을 써서 이러한 복합 값들의 타입을 직접 유저가 정의하여 필드들을 묶어서 하나로 활용할 수 있다.
왜 쓰는걸까?
만약 쇼핑몰 유저의 집주소 정보를 하나하나 변수로 선언해서 저장한다고 가정하자.
유저와 관련된 클래스는 간략하게 user / userHistory / userEvent가 있는데, 우리는 아래와 같은 변수들을 모든 클래스에 복붙해줘야 할것이다.
private String city;
private String district;
private String detail;
private String zipCode;
private String newDistrict; // 신주소
private String newDetail; // 신주소
위와같이 모든 변수들을 복붙해서 사용해도 지장은 없다. 대신 복붙으로 인해 DRY 법칙에서 어긋나게 된다.
DRY (Don't Repeat Yourself) : 똑같은 일을 두번하지 않는다. 중복되는 함수나 코드는 하나의 공통의 콤포넌트에 넣고 사용한다. 큰 시스템을 여러 조각으로 나누고 서로 참조한다
DRY 법칙을 잘 지키면 개발와 유지보수 비용의 절감에서 도움이 되므로, DRY 법치게 위배되지 않게 임베디드를 활용할 수 있다.
어떻게 쓰는걸까?
Embedded 타입을 사용하기 위한 핵심 키워드는 다음과 같다.
- @Embeddable: 값 타입을 정의하는 곳에 표시
- @Embedded: 값 타입을 사용하는 곳에 표시
- 임베디드 타입은 기본 생성자가 필수
먼저 Embedded 타입이 될 클래스를 정의한다.
[Address.java]
@Embeddable
@Data // 기본생성자 필수
@AllArgsConstructor
@NoArgsConstructor
public class Address {
private String city; // 시
private String district; // 구
@Column(name = "address_detail")
private String detail; // 상세주소
private String zipCode; // 우편번호
}
그리고 Address 클래스를 활용할 user, userHistory 엔티티에 각각 Address 클래스를 추가해준다.
[user.java, userHistory.java]
@Embedded
private Address homeAddress;
[테스트코드]
@Test
void embedTest(){
userRepository.findAll().forEach(System.out::println);
User user = new User();
user.setName("steve");
user.setHomeAddress(new Address("서울시", "강남구", "강남대로 365 미왕빌딩", "06241"));
userRepository.save(user);
userRepository.findAll().forEach(System.out::println );
}
테스트 결과는 다음과같다.
User(super=BaseEntity((앞에 내용생략...) name=steve, email=null, homeAddress=Address(city=서울시, district=강남구, detail=강남대로 365 미왕빌딩, zipCode=06241))
불필요한 내용은 생략했으며, 결과적으로 집주소가 잘 들어가있음을 확인할 수 있다.
Embedded 타입의 장점
- 재사용
- 높은 응집도
- Period 객체의 isWork() 메서드처럼 해당 값 타입만 사용하는 의미있는 메서드를 만들 수 있음
장점들은 크게 위와 같다.
재사용의 경우, 아래와 같이 코드를 재사용할 수 있다.
[user.java, userHistory.java]
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "home_city")),
@AttributeOverride(name = "district", column = @Column(name = "home_district")),
@AttributeOverride(name = "detail", column = @Column(name = "home_address_detail")),
@AttributeOverride(name = "zipCode", column = @Column(name = "home_zip_code"))
})
private Address homeAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "company_city")),
@AttributeOverride(name = "district", column = @Column(name = "company_district")),
@AttributeOverride(name = "detail", column = @Column(name = "company_address_detail")),
@AttributeOverride(name = "zipCode", column = @Column(name = "company_zip_code"))
})
private Address companyAddress;
각 column을 AttributeOverrides를 통해 재정의 해줘야한다.
[테스트코드]
@Test
void embedTest(){
userRepository.findAll().forEach(System.out::println);
User user = new User();
user.setName("steve");
user.setHomeAddress(new Address("서울시", "강남구", "강남대로 365 미왕빌딩", "06241"));
user.setCompanyAddress(new Address("서울시", "성동구", "성수이로 113 제강빌딩", "04794"));
userRepository.save(user);
userRepository.findAll().forEach(System.out::println );
}
[결과]
User(super=BaseEntity((앞에 내용 생략...) name=steve, email=null, homeAddress=Address(city=서울시, district=강남구, detail=강남대로 365 미왕빌딩, zipCode=06241), companyAddress=Address(city=서울시, district=성동구, detail=성수이로 113 제강빌딩, zipCode=04794))
집주소와 회사주소가 잘 들어가있음을 확인할 수 있다.
AttributeOverride 때문에 코드가 지저분해보인다.
사실 AttributeOverride를 통해 각 속성들을 재정의해주면 초기에 변수들을 덕지덕지 복붙했던것처럼 지저분하게 보이는 효과가있다.
개발자 본인이 보기에 차라리 객체를 새로 선언해서 사용하는 것이 나을지, 임베디드 타입을 쓰는게 나을지는 직접 결정하면 된다.