본문 바로가기

JPA/양방향연관관계

N:1 양방향 연관관계 매핑하기

길지 않으니 이 글을 먼저 읽어보길 권장한다!!!

code-mania.tistory.com/35

 

DB와 객체의 양방향 연관관계 차이 알아보기

이번 시간에는 양방향 연관관계에 대해 알아보겠다!!!! (좀 빡센 거 같다...) 단방향 연관관계에 대해서 알아볼 때 이런 테이블과 Entity들을 만들었었다. (대충 재활용하겠다는 소리) 객체에서 말

code-mania.tistory.com


전 시간과 같은 테이블과 엔티티를 사용하도록 하겠다!!!

Club과 Student Table
Student Entity와 Club Entity 


양방향 연관관계 매핑

양방향 연관관계 매핑을 할 때는 주인을 반드시 정해줘야 한다. 주인이란 무엇일까?

Student ↔ Club의 양방향 관계 매핑 실습을 해보면서 알아보자!!!

현재 Student → Club은 성립되고 있으므로, Club → Student만 만족시키면 Student ↔ Club가 성립될 것이다.

따라서 Club Entity를 고쳐야한다.

 


Club

package hellojpa;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Setter
@Getter
public class Club {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "CLUB_GENERATOR")
    private Long id;
    private String name;
    private String description;
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "club")
    private List<Student> students = new ArrayList<>();
}

 

 

 

students라는 필드가 추가됐다!! 하나씩 살펴보자~~~
Club:Student = 1:N이 성립한다. 즉, Club → Student는 1 → N인데, 이런 상황에서는 OneToMany 어노테이션을 사용하여 관계를 매핑해준다.

(Tip: 반대의 상황에서는 ManyToOne을 1:1의 상황에서는 OneToOne을 사용해주자!!)

[fetch는 관계설정과는 상관없는 옵션이므로 건너뛰겠다. 궁금하면 검색!!!]

중요한 옵션은 mappedBy이다. 자바 문서를 참고해보면 mappedBy는 양방향 매핑 시 종(주가 아닌) 쪽에 반드시 지정해야한다고 돼있다.

***** 그렇다면 주와 종은 어떻게 결정할까? 객체의 관점에서 외래키가 있는 컬럼의 Entity가 주가 된다!!! *****

(Tip: mappedBy는 OneToMany와 OneToOne에서만 지정 가능하다!! 즉 1:N의 관계에서는 N에 해당하는 Entity가 반드시 주가 된다.
왜냐하면 1:N의 관계에서는 N 테이블에 반드시 외래키 컬럼이 존재한다. 따라서 Java에서도 mappedBy 옵션을 OneToMany에서만 사용가능하게 한 것이다.)

mappedBy 옵션의 value로 들어가는 값은 Student Entity와 함께 봐야 알 수 있다.


Student Entity

package hellojpa;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Getter
@Setter
@Entity
@Table(name = "STUDENT")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "STUDENT_GENERATOR")
    private Long id;

    private int age;

    private String name;

    @ManyToOne
    @JoinColumn(name = "club")
    private Club club;
}

주 Entity인 Student에서는 Club type의 변수가 있고, 변수명은 club이다.

아까 Club에서 봤던 students의 mappedBy 옵션으로 club을 넣었었는데, 이 변수명을 넣어준 것이다.

mappedBy의 옵션의 value로는 양방향 관계의 주 Entity에서 선언된 변수명을 넣어주면 된다.

이제 나머지는 JPA가 알아서 처리해줄 것이다. club.getStudents()를 호출해서 정상작동하는지 확인해보자!!

 


코드 확인해보기

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

public class JpaMain {
    public static void main(String[] args) {
        //애플리케이션 로딩 시점에 DB당 딱 하나만 만들어야 하며, 애플리케이션 전체에서 공유한다.
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        //트랜잭션마다 EntityManager를 만들어줘야 한다.
        EntityManager em = emf.createEntityManager(); // 고객요청 시마다 생성(쓰레드 간에 공유 X)

        EntityTransaction tx = em.getTransaction();
        tx.begin(); //트랜잭션 시작

        try {
            Club club = new Club();
            club.setName("프로그래밍부");
            club.setDescription("재밌게 같이 코딩해봐요~~~");
            em.persist(club);

            Student codeMania = new Student();
            codeMania.setName("code-mania");
            codeMania.setAge(21);
            codeMania.setClub(club);
            em.persist(codeMania);

            Student codeLover = new Student();
            codeLover.setName("code-lover");
            codeLover.setAge(21);
            codeLover.setClub(club);
            em.persist(codeLover);

            em.flush();
            em.clear();

            Club findClub = em.find(Club.class, club.getId());
            List<Student> students = findClub.getStudents();
            for(Student s:students) System.out.println(club.getName() + ": " + s.getName());
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

양방향 관계가 잘 성립된 것을 확인해볼 수 있다!!! 너무 잘 동작한다!!! 너무 행복하다!!!!!

나의 경우 Boot를 섞지 않은 순수 JPA 코드이니, 이 점은 참고하자~~~~

(참고로 코드 중간에 em.flush()와 em.clear()가 있다. 이는 쿼리를 모아서 DB에 날리고, 1차 캐시를 지우는 것이다~~ 잘 모르겠다면 검색해보긔~~~)