-
JPA (8) - 값 타입과 값 타입 컬렉션카테고리 없음 2024. 1. 11. 17:53
- JPA에서는 Entity타입과, 값타입 존재
- > @Entity로 정의하는 객체, 데이터가 변해도 식별자로 지속해서 추적 가능 (회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식가능)
-> 기본 값 타입 : int,Integer,String
1. 기본값 타입
> 기본 값 타입 : 자바 기본 타입, 래퍼 클래스, String, 임베디드 타입, 컬렉션 타입
1.1 기본 값 타입
> 생명주기 엔티티 의존, 값타입은 공유해선 안된다 (회원 변경시 다른 회원 이름도 함께 변경되거나 이런거 조심)
> 자바 기본 타입은 어차피 공유 발가능함 (래퍼나 String등 특수 클래스도 객체라서 공유가능할 것 같지만 안됨)
> List같은거 운용할 때 조심하자
1.2 임베디드 타입 (복합 값 타입)
> 새로운 값 타입을 직접 정의
> JPA는 임베디드 타입이라함
> 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 함
@Entity public class Member { //.. @Embedded private Period period @Embedded private Address address } @Embeddable @Getter @Setter public class Period{ private LocalDateTime startDate; prrivate LocalDateTime endDate; } @Embeddable @Getter @Setter public class Address{ private String city; private String street; private String zipcode; }
> 기본 생성자는 필수이다.
장점: 재사용성, 높은 응집도, 메서드 만들어서 사용도 가능, 임베디드 타입을 포함한 모든 값 타입은 엔티티에 생명주기 의존
* DB입장에선 임베디드건 뭐건 똑같음 그냥 풀어서 저장함
객체와 테이블을 아주 세밀하게 매핑하는 것 뿐이다! 잘 설계된 ORM은 매핑한 테이블 수보다 클래스의 수가 더 많다.
1.3 임베디드 타입과 연관관계
- 임베디드 타입안에 엔티티 집어넣을 수 있고, 임베디드 타입에 값들에 @Column속성 사용 가능
* @AttributeOverride
한 엔티티에서 같은 임베디드 타입을 두번 사용한다고 생각해보자, 임베디드 자체는 변수명으로 구분 가능하지만, 임베디드 안에 값 타입들은 컬럼에 저장할 때 애매해진다. 이럴때 이 어노테이션으로 같은 임베디드 타입이더라도 컬럼을 다르게 저장할 수 있다.
@AttributeOverrides({@AttributeOverride(name = "city",column = @Column(name = "CITY"))})
* 임베디드 타입값이 null이면, 매핑한 컬럼 값은 모두 null
2. 값 타입과 불변 객체
> 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유시 매우 위험 (side effect 발생)
> 값 타입의 실제 인스턴스인 값을 공유하는 것은 위험하다 (인스턴스 복사해서 사용하자)
2.1 객체 타입의 한계
> 항상 값을 복사해서 사용하면, 공유 참조로 부작용을 피할 수 있다.
> 문제는 임베디드 타입 처럼 직접 정의한 값 타입은 자바의 기본 타입이 아니라 객체 타입이다.
> 객체 타입을 참조 값을 직접 대입하는 것은 막을 방법이 없다! 공유 참조 피할 수 없음 복사해서 써야함
2.2 불변 객체
> 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단
> 값 타입은 불변 객체로 설계해야한다.
> 불변 객체: 생성 시점 이후 절대 값을 변경할 수 없는 객체
> 생성자로만 수정하고 수정자를 안만들면 그만이다!!! (Integer,String은 자바가 제공하는 대표적인 불변 객체)
3. 값 타입의 비교
> 인스턴스가 달라도 값이 같으면 같다고 봐야함
> 동일성 비교 : 인스턴스 참조값 비교 == 사용
동등성 비교 : 인스턴스의 값을 비교 equals()사용 -> 값타입은 a.equals(b)를 사용해서 동등성 비교 해야한다!!
-> 임베디드 타입은 equals()메소드를 적절하게 재정의 합시다
4. 값 타입 컬렉션
> 임베디드 타입 List, 혹은 기본 값 타입 List
> 값 타입 하나 이상 저장시 사용
> @ElementCollection 혹은 @CollectionTable 사용 -> 사용 엔티티 기준으로 OneToMany 관계
@Entity public class Member{ @ElementCollection @CollectionTable(name="FAVORITE_FOOD", joinColomns = @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<>(); //임베디드 타입 }
- 각각 Set과 List를 사용하여 값 타입 컬렉션을 Member Entity에 생성했다.
- @CollectionTable를 사용하여 테이블명과 외래키 지정해주었다 (OneToMany라서 외래키 many쪽에 생성)
4.1 값 타입 저장
Member member = new Member(); member.setName("member1"); member.setHomeAddress(new Address("city1","street","1")); member.getFavoriteFoods().add("치킨"); member.getAddressHistory().add(new Address("old1","street","1")); member.getAddressHistory().add(new Address("old2","street","1")); em.persist(member);
4.2 값 타입 조회
Member findMember = em.find(Member.class, member.getId()); //지연로딩전략 사용 List<Addres> addressHistory = findMember.getAddressHistory(); for(Address address : addressHistory){ System.out.println("address = "+address.getCity()); }
4.3 값 타입 수정
//String 컬렉션 - 일반적인 수정 (불변객체라 상관없다) findMember.getFavoriteFoods().remove("칰"); findMember.getFavoriteFoods().remove("한식"); //임베디드 타입 Address oldAdd =findMember.getHomeAddress(); findMember.setHomeAddress(new Address("new",oldAdd.getStreet(),oldAdd.getZipcode())); //컬렉션을 수정하기 위해선 위와같이 새로운 값을 넣으면 됨 //임베디드 컬렉션 findMember.getAddressHistory().remove(new Address("old_1","street","123")); findMember.getAddressHistory().add(new Address("newCity1","street","123"); //remove를 위해서 새로운 객체 만들어서 삭제하고 새로 넣었다.
*remove는 내부적으로 equals()오 hashCode() 메서드 사용함 이거 잘 오버라이딩 해두자
* 값타입 컬렉션은 영속성 전이와 고아객체 기능을 필수로 가지고 있음
4.4 값 타입 컬렉션 제약사항
> 다른 엔티티와 다르게 식별자 개념이 없음
> 값은 변경하면 추적하기 어렵다.
> 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터 삭제하고, 값 타입 컬렉션에 있는 현재 값 모두 다시 저장함 (@OrderColumn()을 사용하여 컬렉션 값 타입으로 생성된 테이블에 식별자와 기본키를 넣어주는 방식 사용 가능 -> INSERT대신 UPDATE가 전송됨 다만, 원하는 대로 작동하지 않을 때가 있다.!)
>값 타입 컬렉션을 매핑하는 테이블은 모든 칼럼을 묶어서 기본키를 구성해야한다. (null X, 중복 저장 X)
(JPA 기본으로 만들어주는 값타입 테이블은 기본키가 없는 상태이다. 실제 DB에 반영할 때 모든 값들을 묶어서 기본키 구성해야함)
4.5 값 타입 컬렉션 대안
> 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려하자
> 일대다 관계를 위한 엔티티 만들고 여기에서 값 타입 사용하자
> 여기에 영속성 전이와 고아 객체 사용하면 값타입 컬렉션 처럼 사용 가능하다.
//기존의 임베디드 타입을 감싸는 Entity생성 @Entity @Table(name="ADDRESS") public class AddressEntity{ @Id @GeneratedValue private Long id; private Address address; public AddressEntity(){ } public AddressEntity(Address address){ this.address = address; } public AddressEntity(String city, String street,String zipcode){ this.address = new Address(city,street,zipcode); } }
@OneToMany(cascade = ALL, orphanRemoval=true) @JoinColumn(name = "MEMBER_ID") private List<AddressEntity> addressHistory = new ArrayList<>(); //일대다 단방향 관계 (양방향이었다면, 주인은 일 쪽에둠) //생명주기가 모두 member에 위임
*** 값 타입은 정말 값 타입이라 판단될 때 사용하기
-> 식별자가 필요하고, 지속해서 추적하고, 변경이 발생한다면 -> 값타입이 아닌 엔티티!!