본 포스팅은 총 4편으로 구성되어 있습니다.
2024.01.30 - [IT/오픈소스] - JPA - 기본 개념
2024.12.23 - [IT/오픈소스] - JPA - 고급 매핑
2024.12.23 - [IT/오픈소스] - JPA - 다양한 쿼리방법
2024.12.24 - [IT/오픈소스] - JPA - 스프링 연동
다양한 연관관계
다대일(N : 1) - N이 주인
- 단방향 양방향 모두 가능
- JPA에서 가장 많이 사용하고, 꼭 알아야 하는 다중성
일대다(1 : N) - 1이 주인
- 단방향인 경우, 가능은 하나, 비효율
- 특수한 상황이 아닌 경우, 다대일 매핑으로 바꾸어서 사용하는것을 적극 권장
- 단방향
@Entity
public class Member(
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
}
@Entity
public class Team(
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
);
본인 테이블에 외래키가 있으면 Entity의 저장과 연관관계 처리를 INSERT한번에 끝낼수있지만,
다른 테이블에 외래키가 있으므로 연관관계 처리를 위한 UPDATE문 실행.
외래키를 저장하는것은 Member테이블인데, 정작 MemberEntity는 TeamEntity를 알수없음.
그러므로 Member를 저장할때는 외래키에 우선 null값을 저장하고, 나중에 업데이트.
Member m1 = new Member("member1");
Member m2 = new Member("member2");
Team t1 = new Team("team1");
t1.getMembers().add(m1);
t1.getMembers().add(m2);
em.persist(m1); // insert-m1
em.persist(m2); // insert-m2
em.persist(t1); // insert-t1, update-m1.fk, update-m2.fk
- 양방향
일대다 양방향 매핑은 존재하지않는다. @ManyToOne은 mappedBy 속성이 없기때문
하지만, 완전히 불가능하지는 않다. 다대일 단방향매핑을 읽기전용으로 추가하면 된다.
@Entity
public class Member(
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
// 둘다 JoinColumn이므로 한쪽을 읽기전용으로 변경
@ManyToOne
@JoinColumn(name="TEAM_ID", insertable = false, updatable = false)
private Team team;
}
이 방법은 일대다 양방향 매핑이라기보다는 일대다 단방향 매핑 반대편에 다대일 단방향 매핑을 읽기전용으로 추가한것뿐
단방향 매핑이 가지는 단점을 그대로 가지므로 다대일로 매핑을 권장.
일대일(1 : 1)
- 일대일 관계는 반대도 일대일 관계다.
- 주 테이블이든 대상 테이블이든 외래키 하나만있으면 양쪽으로 조회가능.
- 누가 외래키를 가질지 선택.
주 테이블 외래 키
- 장점 : 주 테이블만 조회해도 대상 테이블 데이터가 있는지 확인 가능
- 단점 : 값이 없으면 외래키에 null허용
대상 테이블 외래 키
- 장점 : 테이블 관계를 일대일에서 일대다로 변경할때 테이블 구조를 그대로 유지
- 단점 : 지연 로딩을 설정해도 항상 즉시로딩.
다대다(N : N)
- 관계형 DB는 정규화된 테이블 2개로 다대다 관계를 표현 할 수 없다.
- 일대다, 다대일 관계로 풀기 위하여 관계 테이블을 만들어야한다.
@Entity
public class Member{
@Id @Column(name = "MEMBER_ID")
private String id;
private String username;
@ManyToMany
@JoinTable(name = "Member_Product",
joinColumns = @JoinColumn(name = "MEBMER_ID"), // 본인 테이블 매핑컬럼
inversejoinColumns = @JoinColumn(name = "PRODUCT_ID")) // 대상 테이블 매핑컬럼
private List<Product> products = new ArrayList<>();
}
@Entity
public class Locker{
@Id @Column(name = "PRODUCT_ID")
private String id;
private String name;
@ManyToMany(mappedBy = "products")
private List<Member> members;
}
Product a = new Product();
a.setId("productA");
a.setName("상품A");
em.persist(a);
Member m = new Member();
m.setId("member1");
m.setUsername("회원1");
m.getProduct().add(a); // 연관관계 설정
a.getMembers().add(m); // 연관관계 설정
em.persist(m);
@ManyToMany를 사용하면 굉장히 편하지만, 실무에서는 관계테이블에 단순히 외래키만 담고 끝내지않는다. (시간, 수량 등..)
이런 경우에는 더이상 @ManyToMany를 사용할 수 없고 일대다, 다대일 풀어야한다.
@Entity
public class Member{
@Id @Column(name = "MEMBER_ID")
private String id;
private username;
....
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts;
}
@Entity
public class Product{
@Id @Column(name = "PRODUCT_ID")
private String id;
private String name;
}
@Entity
@IdClass(MemberProductId.class)
public class MemberProduct{
@Id
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member; // MemberProductId.member와 연결
@Id
@ManyToOne
@joinColumn(name = "MEMBER_ID")
private Product product; // MemberProductId.product와 연결
private orderAmount ...
}
public class MemberProductId implements Serializable {
private String member; // MemberProduct.member와 연결
private String product; // MemberProduct.product와 연결
// haschCode and equals ..
}
1개 이상의 키가 있을때 JPA는 복합키 클래스를 별도로 만들어야한다.
- Serializable을 구현해야한다.
- 기본 생성자가 있어야한다.
- equals & hashCode를 구현해야한다.
- @IdClass & @EmbeddedId 둘중 하나를 사용해야한다.
복합키로 조회할때는 복합키 클래스로 조회해야한다.
// 조회 코드
MemberProductId memberProductId = new MemberProductId();
memberProductId.setMember("member1");
memberProductId.setProduct("productA");
MemberProduct memberProduct = em.find(MemberProduct.class, memberProductId);
새로운 기본키 사용
- 복합키를 사용하는 방법은 복잡하며, 처리할 일도 상당히 많아진다.
- DB에서 자동으로 생성해주는 대리키 Long값을 사용하면 복합키를 사용하지않아도 된다.
- 대리키를 사용하면 복합키 클래스를 만들 필요도없으며 조회도 훨씬 간단하다.
@Entity
public class Order{
@Id @GeneratedValue
@Column(name = "ORDER_ID")
private Long id;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Product product;
private orderAmount ...
}
// 조회 코드
Order order = em.find(Order.class, 1L);
고급 매핑
관계형 DB에는 객체와 다르게 상속이라는 개념이 없다.
대신, 슈퍼타입 서브타입 관계라는 모델링 기법이 객체의 상속과 유사하다.
ORM에서 이야기하는 상속 관계의 매핑은 객체의 상속구조와 DB의 슈퍼타입 서브타입 관계를 매핑하는것이다.
슈퍼타입 서브타입 논리 모델을 실제 물리 모델인 테이블로 구현하기 위해서는 3가지 방법을 선택할 수 있다.
- 조인전략 : 각각의 테이블로 변환
- 단일테이블전략 : 통합 테이블 하나만 사용
서브타입 테이블 : 서브타입마다 하나의 테이블씩 만듦(권장X)
조인전략
- 엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본키를 받아서 기본키+외래키로 사용하는 전략
- 조회할때 조인을 사용해야한다
- 다만, 객체는 타입으로 구분이 가능하나 테이블은 타입의 개념이 없으므로 따로 구분 컬럼을 추가해주어야한다.
- 장점 : 테이블이 정규화되며, 외래키 참조 무결성 제약조건을 활용할 수 있다.
- 단점 : 조회할때 조인이 많이 사용되므로 성능이 저하된다.
@Entity
@Inheritance(strategy= InheritanceType.JOINED) // 전략 결정
@DiscriminatorColumn(name="DTYPE") // 타입구분 컬럼 설정
public abstract class Item{
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
...
}
@Entity
@DiscriminatorValue(name="A")
// @PrimaryKeyJoinColumn(name = "ALBUM_ID")
// 기본값으로 자식 테이블은 기본키 컬럼명을 부모 테이블의 ID컬럼명으로 그대로 사용하지만, 변경하고싶다면 사용.
public class Album extends Item{
// id는 Item에서 정의했기때문에 사용하지않아도 된다.
private String artist;
...
}
@Entity
@DiscriminatorValue(name="M")
public class Movie extends Item{
// id는 Item에서 정의했기때문에 사용하지않아도 된다.
private String director;
...
}
단일테이블 전략
- 조인 전략과 다르게 1개의 테이블에 모든 컬럼을 집어넣어 단일테이블로 구성한다.
- 장점 : 조인을 사용하지않으므로 조회성능이 빠르다
- 단점 : 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야한다.(특정 엔티티에 따라 다른속성들은 null이 입력되기 때문)
@Entity
@Inheritance(strategy= InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="DTYPE")
public abstract class Item{ // 해당 테이블에 모든컬럼 입력
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;
...
}
@Entity
@DiscriminatorValue(name="A")
public class Album extends Item{ ... }
@Entity
@DiscriminatorValue(name="M")
public class Movie extends Item{ ... }
@Entity
@DiscriminatorValue(name="B")
public class Book extends Item{ ... }
구현 클래스마다 테이블 전략
- 구현 클래스만 테이블을 생성함
- 상속이 사라져버리기때문에 이 전략은 비추천
@MappedSuperclass
부모 클래스는 테이블과 매핑하지않고 자식 클래스만 매핑하고 싶을때 사용.
테이블마다 공통적으로 사용하는 등록일자, 수정일자 등을 뽑아내어 효과적으로 관리 할 수 있다.
@MappedSuperclass
public abstract class BaseEntity{ // 해당 클래스는 테이블과 매핑 x
@Id @GeneratedValue
private Long id;
private String name;
}
@Entity
public class Member extends BaseEntity{
// ID 상속
// NAME 상속
private String email;
}
@Entity
public class Seller extends BaseEntity{
// ID 상속
// NAME 상속
private String shopName;
}
@AttributeOverride : 부모로 부터 물려받은 매핑 정보 재정의.
@Entity
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID)) // id를 MEBMER_ID로 재정의
public class Member extends BaseEntity{ ... }
@Entity
@AttributeOverrides({ // 부모로 부터 물려받은 매핑 정보 둘 이상 재정의.
@AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID))
@AttributeOverride(name = "name", column = @Column(name = "MEMBER_NAME))
})
public class Member extends BaseEntity{ ... }
복합키와 식별 관계 매핑
식별과 비식별 관계는 외래키가 기본키에 포함되는지 여부에 따라 나뉜다.
식별관계는 부모의 기본키를 자신의 외래키 + 기본키로 사용하는 관계
비식별 관계는 부모의 기본키를 자신의 외래키로만 사용하는 관계
복합키 : 비식별 매핑
JPA에서 복합키를 매핑하려면 복합키 생성 클래스를 만들어야한다.
2가지 중에 1개를 골라서 사용해야한다.
- @IdClass
- @EmbeddedId
복합키를 사용할때 지켜야할 규칙이 있다.
Serializable 인터페이스를 구현해야한다.- 기본생성자가 있어야한다.
- 식별자 클래스는 public이여야 한다.
- equals, hashCode를 구현해야한다.
@IdClass
- 관계형 DB에 가까운 방식. 컬럼에 중복이 생긴다.
@Entity
@IdClass(ParentId.class)
public class Parent {
@Id
@Column(name = "PARENT_ID1)
private String id1; // ParentId.id1과 연결
@Id
@Column(name = "PARENT_ID2)
private String id2; // ParentId.id2과 연결
...
}
public class ParentId implements Serializable {
private String id1; // Parent.id1과 매핑
private String id2; // Parent.id2과 매핑
public ParentId() { .. }
public ParentId(String id1, String id2) { .. }
// equals & hashCode
}
@Entity
public class Child {
@Id
private String id;
@ManyToOne
@JoinColumns({ // 부모 테이블의 기본 키 컬럼이 복합 키이므로 자식 테이블의 외래키도 복합키다. 따라서 여러 컬럼을 매핑해야 하므로 @JoinColums 사용
@JoinColumn(name = "PARENT_ID1"),
@JoinColumn(name = "PARENT_ID2")
})
private Parent parent;
}
Parent parent = new Parent();
parent.setId1("myId1");
parent.setId2("myId2");
parent.setName("name");
em.persist(parent); // ParentId를 만들지않아도 내부에서 영속성 컨텍스트 키로 사용한다.
ParentId parentId = new ParentId("myId1","myId2");
Parent parent = em.find(Parent.class, parentId);
@EmbeddedId
- 객체지향적인방법
- 식별자 클래스에서 복합키 설정을 전부 다 함.
@Entity
public class Parent {
@EmbeddedId
private ParentId id; // ParentId 클래스를 넣어야함
...
}
@Embeddable
public class ParentId implements Serializable { // @IdClass와 다르게 식별자 클래스 기본키를 직접 매핑
@Column(name = "PARENT_ID1)
private String id1;
@Column(name = "PARENT_ID2)
private String id2;
public ParentId() { .. }
public ParentId(String id1, String id2) { .. }
// equals & hashCode
}
@Entity
public class Child {
@Id
private String id;
@ManyToOne
@JoinColumns({ // 부모 테이블의 기본 키 컬럼이 복합 키이므로 자식 테이블의 외래키도 복합키다. 따라서 여러 컬럼을 매핑해야 하므로 @JoinColums 사용
@JoinColumn(name = "PARENT_ID1"),
@JoinColumn(name = "PARENT_ID2")
})
private Parent parent;
}
Parent parent = new Parent();
ParentId parentId = new ParentId("myId1","myId2");
parent.setId(parentId);
parent.setName("name");
em.persist(parent);
ParentId parentId = new ParentId("myId1","myId2");
Parent parent = em.find(Parent.class, parentId);
@IdClass vs @EmbeddedId
@EmbeddedId가 중복도 없고 객체 지향적이지만 특정상황에 JPQL 좀더 길어질 수 있다.
em.createQeury("select p.id.id1, p.id.id2 from Parent p"); // @EmbeddedId
em.createQeury("select p.id1, p.id2 from Parent p"); // @IdClass
복합키: 식별 관계 매핑
식별 관계에서는 자식은 부모의 외래 키를 자신의 기본키로 사용하기 때문에 자식에 손자까지 있다면 손자는 자식과 부모의 외래 키까지 기본키로 사용해야한다.
식별 관계는 기본키와 외래키를 같이 매핑해야한다.
@IdClass
// 부모 클래스
@Entity
public class Parent{
@Id @Column(name = "PARENT_ID")
private String id;
...
}
// 자식 클래스
@Entity
@IdClass(ChildId.class)
public class Child{
@Id
@ManyToOne
@JoinColumn(name = "PARENT_ID") // 기본키와 + 외래키를 동시에 매핑
private Parent parent;
@Id @Column(name = "CHILD_ID")
private String childId;
...
}
// 자식 ID (식별자 클래스)
public class ChildId implements Serializable{
private String parent; // Child.parent 매핑
private String childId; // Child.childId 매핑
// equals, hashCode
...
}
// 손자 클래스
@Entity
@IdClass(GrandChildId.class)
public class GrandChild{
@Id
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID"),
@JoinCOlumn(name = "CHILD_ID")
})
private Child child;
@Id @Column(name = "GRANDCHILD_ID")
private String id;
...
}
// 손자 ID
public class GrandChildId implements Serializable{
private ChildId child; // GrandChild.child 매핑
private String id; // GrandChild.id 매핑
// equals, hashCode
...
}
@EmbeddedId
- @Id 대신 @MapsId로 매핑 (외래키와 매핑한 연관관계를 기본키에도 매핑하겠다는 의미)
// 부모
@Entity
public class Parent{
@Id @Column(name = "PARENT_ID")
private String id;
...
}
// 자식
@Entity
public class Child{
@EmbeddedId
private ChildId id;
@MapsId("parentId") // ChildId.parentId 매핑
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
...
}
// 자식 ID
@Embeddable
public class ChildId implements Serializable{
private String parentId; // @MapsId("parentId")로 매핑
@Column(name = "CHILD_ID")
private String id;
// equals, hashCode
...
}
// 손자
@Entity
public class GrandChild{
@EmbeddedId
private GrandChildId id;
@MapId("childId") // GrandCHildId.childId 매핑
@ManyToOne
@JoinColumns({
@JoinColumn(name = "PARENT_ID"),
@JoinColumn(name = "CHILD_ID")
})
private Child child;
...
}
// 손자 ID
@Embeddable
public class GrandChildId implements Serializable{
private ChildId childId; // @MapsId("childId") 매핑
@Column(name = GRANDCHILD_ID)
private String id;
...
}
복합키 식별관계는 아래처럼 복합키 비식별관계로 변경하면 훨씬 간단하다. (소스코드 생략)
조인 테이블
데이터베이스 테이블의 연관관계를 설계하는 방법은 크게 2가지다.
- 조인 컬럼 사용(외래 키) @JoinColumn
- 조인 테이블사용(테이블 사용) @JoinTable
조인 테이블은 주로 다대다 관계를 일대다, 다대일 관계로 풀어내기 위해 사용하지만,
일대일, 일대다, 다대일 에서도 사용할수는 있다.
조인 테이블의 가장 큰 단점은 테이블을 하나 추가해야한다는점.
→ 관리 포인트가 늘기때문에 조인 컬럼을 사용하는것을 추천.
다대다 조인 테이블 (일대일, 일대다, 다대일 코드는 생략)
// 부모
@Entity
public class Parent{
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@ManyToMany
@JoinTable(name = "PARENT_CHILD", // 식별관계만 존재하는 테이블이므로 PARENT_CHLID 테이블은 따로 매핑하지않아도됨.
joinColumns = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID"))
private List<Child> child = new ArrayList<Child>();
...
}
// 자식
@Entity
public class Child{
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
...
}
프록시와 연관관계
- 엔티티 조회 시 연관된 엔티티들이 항상 사용되는 것이 아니다.
- 프록시를 사용하면 연관된 엔티티를 처음부터 데이터베이스에 조회하는 게 아닌 실제 사용하는 시점에 데이터베이스에서 조회할 수 있음.
- 자주 함께 사용하는 엔티티들은 조인을 사용해서 함께 사용하는 것이 효과적
- JPA는 즉시 로딩(Eager Loading)과 지연 로딩(Lazy Loading)을 둘 다 지원
해당 코드를 사용시 영속성 컨텍스트에 엔티티가 없으면 DB를 조회한다.
엔티티를 직접 조회하면 실제 엔티티를 사용하든 사용하지않든 DB를 조회하게된다.
Member member = em.find(Member.class, "member1");
엔티티를 실제 사용하는 시점까지 DB조회를 미루고 싶다면 프록시 객체를 사용하면 된다.
프록시 객체는 실제 클래스를 상속 받아서 만들어지므로 사용하는 입장에서 이것이 진짜 객체인지 프록시 객체인지 구분하지않아도된다.
Member member = em.getReferance(Member.class, "member1"); // 여기서 DB조회를 하지않고
mebmer.getName(); // 실제 사용하는 여기서 DB조회
프록시 객체인지 확인
boolean b = em.getEntityManerFactory().getPersistenceUnitUil().isLoaded(entity); // 초기화 되지않은 프록시객체라면 false
System.out.println(member.getClass().getName()); // ..javassist.. 라고 나오면 프록시객체
즉시 로딩
즉시로딩 : 엔티티를 조회할때 연관된 엔티티도 함께 조회한다.
@ManyToOne(fetch = FetchType.EAGER
Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); // 객체 그래프 탐색
지연 로딩
지연로딩 : 연관된 엔티티를 실제 사용할 때 조회한다.
@ManyToOne(fetch = FetchType.LAZY)
Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); // 프록시 객체
team.getId(); // 실제 객체
@ManToOne, @OneToOne : 즉시 로딩 디폴트
@OneToMany, @ManyToMany : 지연 로딩 디폴트
기본적으로 지연 로딩으로 전부 세팅하고, 자주사용되는것만 즉시로딩으로 하는것이 성능상 권장
(단, 컬렉션 하나 이상을 즉시 로딩하는 것은 권장하지 않으며, 컬렉션 즉시 로딩은 항상 외부 조인이 사용됨)
영속성 전이
1. CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을때 사용.
CascadeType 옵션
- ALL: 모두 적용
- PERSIST: 영속
- REMOVE: 삭제 (부모 삭제시 자식도 같이 삭제 DELETE쿼리)
- MERGE: 병합
- REFRESH: REFRESH
- DETACH: DETACH
JPA에서 엔티티를 저장할때 연관된 모든 엔티티는 영속 상태여야한다.
따라서 부모 엔티티를 영속 상태로 만들때 자식 엔티티도 각각 영속 상태로 등록 해주어야한다.
이때, 영속성 전이를 사용하면, 부모면 영속상태로 만들어도 자식까지 같이 적용된다.
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> children = new ArrayList<>();
Child c1 = new Child();
Child c2 = new Child();
Parent p1 = new Parent();
c1.setParent(p1); // 연관관계 추가
c2.setParent(p1); // 연관관계 추가
p1.getChildren().add(c1);
p1.getChildren().add(c2);
em.persist(p1); // 부모와 함께 자식도 저장
// em.persist(c1); 필요없음
// em.persist(c2); 필요없음
2. 고아
JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능 제공. = CascadeType.REMOVE
다른 곳에 참조되지 않는 엔티티는 고아객체로 판단하고 삭제.
참조하는 곳이 하나일때만 사용되기때문에 @OneToOne, @OneToMany에서만 사용가능
영속성 삭제가 아니라 delete 쿼리로 실제 데이터 삭제.
@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<>();
Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0); // 자식 엔티티를 컬렉션에서 제거
em.flush(); // DELETE FROM CHILD WHERE ID = ? 생성
CascadeType.ALL + orphanRemoval = true 를 동시에 사용하면, 부모는 자식의 생명주기를 관리할수있다.
값타입
JPA는 데이터타입은 엔티티타입과 값타입이 존재.
엔티티타입 :
- @entity로 정의, 식별자로 지속적인 추적 가능, 엔티티 속성값을 변경하더라도 같은 엔티티
값타입:
- int, String, Integer처럼 단순히 값으로 사용하는 자바 기본타입이나 객체
- 기본타입 - 자바 기본타입, 래퍼클래스(Integer), String
- 임베디드 타입 - JPA에서 사용자가 직접 지정한 타입
- 컬렉션 값 타입 - 하나 이상의 값타입 저장
기본타입 - 식별자 x
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
private int age;
...
}
임베디드 타입 - JPA에서 새로운 값 타입 직접 정의해서 사용
- 강한 응집력 적용가능.
- @AttributeOverride를 사용해서 임베디드 타입에 정의한 매핑정보 재정의 가능
// Embedded type 사용하지 않은 경우
@Entity
public class MemberWithoutEmbeddedType {
@Id
@GeneratedValue
private Long id;
private String name;
// 근무 기간
@Temporal(TemporalType.DATE)
Date startDate;
@Temporal(TemporalType.DATE)
Date endDate;
// 집 주소
private String city;
private String street;
private String zipcode;
...
}
...
// 근무 기간, 집 주소 Embedded type 만들어서 사용한 경우
@Entity
public class MemberWithEmbeddedType {
@Id
@GeneratedValue
private Long id;
private String name;
// 근무 기간
@Embedded
Period workPeroid;
// 집 주소
@Embedded
Address homeArrdress;
...
}
...
// 기간 embedded type
@Embeddable
public class Period {
@Temporal(TemporalType.DATE)
Date startDate;
@Temporal(TemporalType.DATE)
Date startDate;
...
public boolean isWork(Date date) {
// 값 타입을 위한 메소드 정의 가능
}
...
}
// 주소 embedded type
@Embeddable
public class Address {
@Column(name="city") // 매핑할 컬럼 정의 가능
private String city;
private String street;
private String zipcode;
...
}
값 타입 컬렉션
- 값 타입을 하나 이상 저장하려면 컬렉션에 넣고 @ElementCollection, @CollectionTable 어노테이션을 사용
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Embedded
private Address homeAddress;
@ElementCollection
@CollectionTable(name = "favorite_foods",
joinColumns = @JoinColumn(name = "member_id"))
@Column(name = "food_name")
private Set<String> favoriteFoods = new HashSet<String>(); // String은 기본값 타입
@ElementCollection
@CollectionTable(name = "address",
joinColumns = @JoinColumn(name = "member_id"))
private List<Address> addressHistory = new ArrayList<Address>(); // Addresssms 임베디드 타입
...
}
@Embedaable
public class Adress {
@Column
private String city;
private String street;
private String zipcode;
...
}
값타입 컬렉션 제약사항
- 값 타입 컬렉션에 보관된 값 타입들의 경우에는 별도의 테이블에 보관되므로 변경시 원본 데이터 찾기 어려움
- 그러므로, JPA 구현체들은 값 타입 컬렉션에 변경 사항 발생하면 컬렉션이 매핑된 테이블의 연관된 모든 데이터 삭제 후 현재 들어있는 값 새로 DB에 저장함
→ 값 타입 컬렉션 쓰는 대신 새 엔티티 만들어서 1:N 관계로 사용 권장
'IT > 오픈소스' 카테고리의 다른 글
JPA - 스프링 연동 (2) | 2024.12.24 |
---|---|
JPA - 다양한 쿼리방법 (0) | 2024.12.23 |
프로메테우스(Prometheus) 자바 클라이언트 활용 (0) | 2024.05.06 |
프로메테우스(Prometheus) 기본 사용법 (0) | 2024.05.05 |
프로메테우스(Prometheus) 오픈소스란? (0) | 2024.05.05 |
댓글