값 타입
9. 값 타입
JPA의 데이터 타입
- 엔티티 타입
- 값 타입
- 기본 값 타입
- 자바 기본 타입(int, double 등)
- 래퍼 클래스(Integer 등)
- String
- 임베디드 타입(복합 값 타입)
- 사용자 정의 타입
- 컬렉션 값 타입
- 하나 이상의 값 타입 저장
- 기본 값 타입
9.1 기본 값 타입
- Member는 엔티티 타입이다.
- Long, String, int는 값 타입이다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
private int age;
}
9.2 임베디드 타입(복합 값 타입)
- 사용자 정의 값 타입이다.
- @Embeddable: 값 타입을 정의하는 곳에 표시한다.
- @Embedded: 값 타입을 사용하는 곳에 표시한다.
- 임베디드 타입은 기본 생성자가 필수이다.
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String name;
@Embedded private Period period; // 근무 기간
@Embedded private Address address; // 집 주소
}
@Embeddable
@Getter
public class Period {
private LocalDateTime startDateTime;
private LocalDateTime endDateTime;
public Period() {
}
public Period(LocalDateTime startDateTime, LocalDateTime endDateTime) {
this.startDateTime = startDateTime;
this.endDateTime = endDateTime;
}
public boolean isWork(LocalDateTime dateTime) {
return (dateTime.isAfter(startDateTime) && dateTime.isBefore(endDateTime))
|| dateTime.isEqual(startDateTime)
|| dateTime.isEqual(endDateTime);
}
}
@Embeddable
@Getter
public class Address {
@Column(name = "city")
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;
}
}
9.2.1 임베디드 타입과 테이블 매핑
- 임베디드 타입은 엔티티의 테이블에 매핑된다. 임베디드 타입 사용 전과 사용 후의 테이블의 매핑은 같다.
9.2.1 임베디드 타입과 연관관계
- 임베디드 타입은 값 타입을 포함하거나 엔티티를 참조할 수 있다.
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String name;
@Embedded private PhoneNumber phoneNumber;
@Embedded private Address address;
}
@Embeddable
@Getter
public class Address {
@Column(name = "city")
private String city;
private String street;
private String state;
@Embedded
private Zipcode zipcode;
public Address() {
}
public Address(String city, String street, String state, Zipcode zipcode) {
this.city = city;
this.street = street;
this.state = state;
this.zipcode = zipcode;
}
}
@Embeddable
@Getter
public class Zipcode {
private String zip;
private String plusFor;
public Zipcode() {
}
public Zipcode(String zip, String plusFor) {
this.zip = zip;
this.plusFor = plusFor;
}
}
@Embeddable
@Getter @Setter
public class PhoneNumber {
private String areaCode;
private String localNumber;
@ManyToOne
@JoinColumn(name = "provider_id")
private PhoneServiceProvider provider;
public PhoneNumber() {
}
public PhoneNumber(String areaCode, String localNumber, PhoneServiceProvider provider) {
this.areaCode = areaCode;
this.localNumber = localNumber;
this.provider = provider;
}
}
@Entity
@Getter @Setter
public class PhoneServiceProvider {
@Id @GeneratedValue
private Long id;
private String name;
}
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member();
member.setName("member");
Zipcode zipcode = new Zipcode("zipcode", "1234");
Address address = new Address("city", "street", "state", zipcode);
PhoneServiceProvider phoneServiceProvider = new PhoneServiceProvider();
phoneServiceProvider.setName("LGU+");
PhoneNumber phoneNumber = new PhoneNumber("010", "010", phoneServiceProvider);
member.setPhoneNumber(phoneNumber);
member.setAddress(address);
em.persist(phoneServiceProvider);
em.persist(member);
em.flush();
em.clear();
tx.commit();
}
catch (Exception e) {
tx.rollback();
e.printStackTrace();
}
finally {
em.close();
}
emf.close();
}
}
9.2.3 @AttributeOverride: 속성 재정의
- 임베디드 타입에 정의한 매핑정보를 재정의할 때 @AttributeOverride를 사용한다.
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String name;
@Embedded
private Address homeAddress;
@Embedded
@AttributeOverrides({@AttributeOverride(name = "city", column = @Column(name = "company_city")),
@AttributeOverride(name = "street", column = @Column(name = "company_street")),
@AttributeOverride(name = "zipcode", column = @Column(name = "company_zipcode")),
})
private Address companyAddress;
}
@Embeddable
@Getter
public class Address {
@Column(name = "city")
private String city;
private String street;
private String zipcode;
}
9.2.4 임베디드 타입과 null
- 임베디드 타입이 null이면 매핑한 컬럼 값은 모두 null이 된다.
9.3 값 타입과 불변 객체
9.3.1 값 타입 공유 참조
- 임베디드 타입 같은 값 타입은 여러 엔티티에서 공유하면 안된다.
- member1과 member2는 같은 Address를 참조하기 때문에 데이터베이스에서 둘 다 newCity로 변경된다.
member1.setHomeAddress(new Address("Oldcity"));
Address address = member1.getHomeAddress();
address.setCity("NewCity");
member2.setHomeAddress(address);
9.3.2 값 타입 복사
- 값 타입의 인스턴스는 복사해서 사용해야 한다.
- 하지만 복사하지 않고 원본의 참조 값을 직접 넘기는 것을 막을 방법이 없다.
- 따라서 근본적으로 객체의 공유 참조를 막으려면 객체의 값을 수정하지 못하게 하는 것이다.
member1.setHomeAddress(new Address("Oldcity"));
Address address = member1.getHomeAddress();
Address newAddress = address.clone();
newAddress.setCity("NewCity");
member2.setHomeAddress(newAddress);
9.3.3 불변 객체
- 객체를 불변하게 만들면 값을 수정할 수 없으므로 값 타입 객체의 공유 참조 부작용을 막을 수 있다.
- 불변 객체는 생성자로만 값을 설정하고 수정자를 만들지 않으면 된다.
- 값 타입 객체의 값을 수정하려면 새로운 객체를 생성해서 사용해야 한다.
- Integer, String은 자바가 제공하는 대표적인 불변 객체이다.
@Embeddable
@Getter
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;
}
}
9.4 값 타입의 비교
- 동일성 비교: 인스턴스의 참조 값을 비교, == 사용
- 동등성 비교: 인스턴스의 값을 비교, equals() 사용
- int a와 b는 == 비교시 참 이지만, Address a와 b는 == 비교시 서로다른 인스턴스 이므로 거짓이다.
- 객체 기반의 값 타입을 비교할 때는 equals() 를 사용해서 동등성 비교를 해야한다.
- 따라서 equals()를 재정의해야 한다.
int a = 10;
int b = 10;
Address a = new Address("서울시", "종로구", "1번지");
Address b = new Address("서울시", "종로구", "1번지");
9.5 값 타입 컬렉션
- 값 타입을 하나이상 저장하려면 컬렉션에 보관한다. 그리고 @ElementCollection, @CollectionTable 어노테이션을 사용한다.
- 컬렉션은 데이터베이스에서 별도의 테이블을 추가하여 매핑된다.
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded
private Address homeAddress;
@ElementCollection
@CollectionTable(name = "favorite_foods", 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<>();
}
@Embeddable
@Getter
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;
}
}
9.5.1 값 타입 컬렉션 사용
- 총 6번의 INSERT SQL을 실행한다.
- 값 타입 컬렉션은 영속성 전이(Cascade) + 고아 객체 제거(Orphan Remove) 기능을 필수로 가진다고 볼 수 있다.
- 값 타입 컬렉션의 페치 전략은 LAZY가 기본이다.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member();
member.setHomeAddress(new Address("통영", "뭉돌해수용장", "660-123"));
member.getFavoriteFoods().add("짬뽕"); // FAVORITE_FOODS INSERT SQL
member.getFavoriteFoods().add("짜장"); // FAVORITE_FOODS INSERT SQL
member.getFavoriteFoods().add("탕수육"); // FAVORITE_FOODS INSERT SQL
member.getAddressHistory().add(new Address("서울", "강남", "123-123")); // ADDRESS INSERT SQL
member.getAddressHistory().add(new Address("서울", "강북", "000-000")); // ADDRESS INSERT SQL
em.persist(member); // MEMBER INSERT SQL
em.flush();
em.clear();
tx.commit();
}
catch (Exception e) {
tx.rollback();
e.printStackTrace();
}
finally {
em.close();
}
emf.close();
}
}
insert into Member (city, street, zipcode, name, id) values (?, ?, ?, ?, ?)
insert into address (member_id, city, street, zipcode) values (?, ?, ?, ?)
insert into address (member_id, city, street, zipcode) values (?, ?, ?, ?)
insert into favorite_foods (member_id, food_name) values (?, ?)
insert into favorite_foods (member_id, food_name) values (?, ?)
insert into favorite_foods (member_id, food_name) values (?, ?)
- 지연 로딩시 총 3번의 SELECT SQL을 실행한다.
Member foundMember = em.find(Member.class, member.getId()); // MEMBER SELECT SQL
Address homeAddress = foundMember.getHomeAddress();
Set<String> favoriteFoods = foundMember.getFavoriteFoods();
for (String favoriteFood : favoriteFoods) {
System.out.println("favoriteFood = " + favoriteFood); // FAVORITE_FOODS SELECT SQL
}
List<Address> addressHistory = foundMember.getAddressHistory();
for (Address address : addressHistory) {
System.out.println("address = " + address); // ADDRESS SELECT SQL
}
select m.id, m.city, m.street, m.zipcode, m.name
from Member m
where m.id=?
select f.member_id, f.food_name
from favorite_foods f
where f.member_id=?
select a.member_id, a.city, a.street, a.zipcode
from address a
where a.member_id=?
Member foundMember = em.find(Member.class, member.getId());
// 임베디드 값 타입 수정
foundMember.setHomeAddress(new Address("city", "street", "zipcode"));
// 기본 값 타입 컬렉션 수정
foundMember.getFavoriteFoods().remove("탕수육");
foundMember.getFavoriteFoods().add("치킨");
// 임베디드 값 타입 컬렉션 수정
foundMember.getAddressHistory().remove(new Address("서울", "강북", "000-000"));
foundMember.getAddressHistory().add(new Address("서울", "서초", "000-000"));
-- 임베디드 값 타입 수정
update Member set city=?, street=?, zipcode=?, name=?
where id=?
-- 기본 값 타입 컬렉션 수정 : 수정 할 값 삭제 후 새로운 값을 삽입한다.
delete from favorite_foods
where member_id=? and food_name=?
insert into favorite_foods (member_id, food_name) values (?, ?)
-- 임베디드 값 타입 컬렉션 수정 : 모든 데이터 삭제 후 수정하여 다시 삽입한다.
delete from address
where member_id=?
insert into address (member_id, city, street, zipcode) values (?, ?, ?, ?)
insert into address (member_id, city, street, zipcode) values (?, ?, ?, ?)
9.5.2 값 타입 컬렉션의 제약사항
- 값 타입은 식별자라는 개념이 없고 단순한 값들의 모음이므로 값 변경시 데이터베이스에서 원본 데이터를 찾기 어렵다.
- 값 타입 컬렉션에 변경 사항이 발생하면, 값 타입 컬렉션이 매핑된 테이블의 연관된 모든 데이터를 삭제하고, 현재 값 타입 컬렉션 객체에 있는 모든 값을 데이터베이스에 다시 저장한다.
- 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 한다. 기본 키 제약 조건으로 컬럼에 null을 입력할 수 없고, 같은 값을 중복해서 저장할 수 없다.
Leave a comment