엔티티 타입
@Entity
로 정의하는 객체값 타입
int
, Integer
, String
처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체기본값 타입
임베디드 타입(embedded type, 복합 값 타입)
컬렉션 값 타입(collection value type)
String name
, int age
자바의 기본 타입은 절대 공유되지 않는다.
Integer
같은 래퍼 클래스나 String
같은 특수한 클래스는 공유가능한 객체지만, 변경할 수 있는 세터가 없어서 변경 안됨.
int a = 10;
int b = a;
a = 20;
-------------------
a = 20
b = 10
-------------------
Integer a = new Integer(10);
Integer b = a;
b.setValue(20);
-------------------
a = 20
b = 20
회원 엔티티는 이름, 근무 시작일, 근무 종료일, 주소 도시, 주소 번지, 주소 우편번호를 가진다.
이 공통된 부분을 묶어내는게 임베디드 타입
JPA에서는 어떻게 쓰냐
@Embeddable
: 값 타입을 정의하는 곳에 표시@Embedded
: 값 타입을 사용하는 곳에 표시장점
Period.isWork()
처럼 값 타입만 사용하는 의미 있는 메서드를 만들 수 있음DB와 객체에서는 위와 같은 차이가 있다.
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
//기간 Period
@Embedded
private Period workPeriod;
//주소
@Embedded
private Address homeAddress;
}
@Embeddable
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
public boolean isWork() {
//... 특정 메서드 넣을 수 있음
}
//... Getter Setter
}
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
//... Get Set
}
-------------------------------
Hibernate:
create table Member (
MEMBER_ID bigint not null,
city varchar(255),
street varchar(255),
zipcode varchar(255),
USERNAME varchar(255),
endDate timestamp,
startDate timestamp,
TEAM_ID bigint,
primary key (MEMBER_ID)
)
임베디드 타입은 엔티티의 값일 뿐이다.
매핑하는 테이블은 같은데 객체지향적으로 사용할 수 있다는 점이 장점.
쉽게 말해 데이터베이스의 내용은 그대로 유지하는데 임베디드를 이용하여 공통적인 부분을 따로 묶어서 사용할 수 있다는 것.
근데 또 현업에서 그렇게 많이 사용하지는 않는다 함.
@AttributeOverride: 속성 재정의
@Embedded
private Address homeAddress;
@Embedded
private Address workAddress;
오류남.
@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_ZIPCODE")),
})
private Address workAddress;
--------------------------
Hibernate:
create table Member (
MEMBER_ID bigint not null,
city varchar(255),
street varchar(255),
zipcode varchar(255),
WORK_CITY varchar(255),
WORK_STREET varchar(255),
WORK_ZIPCODE varchar(255),
USERNAME varchar(255),
endDate timestamp,
startDate timestamp,
TEAM_ID bigint,
primary key (MEMBER_ID)
)
값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념이다. 따라서 값 타입은 단순하고 안전하게 다 룰 수 있어야 한다.
쉽게 말해서 어...
Member member1 = new Member();
Member member2 = new Member();
City city = new City("A");
member1.setCity(city);
member2.setCity(city);
// city를 공유하게 됨
member1.getCity().changeCity("B");
//안에 있는 city 값을 변경하게 되면,
//member2에도 city 값이 변경됨
이와 같이 정보가 변경될 수 있다.
그러므로 아래와 같이 복사해서 사용해야 한다.
Member member1 = new Member();
Member member2 = new Member();
City city = new City("A");
member1.setCity(city);
City copyCity = new City(city.getCity());
member2.setCity(copyCity);
//안의 내용만 가져와 넣어줌.
정리
불변 객체
Member member1 = new Member();
Member member2 = new Member();
City city = new City("A");
member1.setCity(city);
member2.setCity(city);
// city를 공유하게 됨
member1.getCity().changeCity("B");
// 이 메서드 자체를 없앤다는 뜻. 그러면
// 바꿀일이 없으니 복사해도 넣어도 상관없음
이제 만약 바꾸고 싶다면 그냥 새로운 생성자로 (new
) 만들면 됨.
int a = 10;
int b = 10;
a == b -> true
Address address = new Address("서울시");
Address address = new Address("서울시");
address1 == address2 -> false
address1.equals(address2) -> false
equals
로 했을때, true
가 나와야할거같은데 ==
비교가 기본이므로 false
로 나옴.
오버라이딩으로 값 비교로 바꾸면 true
로 나옴
여기서 말하고 싶은건,
동일성 비교 : 값을 비교 == 사용
동등성 비교 : 인스턴스 값을 비교 equals
사용
의 차이인거 같다.
@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD", joinColumns =
@JoinColumn(name = "MEMBER_ID")
)
@Column(name = "FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<>();
@ElementCollection
@CollectionTable(name = "ADDRESS", joinColumns =
@JoinColumn(name = "MEMBER_ID")
)
private List<Address> addressHistory = new ArrayList<>();
-----------------------------------
Hibernate:
create table FAVORITE_FOOD (
MEMBER_ID bigint not null,
FOOD_NAME varchar(255)
)
create table ADDRESS (
MEMBER_ID bigint not null,
city varchar(255),
street varchar(255),
zipcode varchar(255)
)
값 타입 컬랙션
@ElementCollection
, @CollectionTable
사용
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity", "stree", "abc"));
// 값 타입 저장
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
member.getAddressHistory().add(new Address("old1", "street", "abc1");
member.getAddressHistory().add(new Address("old2", "street", "abc2");
em.persist(member);
em.flush();
em.clear();
// 멤버 조회
Member findMember = em.find(Member.class, member.getId());
// 컬렉션은 지연로딩이므로, 불러오지 않음. 임베디드는 불러옴.
// 이렇게 직접 값을 찾을때, DB로부터 값을 찾게됨.
List<Address> addressHistory = findMember.getAddressHistory();
for (Address address : addressHistory) {
System.out.println("address = " + address.getCity);
}
// 수정
Address a = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity", a.getStreet(), a.getZipcode()));
// 임베디드의 경우
// 이렇게 값을 가져온 뒤 일일이 수정하고 새로운 생성자로 만들어야됨.
// 컬렉션 수정
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");
findMember.getAddressHistory().remove(new Address("old1", "street", "abc1");
findMember.getAddressHistory().add(new Address("newOld1", "street", "abc12");
// 이와 같이 변경을 해야하는데 이때 쿼리가 어떤 방식으로 나가냐 하면,
-----------------------
Hibernate:
/* delete collection ... */ delete
from
ADDRESS
where
MEMBER_ID=?
Hibernate:
/* insert collection ... */ insert
into
ADDRESS
(MEMBER_ID, city, street, zipcode)
values
(?, ?, ?, ?)
----------------------------
// 이렇게 컬렉션 내용을 모두 지우고, 다시 새로 저장하게 된다.
이렇게 복잡하게 쓸거면 다른걸 선택해라.
대안
@Entity
public class AddressEntity {
@Id @GenerateValue
private Long id;
private Address address;
// Getter Setter ...
}
@Entity
public class Member {
...
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
public List<AddressEntity> addressHistory = new ArrayList<>();
}
이렇게 그냥 일대다 만들고, Cascade
+ 고아 객제 제거를 이용해 값 타입 컬렉션처럼 사용하는 것이 훨씬 좋다.