---
tags: github, jpa,embedded
---
study2. 값객체와 @Embdedd 를 이용한 city, gu, bunji 리팩토링
===
## 목표
9장 에서 `값객체` 와 `@Embdedd` 에 대한 내용이 나오는데, 간단 하지만 매우 강력하다.
편리한 학습을 위해 먼저 학습 해 보자.
#### 사전 준비사항
1. 실습 할 환경
> [study1. 학습용 Spring Boot + JPA 환경 만들기](/BRj-hewDTb-vs1Faqexp6Q) 참고
> 전체 코드와 TEST는 [github](https://github.com/bonjugi/jpa-study-embedded) 에서 볼수 있다.
## city, gu, bunji 문제점
위의 사전 준비사항을 마쳤다면 현재 Member 엔티티는 다음과 같을 것이다.
```java=
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
private String city;
private String gu;
private String bunji;
public Member(String name) {
this.name = name;
}
}
```
생성되는 DDL 은 다음과 같다.
```
create table member (
id bigint not null auto_increment,
name varchar(255),
city varchar(255),
gu varchar(255),
bunji varchar(255),
primary key (id)
) engine=InnoDB
```
여기에서 멤버의 주소지가 서울 인지 체크하는 서비스 로직을 추가 한다고 가정 해 보자.
다음과 같은 서비스 레이어가 생길 것 이다.
(가독성을 위해 코드들은 약간씩 간소화 하겠다)
```java=
public class MemberService {
private final MemberRepository repository;
public boolean inSeoul(Long id){
Member member = repository.findById(id);
return member.inSeoul();
}
}
```
도메인 로직인 `멤버가 서울인지?` 를 inSeoul() 메소드로 체크 하려면 다음과 같을것 이다.
```java=
public class Member {
...
public boolean inSeoul(){
if (bunji.isEmpty() || city.isEmpty() || gu.zipCode()) {
throw new IllegalStateException("주소 정보가 부족합니다.");
}
return city.equals("Seoul");
}
}
```
Member 엔티티가 주소와 관련된 값들을 비교하는 로직에 불편함을 느껴 지는가?
> 혹시 위와 같은 로직을 서비스에 만들고자 하진 않았는지?
> 만약 그러하다면 여러곳에서 중복코드가 발생하고있진 않은지?
주소 에 대한 역할은 Address 라는 객체 한테 떠넘기면 더욱 객체지향적일것 같다.
예를들면 이렇게 말이다.
```java=
public class Member {
...
public boolean inSeoul(){
return address.inSeoul();
}
}
```
> 물론 현재는 address 라는 멤버변수가 없어 컴파일 에러가 날 것이다.
> 아래의 리팩토링을 통해 address 값객체를 만들어 보자.
## 값 객체로 리팩토링
3개의 값 들을 Address 라는 객체 로 표현하면 좋을것 같다.
기존의 city, gu, bunji 를 Address 객체로 바꿔보자.
```java=
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
private Address address; /* new! */
public Member(String name) {
this.name = name;
}
}
```
```java=
public class Address {
private String city;
private String gu;
private String bunji;
public Address(String city, String gu, String bunji) {
if (bunji.isEmpty() || city.isEmpty() || gu.isEmpty()){
throw new IllegalStateException("주소 정보가 부족합니다.");
}
this.city = city;
this.gu = gu;
this.bunji = bunji;
}
public boolean inSeoul(){
return city.equals("Seoul");
}
}
```
도대체 어떻게 좋다는 건지 감이 오지 않는가?
객체지향 관점에서 아래와 같은 장점이 생긴다.
#### 1. 표현이 좋아진다.
각각으로써는 의미가 없던 city, gu, bunji 가 모여 주소를 표현하게 된다.
세 값들에 연관성을 설명하지 않더라도 각각이 Address 의 구성요소임을 알수 있다.
#### 2. 응집력이 높아진다.
Address 안에는 이미 주소관련된 필드들을 모두 가지고 있고 무엇이든 연산 할 수 있다.
Member 와 Address 가 각각 적절한 책임을 나눠 갖게 되었다.
지번주소나 빌딩번호를 활용한 도메인 로직이 추가되어도 Address로 해결이 가능하다.
#### 3. POJO 로써, 재사용 가능하다.
Member 엔티티 뿐 아니라, Company 같은 다른 엔티티에서도 재활용이 가능하다.
```java=
public class Company {
...
private Address address;
}
```
Company 도 기존에 만들어진 inSeoul() 메소드를 쓸수있는것은 물론, 추가적인 주소관련 메소드도 추가하여 활용할 수 있다.
값객체의 장점은 여기까지만 설명 하겠다.
> 불변객체로 설계할수있어 엔티티와 큰 차이가 있는데 좀 길어질것 같다.
여기서 이런 의문이 들수 있다.
Address 객체가 DB 상의 3개의 필드를 대체 한다는것인가?
그렇다면 필드 타입은 무엇인가?
대체당한 3개의 필드들은 사라지는가?
예를들면 DDL 이 이렇게 되진 않을까?
```
create table member (
id bigint not null auto_increment,
/* 세개의 필드는 사라지나?
city varchar(255),
gu varchar(255),
bunji varchar(255),
*/
address ??? (255), /* 새로 생기는 address 필드의 타입은 뭘까? */
name varchar(255),
primary key (id)
) engine=InnoDB
```
이것은 `@Embedded` (or `@Embeddable`) 로 관계매핑을 가능하게 해준다.
자세히 알아보자.
## @Embedded (or @Embeddable)
엔티티에 `값객체`를 매핑하기 위해서는 @Embeddable 어노테이션만 추가 하면 된다.
```java=
...
@Embeddable
public class Address {
private String city;
private String gu;
private String bunji;
...
}
```
Address를 참조하는 모든 엔티티는, Address 의 필드들을 @Column 처럼 사용 할 수 있게 된다.
다시 프로젝트를 런 해서 생성되는 테이블을 확인 해 보자.
```
create table member (
id bigint not null auto_increment,
city varchar(255),
gu varchar(255),
bunji varchar(255),
name varchar(255),
primary key (id)
) engine=InnoDB
```
> 여전히 city, gu, bunji 필드들이 살아있다.
> DB 매핑은 보존하면서 세 필드들을 객체화 시켰다.
### @Embedded 는 아직 설명하지 않았는데?
큰 차이는 없다.
단지 @Embeddable 과 달리, @Embedded 은 엔티티에서 작성 할수 있게 해준다.
```java=
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
@Embedded
private Address address;
public Member(String name) {
this.name = name;
}
}
```
@Embeddable 또는 @Embedded 은, 둘중 하나만 쓰고 하나는 생략해도 괜찮다.
어디를 생략할지는 장단점이 있어 팀마다 다르게 운용될수 있다.