일어났는데 날씨가 화창하다 !
공부하기 딱 좋은 날이다.
바람도 선선하니 창문을 보니 기분도 좋아지고

image

여유롭게 커피마시면서 이렇게 공부한게 얼마마인가
소소한 것에 행복을 느껴본다.


어제는 JPA에 대해 알아보았다.
영속성 컨텍스트와 Entity 맵핑에 대해 알아보았고
오늘은 Entity의 연관관계를 맵핑하는 방법을 알아보자 !

연관 관계 Mapping

연관 관계 맵핑은 이전에 Spring data JDBC 학습을 했을 때
사용해봤던 경험이 있다. 그리고 RDBMS 공부 할 떄 1:1, 1:N, N:N 등
Entity간의 연관관계를 정해 데이터를 참조할 수 있었다.

테스트를 위한 간단한 정의를 해보았다.
Member : memberId, email, name, phone, createdAt, modifiedAt
Order : orderId, orderStatus, createdAt, modifiedAt
해당 엔티티에 위와 같은 Column을 가지는 클래스는 존재한다고 가정하겠다.

Member - Order의 연관관계를 맵핑을 연습해볼 것이다.


다대일 관계 (N:1)

위에서 가정한 테이블과 클래스가 이다고 가정하고
다대일 관계를 정의해보겠다.

한명의 손님당 여러개의 주문을 할 수 있으니 Order(N) : Member(1)
관계가 정의되어질 수 있다. 즉, Order Column에서 Member를 참조할 수 있게
만들어주어야한다. 마치 SQL로 JOIN을 하듯이

@ManyToOne
다대일관계를 정의해주는 어노테이션이다.
이름부터 직관적이라 기억하기 쉬울 것 같다.

사용방법을 코드로 알아보자

@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;

public void addOrder(Order order){
    this.orders.add(order);
}

해당 클래스는 Order Entity 클래스에서 추가한 모습이다.

기존에 Order : orderId, orderStatus, createdAt, modifiedAt
총 4개의 Column을 정의 했었는데 여기에 member Column을 추가한 것이다.

그리고 해당 컬럼에 @ManyToOne 어노테이션과
Member 타입의 객체를 만들고, @JoinColumn으로 해당 Column을 참조할
외래키 Column을 적어주면 우리는 member 객체를 통해
각 Order객체의 member 객체를 조회할 수 있게 되는 것이다.

테스트를 위해 DB에 Member 2명과, Order 1건을 만들어 테스트해보자

@Configuration
public class JpaTest {
    private EntityManager em;
    private EntityTransaction tx;

    @Bean
    public CommandLineRunner testJpaSingleMappingRunner(EntityManagerFactory entityManagerFactory){
        this.em = entityManagerFactory.createEntityManager();
        this.tx = em.getTransaction();
        return args -> {
            test();

        };
    }
    public void test(){
        tx.begin();
        Member member1 = new Member("dhfif718@naver.com","이재혁","010-7894-1234");
        Member member2 = new Member("tmdghwlq@naver.com","염승호","010-1234-5623");
        em.persist(member1);
        em.persist(member2);

        Order order = new Order();
        order.addMember(member2);
        em.persist(order);
        tx.commit();

        Order findOrder = em.find(Order.class, 1L);
        System.out.println("findOrder: " + findOrder.getMember().getMemberId() + ", " + findOrder.getMember().getEmail());
    }
}

// 출력
findOrder: 2, tmdghwlq@naver.com

member 객체 2개(member1,member2)와
Order 객체 1개를 만들었다. Order 객체에는 Setter를 이용하여
member2 객체를 member 참조변수에 참조하도록 코드를 추가했다.

만든 객체들을 영속성 컨텍스트의 등록후
commit();을 진행하여 DB에 등록했다.

객체를 한번 DB에서 조회해보자

Order Table 조회

image

Member Table 조회 image

Table 이름은 ORDERS와 USERS로 변경해서 작업했다.
실제로 조회해본 결과 member 객체 2개에 대한 데이터가
잘 저장되어있는 모습과 order 또한 잘 맵핑되어있다.

여기서 @JoinColumn으로 외래키를 적었는데
해당 외래키가 DB Table에 적용된 모습이 확인 가능하다

마지막으로 코드 마지막 구문에 출력으로
해당 객체를 getter를 이용해 값을 조회해보았을때도
정상적으로 member2의 객체 값을 가져오는 모습을 볼 수 있다.


@OneToMany
만약 N:1이 아닌 일대다(1:N)관계를 표현할때는 어떻게해야할까?
일대다 단방향 맵핑은 왜래키를 포함하지 않기때문에 사실 많이 사용하는 방법이 아니라고 들었다.

위에 예제로 들면 Order가 아닌 Member가 Order를 참조할 수 있어야한다.
현재 상태에서는 알수 있는 정보는
-> 주문에서 어떤 고객이 주문했는지 알 수 있다.

주문을 통한 확인이 아닌
어떤 고객이 무슨 주문들을 했는지를 알고 싶을때
일대다 매핑을 추가해주면 양방향 관계를 만들어 줄 수 있다.

@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();

public void addOrder(Order order){
    this.orders.add(order);
}

Member Entity 클래스에 추가된 코드다

OneToMany같은 경우에는 @JoinColumn을 사용하지 않았고
속성중 mappedBy를 사용했다.
안에 있는 member는 우리가 @ManyToOne으로 N:1로 연결 시켜놓은
필드 변수명과 동일하게 설정하면 된다.

양방 연관 관계 맵핑을 끝냈으니
(다대다와는 다른내용이다.)

위에서 보았던 예제에서 test 메서드 내용만 바꾸어 보고
테스트를 진행해보자

private void test() {
    tx.begin();
    Member member = new Member("dhfif718@naver.com", "이재혁", "010-1234-5678");
    Order order1 = new Order();
    Order order2 = new Order();

    member.addOrder(order1);
    member.addOrder(order2);
    order1.addMember(member);
    order2.addMember(member);

    em.persist(member);
    em.persist(order1);
    em.persist(order2);

    tx.commit();

    Member findMember = em.find(Member.class, 1L);

    findMember.getOrders().stream()
    .forEach(findOrder -> {
    System.out.println("findOrder: " + findOrder.getOrderId() + ", " + findOrder.getOrderStatus());
    });
}

//출력
findOrder: 1, ORDER_REQUEST
findOrder: 2, ORDER_REQUEST

test 메서드의 내용만 변경되었고 확인해보면
Member는 1개, Order는 2개를 생성하였고

Member 객체의 addOrder 메서드를 통해 Order객체를 2개를
List에 add 시켜 정리해주었다.

3개의 객체를 영속성 컨텍스트에 올린 후 commit();하여
DB에 최종적으로 저장하게되어진다.

image

image

야기서 양방향이라는 것을 확인 할 수 있는 점은

  1. Order 클래스의 member 객체를 통해 주문의 고객을 알 수 있다.
  2. Member 클래스의 orders 객체를 통해서 고객이 무슨 주문들을 했는지 알 수 있다.
    이렇게 연관관계 맵핑을 통해 서로 데이터를 조회할 수 있는
    양방향 연관관계가 형성되었다는 것을 검증해 볼 수 있다.

한 가지 더 얘기해보자면 Member 클래스의 DB USERS
DB의 List</Order/> orders 객체는 Column으로 표시되지 않는다는 것이다.



오늘은 다대일과 일대다에 대한 어노테이션 사용 방법을 공부했다.

다대다 관계에 대해서는 내일 이어서 정리해보려고 한다.


오늘의 커피량: ☕️ ☕️ ☕️
오늘의 점심: 김치찌개, 계란말이, 밥