JPA

JPA기본편 - 엔티티 매핑 (연관관계 매핑)

땅콩콩 2023. 4. 5. 00:09
데이터 중심 설계의 문제점

기본 매핑 예제를 보면 데이터베이스 테이블의 외래키를 객체에 그대로 가져오며 테이블 설계에 객체를 맞춘 것을 볼 수 있다.

@Entity
@Table(name = "ORDERS") //order가 예약어로 걸려있는 경우가 있어서 ORDERS로 많이 쓴다.
public class Order {

    @Id @GeneratedValue
    @Column(name = "ORDER_ID")
    private Long id;
    
    @Column(name = "MEMBER_ID")
    private Long memberId; //누가 주문했는지.

    private LocalTime orderDate; 

    @Enumerated(EnumType.STRING)
    private OrderStatus Status;
}

 

그리고 이런 구조에서 주문한 회원을 조회하려면 다음과 같은 로직을 거쳐야 한다.

내가 기존에 장고로 백엔드 로직을 개발할때 이런 방식으로 처리한 부분이 많았던것같다...

반성이 되는 부분이었고 이부분을 처리하는 더 좋은 방법이 궁금했다.

Order order = em.find(Order.class, 1L);
Long memberId = order.getMemberId();
Member member = em.find(Member.class, memberId);

 

위의 코드를 더 객체지향적인 구조로 조회하려면 다음과 같이 객체에서 연관된 객체를 바로 끄집어낼 수 있어야 한다.

Order order = em.find(Order.class, 1L);
Member member = order.getMember()

이런 문제는 연관관계 매핑을 통해 해결할 수 있다. 

 

 

연관관계 매핑 기초

객체는 참조를 사용해서 관계를 맺고, 테이블은 외래키를 사용해서 관계를 맺는다.

이 둘은 완전히 다른 특징을 갖기 때문에 객체와 테이블을 매핑하는 일은 매우 까다롭다.

 

위에서 살펴본 것처럼 테이블의 외래키를 객체에 그대로 가져와 사용하는 방법도 있지만, 앞서 언급했듯이 객체지향적이지 못하다.

따라서 연관 관계 매핑을 통해 이 부분을 개선해볼것이다.

참조를 통한 객체의 연관관계는 언제나 단방향이다.

객체간에 연관관계를 양방향으로 접근할 수 있게 하고싶으면 반대쪽에도 필드를 추가해야 한다.

이렇게 양쪽에서 서로를 참조하는 것을 양방향 연관관계라고 하지만, 사실은 서로 다른 단방향 연관관계 2개이다.

 

//Member.java

@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    @Column(name = "USERNAME")
    private String name;

    //member 입장에서 team은 many to one!
    //외래키가 있는 곳을 주인으로 정해라! 일대다 관계이면 "다"쪽이 연관관계의 주인!
    @ManyToOne //연관관계의 주인. 데이터 변경은 여기서만 가능하다!
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    public void setTeam(Team team) {
        this.team = team;
    }
}
//Team.java

@Entity
public class Team {

    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    
    private String name;

    //team 입장에서 member는 one to many!
    //mappedBy = 나는 지금 뭐랑 연결되어있지? Member class의 team필드!
    //연관관계의 주인이 아니므로 조회만 가능하고, 데이터 변경은 불가하다.
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    public void addMember(Member member) { //연관관계 편의 메서드
        member.setTeam(this);
        members.add(member); 
        //연관관계의 주인이 아닌데도 members까지 값을 처리해주는 이유는 객체관점에서 더 안전하기 때문이다.
    }
}
  • @ManyToOne, @OneToMany = 말그대로 다대일, 일대다 관계를 표현한다.
  • @JoinColumn(name="") = 외래키를 매핑할 때 사용한다. name속성에는 매핑할 외래 키 이름을 지정한다. 이 어노테이션은 생략이 가능하다. (생략하면 기본 전략 사용)
  • mappedBy = 연관관계에서 주인이 아닌쪽에 사용하여 그 속성의 값으로 연관관계의 주인을 지정해야 함.
  • 연관관계의 주인은 외래키가 있는 곳!
  • 연관관계의 주인만 외래키의 데이터를 변경할 수 있다. 주인이 아닌 반대편(inverse)은 읽기만 가능하다.

 

양방향 연관관계의 주의점

양방향 연관관계 사용에서 자주 발생하는 실수는 연관관계의 주인에는 값을 입력하지 않고, 주인이 아닌 곳에만 값을 입력하는 것이다.

외래키의 데이터를 변경할 수 있는 것은 연관관계의 주인이므로, 값의 변경을 원하면 연관관계의 주인에 값을 넣어줘야 한다.

 

그러면 이렇게 생각할 수 있다. 연관관계의 주인이 아닌쪽에는 값을 넣어도 변경이 되지 않으므로 넣어줄 필요가 없는것이 아닌가?

결론부터 말하자면 아니다. 객체 관점에서 생각한다면 양쪽 방향에 모두 값을 입력해주는 것이 안전하다.

그렇지 않으면 jpa가 없는 순수 객체 상태에서 문제가 발생할 수 있기 때문이다.

 

그런데 매번 값을 변경할 때마다 주인인 쪽, 주인이 아닌 쪽 양쪽에 값을 입력해주는것은 귀찮기도 하고 헷갈린다.

따라서 위의 코드중 Team 클래스의 addMember와 같이 양쪽 값을 모두 입력해주는 연관관계 편의 메서드를 한쪽에 만들어두고 사용하는 것이 좋다.

 

정리
  • 우선 단방향 매핑을 잘 만들자. 단방향 매핑만으로 테이블과 객체의 연관관계 매핑은 이미 완료되었다.
  • 그리고 이후에 개발과정에서 역참조가 필요하면 반대방향 참조를 추가해 단방향을 양방향으로 만들자.
  • 양방향 연관관계를 매핑하려면 객체에서 양쪽 방향을 모두 관리해야 한다.
  • 단방향을 양방향으로 만들면 반대 방향으로 객체 그래프 탐색 기능이 추가된다.

 

*인프런 김영한님의 JPA 기본편 강의를 들으며 정리한 내용입니다.

*https://www.inflearn.com/course/ORM-JPA-Basic