JPA 에서의 연관관계

이유석·2023년 1월 4일
2

JPA - Entity

목록 보기
3/14
post-thumbnail
post-custom-banner

연관관계

연관관계란 두 도메인(객체, 테이블) 이 서로 논리적인 의미를 갖고 양쪽을 참조하는 관계를 뜻 합니다.

객체 지향 프로그램에서의 객체들이 서로 연관관계를 맺는 방법RDB 에서의 테이블들이 연관관계를 맺는 방법이 다릅니다.
이 간극을 채워주기 위한 기술이 ORM 이며 ORM 기술의 Java 의 표준 명세가 JPA 입니다.

연관관계 매핑 시에는 아래와 같은 사항들을 생각하명 수행하여야 합니다.

  • 방향 (Direction) : 단방향 연관관계, 양방향 연관관계
  • 연관관계의 주인 : 양방향 연관관계 매핑시, 반대편 테이블의 외래키를 관리하는 객체입니다.
  • 다중성 (Multiplicity) : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)

데이터 중심의 모델링

JPA 에서 제공하는 연관관계 매핑에 대해서 알아보기 이전에, 잘못된 연관관계 방법을 살펴보고 해당 방법의 단점을 살펴보도록 하겠습니다.


위 테이블을 Java class 로 표현해보도록 하겠습니다.

class MEMBER {
	private long id;
    private long teamId;
    private String userName;
}

class TEAM {
	private long id;
    private String teamName;
}

이와 같은 방식을 데이터 중심적으로 모델링이라 합니다.
MEMBER 테이블과 TEAM 테이블은 연관관계를 갖고 있으며, 해당 연관관계는 TEAM_ID 라는 외래키를 통하여 확인할 수 있습니다.

데이터 중심적 모델링 방법은 서로 다른 두 객체 사이의 연관관계를 활용할 수 없게됩니다.
즉, 객체지향 프로그래밍의 객체를 제대로 활용할 수 없게됩니다.

또한 해당 객체를 데이터베이스에 저장(영속화) 및 조회를 하는 과정에서 추가적인 코드가 필요합니다.
아래 코드 예시를 통해서 확인해볼 수 있습니다.

특정 Member 가 속한 Team 객체를 조회하는 코드

@RequiredArgsConstructor
public class Jpa {

	private final EntityManagerFactory emf;
    
    public void save() {
    	EntityManager em = emf.createaEntityManager();
        EntityTransaction tx = em.getTransaction();
        
        tx.begin();
        try {
        	Team team = new Team();
            team.setTeamName("teamA");
            em.persist(team);
            
            Member member = new Member();
            member.setUserName("member1");
            member.setTeamId(team.getId());
            
            em.persiste(member);
            tx.commit();
        } catch(Exception e) {
        	tx.rollback();
        } finally {
        	em.close();
        }
    }
    
    public void find(Long memberId) {
    	EntityManager em = emf.createaEntityManager();
        EntityTransaction tx = em.getTransaction();
        
        tx.begin();
        try {
        	// 불필요한 과정이 많이 필요해진다.
            Member member = em.find(Member.class, memberId);
            Long teamId = member.getTeamId();
            Team team = em.find(Team.class, teamId);
            
            tx.commit();
        } catch(Exception e) {
        	tx.rollback();
        } finally {
        	em.close();
        }
    }
}

특정 Member 가 속한 Team 정보를 조회하기 위해서는 아래의 작업이 필요해보입니다.

  1. memberId 에 해당하는 특정 Member 객체를 조회한다.
  2. Member 객체가 외래키로 갖고 있는 teamId 정보를 통해서, teamId 에 해당하는 Team 객체를 조회한다.

즉, Member, Team 을 조회하는 2개의 쿼리문이 동작하게 됩니다.
연관관계가 무수히 많아진 상태에서 이와같은 코드를 적용해야 한다고 생각해봅시다. 머리가 아파진다.

방향

관계형 데이터베이스의 테이블에는 방향이라는 개념이 없습니다.

  • 즉, 외래키 하나로 양쪽의 테이블을 필요에 따라 조인하여 조회 및 저장 할 수 있습니다.

하지만 객체지향프로그래밍에서는, 참조용 필드가 있는 쪽으로만 참조가 가능합니다.

class MEMBER {
	private long id;
    private String userName;
    
    @ManyToOne
    private TEAM team;
}

class TEAM {
	private long id;
    private String teamName;
}
  • 위와 같은 객체 설계에서 TEAM 클래스에 대한 참조용 필드를 갖고 있는 MEMBER 클래스 방향으로만 참조가 가능합니다.

단방향 연관관계

  • 참조용 필드를 한쪽만 갖고 있는 구조 입니다.
  • 즉, 참조용 필드를 갖고 있는 클래스만이 반대편의 클래스를 참조할 수 있습니다.

아래의 다이어그램과 같이 표현할 수 있습니다.

양방향 연관관계

  • 참조용 필드를 양쪽 모두 갖고 있는 구조 입니다.
  • 즉, 양쪽의 클래스 모두 각 반대편의 클래스를 참조할 수 있습니다.

아래의 다이어그램과 같이 표현할 수 있습니다.

  • 위 도표의 객체 연관관계를 살펴보면 아래의 연관관계를 찾을 수 있습니다.

    • Member → Team 연관관계 1개(단방향)
    • Team → Member 연관관계 1개(단방향)
  • 객체의 양방향 관계는 사실 서로 다른 단방향 관계 2개입니다.
    객체를 양방향으로 참조하려면 억지로 단방향 연관관계를 2개 만들어야 합니다.

class Member {
	private long id;
    private String userName;
    
    @ManyToOne
    @JoinColumn(name="team_id")
    private Team team;
}

class Team {
	private long id;
    private String teamName;
    
    @OneToMany(mappedBy="team")
    private List<Member> memberList = new ArrayList<>();
}

// member.getTeam(); 방식으로 조회
// team.getMemberList(); 방식으로 조회
  • 도표에서의 테이블 연관관계를 살펴보면 아래의 연관관계를 찾을 수 있습니다.
    • MEMBER ↔ TEAM 의 연관관계 1개 (양방향)
  • 테이블의 양방향 관계는 외래 키 하나로 관리합니다.
SELECT * 
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID;

SELECT *
FROM TEAM T
JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID;

연관관계의 주인

  • 양방향 연관관계 매핑시, 반대편 테이블의 외래키를 관리하는 객체입니다.

  • 해당 다이어그램을 살펴봅시다.
    • MEMBER 테이블이 TEAM_ID (외래키) 를 갖고 있습니다.
    • 즉, MEMBER 테이블이 해당 연관관계의 주인이 됩니다.

이와같이 외래키를 관리하는 객체(연관관계의 주인)을 따로 정해주는 이유는 아래와 같습니다.

  • 객체의 양방향 연관관계는 A → B, B → A 처럼 참조가 2군데에서 일어납니다.
  • 이를 테이블 연관관계로 옮기면, 외래키 1개를 통해서 표현할 수 있습니다.
    이때, 해당 외래키를 관리하는 테이블을 지정해 주어야 합니다.

주인만이 외래키를 관리(등록, 수정) 합니다.

  • 객체에 둘 다 정보를 업데이트 하여도, 연관관계의 주인에 해당하는 객체의 변화 정보만 업데이트 됩니다.

주인의 반대편 (즉, 외래키를 관리하지 않는 테이블) 은 외래키에 영향(변화)를 줄 수 없습니다

  • 단순 읽기만 가능합니다.
  • TEAM 객체는 TEAM_ID(외래키)를 통해서 TEAM 에 속한 MEMBER 를 조회할 수 있다.
    해당 MEMBER 의 TEAM_ID(외래키)를 변경하는 것 은 불가능 합니다.

다중성

JPA 에서는 다양한 다중성을 위한 어노테이션을 제공합니다.

  • 다대다 (N:M)
    • @ManyToMany
    • 실무에서는 이를 사용하지 않고, 연결 테이블을 추가하여 일대다, 다대일 관계로 풀어내어 사용합니다.

출처

profile
소통을 중요하게 여기며, 정보의 공유를 통해 완전한 학습을 이루어 냅니다.
post-custom-banner

0개의 댓글