-
-
안녕하세요? 오늘은 JPA의 다양한 연관관계 매핑에 대해서 알아보도록 하겠습니다.
※ JPA의 연관관계에 대해서는 이전 포스팅을 참조해주시길 바라겠습니다.
-
관계 매핑 시 고려사항은 아래와 같습니다.
[다중성]
[단방향/양방향]
[연관관계의 주인]
그럼, 각각의 연관관계 대해서 자세히 알아보도록 하겠습니다.
* [다중성]
다중성에는, 아래와 같은 연관관계가 존재합니다.
다대일 [N:1] - @ManyToOne
일대다 [1:N] - @OneToMany
일대일 [1:1] - @OneToOne
다대다 [N:N] - @ManyToMany
-
다대일 [N:1]
@ManyToOne
: 실무에서 가장 많이 쓰임 : 외래 키 관리 엔티티가 주인으로 타 엔티티를 참조 사용 : 다대일의 반대는 일대다 |
단방향 - 주 테이블 외래 키 관리 엔티티 생성)
@Entity
public class EntityA {
@Id @GeneratedValue
@Column(name = "ENTITYA_ID")
private Long id;
@Column(name = "ENTITYA_NAME")
private String name;
@ManyToOne
@JoinColumn(name = "ENTITYB_ID")
private EntityB entityB;
...
}
단방향 - 대상 테이블 엔티티 생성)
@Entity
public class EntityB {
@Id @GeneratedValue
@Column(name = "ENTITYB_ID")
private Long id;
@Column(name = "ENTITYB_NAME")
private String name;
...
}
샘플코드)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("유닛명");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
EntityB entityB = new EntityB();
entityB.setName("EntityB_1");
em.persist(entityB);
EntityA entityA = new EntityA();
entityA.setName("entityA_1");
entityA.setEntityB(entityB);
em.persist(entityA);
EntityA findEntityA = em.find(EntityA.class, entityA.getId());
EntityB findEntityB = findEntityA.getEntityB();
tx.commit();
+
다대일 양방향 연관관계시에는, 외래 키가 있는 쪽이 연관관계의 주인이 됩니다.
양쪽을 서로 참조하도록 개발이 이루어져야 합니다.
양방향 - 주 테이블 외래 키 관리 엔티티 생성
@Entity
public class EntityA {
@Id @GeneratedValue
@Column(name = "ENTITYA_ID")
private Long id;
@Column(name = "ENTITYA_NAME")
private String name;
@ManyToOne
@JoinColumn(name = "ENTITYB_ID")
private EntityB entityB;
...
}
양방향 - 대상 테이블 엔티티 생성
@Entity
public class EntityB {
@Id @GeneratedValue
@Column(name = "ENTITYB_ID")
private Long id;
@Column(name = "ENTITYB_NAME")
private String name;
@ManyToOne(mappedBy = "entityB") // 연관관계의 주인의 EntityB 변수명
private List<EntityA> entityA = new ArrayList<>();
...
}
샘플코드)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("유닛명");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
EntityB entityB = new EntityB();
entityB.setName("EntityB_1");
em.persist(entityB);
EntityA entityA = new EntityA();
entityA.setName("entityA_1");
entityA.setEntityB(entityB);
em.persist(entityA);
entityB.getEntityAs().add(entityA);
EntityA findEntityA = em.find(EntityA.class, entityA.getId());
List<EntityA> entityAs = findEntityA.getEntityB().getEntityAs();
tx.commit();
※ 양방향은, 필요시에 추가를 하면 되지만, 객체 시점에서의 설계는, 단방향 매핑 연관관계에서 끝나야 올바른 설계입니다.
-
일대다 [1:N]
@OneToMany
: 다대일의 반대 > 1이 연관관계의 주인 : 테이블 DB설계상, 일대다 관계는 항상 다(N) 쪽에 외래 키가 존재 : 객체와 테이블의 차이 때문에, 반대편 테이블의 외래 키를 관리하는 특이한 구조 : @JoinCloumn 사용이 필수 > 그렇지 않다면, 테이블 사이 중간 조인 매핑 테이블이 자동으로 생성 (default : join table 방식) |
다대일 단방향 - 주 테이블 엔티티 생성)
@Entity
public class EntityB {
@Id @GeneratedValue
@Column(name = "ENTITYB_ID")
private Long id;
@Column(name = "ENTITYB_NAME")
private String name;
@OneToMany
@JoinColumn(name = "ENTITYB_ID")
private List<EntityA> entityAs = new ArrayList<>();
...
}
다대일 단방향 - 대상 테이블 엔티티 생성)
@Entity
public class EntityA {
@Id @GeneratedValue
@Column(name = "ENTITYA_ID")
private Long id;
@Column(name = "ENTITYA_NAME")
private String name;
...
}
샘플코드)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("유닛명");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
EntityA entityA = new EntityA();
entityA.setName("entityA_1");
em.persist(entityA);
EntityB entityB = new EntityB();
entityB.setName("entityB_1");
entityB.getEntityAs().add(entityA);
// └> EntityA 테이블의 변경(Update)이 이루어진다.
em.persist(entityB);
tx.commit();
+
일대다 단방향 매핑의 단점
: 엔티티가 관리하는 외래 키가 다른 테이블에 존재 : 연관관계 관리를 위해 추가로 Update SQL가 실행된다. > 성능의 저하 (entityA insert -> entityB insert -> entityA update) |
※ 일대다 단방향 매핑보다는, 다대일 양방향 매핑을 사용 추천
일대다 양방향 - 주 테이블 엔티티 생성)
@Entity
public class EntityB {
@Id @GeneratedValue
@Column(name = "ENTITYB_ID")
private Long id;
@Column(name = "ENTITYB_NAME")
private String name;
@OntToMany
@JoinColumn(name = "ENTITYB_ID")
private List<EntityA> entityAs = new ArrayList<>();
...
}
일대다 양방향 - 대상 테이블 엔티티 생성)
@Entity
public class EntityA {
@Id @GeneratedValue
@Column(name = "ENTITYA_ID")
private Long id;
@Column(name = "ENTITYA_NAME")
private String name;
@ManyToOne
@JoinColumn(name = "ENTITYB_ID", insertable=false, updateable=false)
private EntityB entityB;
...
}
공식적으로 이런 매핑은 존재하지 않는다.
읽기 전용 필드를 사용해서 양방향 처럼 사용하는 방법
해당 방법은 스펙상에서 존재하지 않음
※ 다대일 양방향 매핑을 사용 추천
-
일대일 [1:1]
@OneToOne
: 주 테이블이나 대상 테이블 중에 외래 키 선택 가능 : 외래 키에 DB 유니크 제약조건 추가 : 다대일 양방향 연관관계와 유사 (외래키가 있는곳이 주인) |
+
주 테이블과 대상 테이블에서의 외래 키 선택 관리 대한 차이점
++주 테이블에 외래 키
: 주 객체가 대상 객체의 참조를 가지는 것처럼, 주 테이블에 외래 키를 두고, 대상테이블을 찾음 : JPA 매핑 편리 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능 : 값이 없으면 외래 키에 Null 허용 |
++대상 테이블에 외래 키
: 대상 테이블에 외래 키가 존재 : 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때, 테이블 구조 유지 : 프록시 기능의 한계로 지연 로딩으로 설정해도, 항상 즉시 로딩 됨 |
일대일 단방향 - 주테이블 외래키 관리 주 엔티티 생성)
@Entity
public class EntityA {
@Id @GeneratedValue
@Column(name = "ENTITYA_ID")
private Long id;
@Column(name = "ENTITYA_NAME")
private String name;
@OneToOne
@JoinColumn(name = "ENTITYC_ID")
private EntityC entityC;
...
}
일대일 단방향 - 대상 테이블 엔티티 생성)
@Entity
public class EntityC {
@Id @GeneratedValue
@Column(name = "ENTITYC_ID")
private Long id;
@Column(name = "ENTITYC_NAME")
private String name;
...
}
샘플 코드)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("유닛명");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
EntityA entityA = new EntityA();
entityA.setName("entityA_1");
em.persist(entityA);
EntityC entityC = new EntityC();
entityC.setName("entityC_1");
em.persist(entityC);
EntityA findEntityA = em.find(EntityA.class, entityA.getId());
EntityB findEntityC = findEntityA.getEntityC();
tx.commit();
일대일 양방향 - 주테이블 외래키 관리 엔티티 생성
@Entity
public class EntityA {
@Id @GeneratedValue
@Column(name = "ENTITYA_ID")
private Long id;
@Column(name = "ENTITYA_NAME")
private String name;
@OneToOne
@JoinColumn(name = "ENTITYC_ID")
private EntityC entityC;
...
}
일대일 양방향 - 대상 테이블 엔티티 생성
@Entity
public class EntityC {
@Id @GeneratedValue
@Column(name = "ENTITYC_ID")
private Long id;
@Column(name = "ENTITYC_NAME")
private String name;
@OneToOne(mappedBy = "entityC")
private EntityA entityA;
...
}
일대일 양방향 주테이블 외래키 관리 샘플 코드)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("유닛명");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
EntityA entityA = new EntityA();
entityA.setName("entityA_1");
em.persist(entityA);
EntityC entityC = new EntityC();
entityC.setName("entityC_1");
em.persist(entityC);
EntityA findEntityA = em.find(EntityA.class, entityA.getId());
EntityC findEntityC = findEntityA.getEntityC().getEntityAs();
tx.commit();
※ 일대일 단방향 - 대상 테이블 외래키 관리는 JPA에서 지원 불가
일대일 양방향 - 대상 테이블 외래키 관리 주 테이블 엔티티 생성
@Entity
public class EntityA {
@Id @GeneratedValue
@Column(name = "ENTITYA_ID")
private Long id;
@Column(name = "ENTITYA_NAME")
private String name;
@OneToOne
@JoinColumn(name = "ENTITYC_ID")
private EntityC entityC;
...
}
일대일 양방향 - 대상 테이블 엔티티 생성
@Entity
public class EntityC {
@Id @GeneratedValue
@Column(name = "ENTITYC_ID")
private Long id;
@Column(name = "ENTITYC_NAME")
private String name;
@OneToOne(mappedBy = "entityC")
private List<EntityA> entityAs = new ArrayList<>();
...
}
일대일 양방향 대상 테이블 외래키 관리 샘플 코드)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("유닛명");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
EntityC entityC = new EntityC();
entityC.setName("entityC_1");
em.persist(entityC);
EntityA entityA = new EntityA();
entityA.setName("entityA_1");
em.persist(entityA);
EntityA findEntityA = em.find(EntityA.class, entityA.getId());
EntityC findEntityC = findEntityA.getEntityC().getEntityAs();
tx.commit();
-
다대다 [N:N]
@ManyToMany
: 실무에서 사용 불가 : 연결 테이블이 단순히 연결만 하고 끝나지 않음 ex) 주문시간, 수량 같은 데이터가 들어올 수 있음 |
+
DB 구조상, 관계형 DB는 정규화된 테이블 2개로 다대다 관계 표현이 불가능
> 연결 테이블을 추가해서, 일대다, 다대일 관계로 풀어내야함
+
객체는 컬렉션을 사용해서, 객체 2개로 다대다 관계 가능
다대다 연관관계의 한계를 극복하기 위해서는, @ManyToMany -> @OntToMany, @MantToOne 형식으로, 연결 테이블용 엔티티를 추가해서, 연결 테이블을 엔티티로 승격시킨다.
하지만, 다시한번 말하지만, 해당 연관관계는 사용이 불가능하다.
* [단방향/양방향]
테이블
: 외래 키 하나로 양쪽 조인 가능 > 사실 방향이라는 개념이 없음 |
객체
: 참조용 필드가 있는 쪽으로만 참조 가능 : 한쪽만 참조하면 단방향 : 양쪽이 서로 참조하면 양방향 > 사실상 양방향은 단방향이 두개 |
* [연관관계의 주인]
: 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음 : 객체 양방향 관계는 A -> B, B -> A처럼 참조가 두 곳 > 둘 중 한곳에서 외래 키를 관리할 곳을 지정해야 함 > 외래치를 관리하는 참조가 주인이 된다. ※ 주인의 반대편은 외래 키에 영향을 주지 않고, 단순 조회만 가능 |
-
오늘은 JPA의 다양한 연관관계 매핑에 대해서 알아보았습니다.
그럼 오늘도 즐거운하루 되시길 바라겠습니다.
'프로그래밍 > Back-end' 카테고리의 다른 글
JPA 키 매핑 종류와 전략에 대해서! (0) | 2024.10.14 |
---|---|
JPA 주요 매핑 어노테이션에 대해서! (0) | 2024.10.14 |
JPA 상속관계 매핑 MappedSuperclass (0) | 2024.10.11 |
JPA 상속관계 매핑 (0) | 2024.10.11 |
JPA 객체 지향 모델링 연관관계 매핑 (1탄) (0) | 2024.10.11 |
Java의 인터셉터(Interceptor)와 필터(Filter)에 대해서! (0) | 2024.10.10 |
Java 쿠키(Cookie)와 세션(Session)에 대해서! (0) | 2024.10.10 |
Java 스트링(String) 스트링버퍼(StringBuffer) 스트링빌더(StringBuillder)에 대해서! (0) | 2024.10.10 |