엔티티 타입
- @Entity로 정의하는 객체
- 데이터가 변해도 식별자로 지속해서 추적 가능
값 타입
- int, integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
- 식별자가 없고, 값만 있으므로 변경시 추적 불가
값타입의 분류
- 기본값 타입2.값을 공유하면 안됨
- int, double 같은 기본 타입은 절대 공유 x
- Integer같은 래퍼 클래스나 String같은 특수 클래스는 공유 가능한 객체이지만 변경이 안됨
- 4.래퍼 클래스(Integer, Long)
- 1.(생명주기를 엔티티의 의존 → 회원을 삭제하면 이름, 나이 필드 함께 삭제)
- 임베디드 타입(예를 들면 x,y값을 같이 묶어서 사용하고 싶을때)
- 새로운 값 타입을 직접 정의할 수 있음
- JPA는 임베디드 타입이라 함
- 주로 기본 값 타입을 모아 만들어서 복합 값 타입이라고도 함
- int, String과 같은 값 타입
- 임베디드 타입은 엔티티의 값일 뿐이다.
- 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.
- 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능
- 잘 설계한 ORM애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많음
- 기본 생성자는 필수
- @Embeddable
- 값 타입을 정의 하는곳에 표시
- @Embedded
- 값 타입을 사용하는 곳에 표시한다.
값 타입과 불변 객체public class Mebmer{ //기타 객체 @Embedded private Address homeAddress; @Embedded @AttributeOverrides({ @AttributeOverride(name="city", column=@Column(name ="WORK_CITY")), @AttributeOverride(name="street", column=@Column(name ="WORK_ST")), @AttributeOverride(name="zipcode", column=@Column(name = "WORK_ZP")) }) private Address workAddress } @Embeddable public class Address{ private String city; private String street; private String zipcode; }
- 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험함
- 부작용(side effect)발생
- 객체의 공유 참조는 피할 수 없음. 따라서 근복적인 해결책이 필요한데 가장 단순한 방법은 객체의 값을 수정하지 못하게 막으면 된다. 예를들면 Setter같은 수정자 메소드를 모두 제거한다. 그러면 공유 참조를 해도 값을 변경하지 못하므로 부작용을 막을 수 있다.
<aside> 💡 만약 member의 속성 값만 바꾸고 싶다면? 또는 현재 값을 복사하고 싶다면? 아래와 같이 새로운 객체를 만들어서 저장Address address = new Address("CITY","street","10000"); Member member = new Member(); member.setUsername("member1") member.setHomeAddress(address); em.persist(member1); Member member2 = new Member(); member2.setUsername("member2") member2.setHomeAddress(address); em.persist(member2); //첫 번째 member의 Address(city) 속성만 변경하고 싶다. member.getHomeAddress().setCity("new city"); tx.commit(); //member1과 member2 두 곳에서 update가 발생
즉 위와 같은 문제를 막기 위해서는 불변 객체를 사용해야 한다.Address address = new Address("city", "street", "10000"); // Member member = new Member(); member.setUsername("member1"); member.setHomeAddress(address); em.persist(member); // Address 객체 복사 Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode()); // Member member2 = new Member(); member2.setUsername("member2"); member2.setHomeAddress(copyAddress); // 복사한 것을 넣는다. em.persist(member2); // 첫 번째 member의 Address(city) 속성만 변경된다.! member.getHomeAddress().setCity("new city");
- 값 타입은 불변 객체로 설계해야 한다.
- 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단할 수 있다.
- 불변이라는 작은 제약으로 부작용이라는 큰 재앙을 막을 수 있음
- 불변 객체란
- 생성 시점 이후 절대 값을 변경할 수 없는 객체
- 생성자로만 값을 설정하고 Setter를 만들지 않으면 된다.
- 값을 변경하고 싶으면? 아래와 같이 생성자를 통해 새로운 객체를 만들고 통으로 변경함
//Address를 불변 객체로 만들어보기 @Embeddable public class Address{ private String city; protected Address(){} // JPA에서 기본 생성자는 필수다. //생성자로 초기 값을 설정한다. public Address(String city){ this.city = city } public String getCity(){ return city; } //수정자는 만들지 않는다. }
Address address = new Address("city", "street", "10000"); Member member = new Member(); member.setUsername("member1"); member.setHomeAddress(address); em.persist(member); Address newAddress = new Address("NewCity", address.getStreet(), address.getZipcode()); member.setHomeAddress(newAddress); // 통으로 변경 tx.commit(); <https://gmlwjd9405.github.io/2019/09/12/value-type-of-basic-and-embedded.html값> 타입의 비교
- 불변 객체
- </aside>
- 값 타입 공유 참조
- 코드가 실행하면 workAddress의 임베디드 부분은 WORK_~~~필드로 만들어지게 됨
- public class Mebmer{ //기타 객체 @Embedded private Address homeAddress; @Embedded private Address workAddress } public class Address{ private String city; private String street; private String zipcode; }
- @AttributeOverride: 속성 재정의
- </aside>
- 값 타입의 비교
- 동일성 비교: 인스턴스의 참조 값을 비교,== 사용
- 동등성 비교: 인스턴스의 값을 비교, equals()사용
- 자바가 제공하는 객체의 비교는 2가지이다.
- 컬렉션 값 타입
- 값 타입을 컬렉션에 담아서 쓰는 것을 말한다.
- 값 타입 컬렉션은 값 타입을 하나 이상 저장할 때 사용
- 데이터 베이스에는 List나, Set과 같은 컬렉션의 형태가 없기 때문에, 별도의 테이블에 저장
- 컬렉션은 1:N의 개념이다.
public class Member {
@Id
private Long id;
private Set<String> favoriteFoods;
private List<Address> addressHistory;
}
구현 예시
기본 어노테이션 @ElementCollection, @CollectionTable을 사용
@Entity
public class Member {
...
@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<>();
...
}
@Embeddable
public class Address{
@Column
private String city;
private String street;
private String zipcode;
}
값 타입 컬렉션도 마찬가지로, 값을 수정하기 위해서는 새로운 인스턴스를 생성하여 통으로 갈아 끼워야한다.
값 타입 컬렉션의 제약사항
- 값 타입 컬렉션은 값 타입 엔티티와는 다르게 식별자 개념이 없다.
- 값이 변경은 되지만 추적이 어렵다.ADDRESS에는 ID가 존재하지 않는다. 그렇기 때문에 값이 중간에 변경되었을 때 DB가 해당 ROW만을 찾아서 변경이 어렵다.
- 대안
- @orderColumn(name="address_history_order")를 사용하여 update query가 동작하게 할 수 있으나, 컬럼을 하나 더 만들어 사용자가 아닌 jpa가관리하게 만드는 방법이다.그렇다 보니 의도한 대로 동작하지 않을때가 많다.
- 결론
- 값 타입 컬렉션을 사용하지 말고, 엔티티타입을 사용하는것이 좋다(엔티티를 새로 만들어 @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)관계를 고려하는것이 좋음)→ 값타입을 엔티티로 승격시키는것
- 값 타입 컬렉션은 정말 단순한 경우에 사용하자..[치킨, 피자,족발]등과 같은 메뉴 선택박스값으로 순서가 섞여도 상관없는 대상...
- 대안
- 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다
- Hibernate: create table ADDRESS ( MEMBER_ID bigint not null, city varchar(255), street varchar(255), zipcode varchar(255) )
'SPRING > JPA' 카테고리의 다른 글
[JPA] 프록시 (0) | 2022.01.21 |
---|---|
[JPA]객체지향 쿼리 심화 (0) | 2022.01.21 |
[JPA]NamedQuery (0) | 2022.01.21 |
[JPA] 엔티티 직접사용 (0) | 2022.01.21 |
[JPA] Hibernate 초기화 전략 (0) | 2022.01.21 |