본문 바로가기

JPA

단방향 연관관계

ORM을 이용하여 테이블을 객체에 매핑할 때 객체와 관계형 DB의 패러다임 차이로 굉장히 불편한 점이 생긴다.

객체는 .을 이용한 참조를 사용하고, DB는 FK를 이용한 참조를 사용한다.

이 차이로 인한 문제점은 무엇인지 알아보고 JPA로 해결해보자~~~~


예제에 사용할 테이블이다. 이 두 테이블의 관계를 알아보자~~~

클럽 하나는 여러 명의 학생을 가질 수 있습니까? Yes

학생 한 명은 여러 개의 클럽을 가질 수 있습니까? No

위 테이블의 관계는 Student:Club = N:1이다!!!

(만약 둘 다 Yes라고 하면 N:M이 될 것이다)


그러면 이제 위와 같은 테이블을 객체로 바꿔보겠다.(엔티티는 다이어그램 보고 대충 만들어보자~~~)

다른 것보다 club의 Type이 Long이라는 것에 주의하자!!!!

 

음 좋았어!!! 별 문제가 없어보인다. 그러면 이제 정말 간단하게 CRUD 중 CR을 해보자~~~

(참고로 지금 아래 코드는 Spring을 엮지 않은 순수 JPA를 사용한 것이다!!!!!)

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.getId());
    em.persist(codeMania);
    
    em.flush();
    em.clear();

    Student firstStudent = em.find(Student.class, codeMania.getId());
    Club clubOfFirstStudent = em.find(Club.class, firstStudent.getClub().getId());
    
    System.out.println("firstStudent = " + firstStudent.getName());
    System.out.println("clubOfFirstStudent = " + clubOfFirstStudent.getName());

    tx.commit();
} catch (Exception e) {
    tx.rollback();
} finally {
    em.close();
}

데이터는 잘 들어간 모습이다. 하지만 지금 코드는 굉장히 답답하다.

 

 

 

 

 

 

 

먼저 학생 데이터를 persist 할 때 setClub()으로 club의 id값을 넣고 있다. 여기까지는 참을 수 있다고 하자~~

그런데 학생이 어떤 클럽 소속인지 확인하려면 em.find()를 통해 firstStudent.getClub()으로 얻어온 club의 id값으로 조회를 또 해야 된다.

굉장히 이상하다. 학생정보를 조회했고, 여기에는 클럽도 포함되어 있다.

하지만 클럽을 알고 싶으면 클럽정보를 또 조회해야 된다. 너무 이상하고 귀찮다.


여기서 DB와 객체의 차이를 알 수 있다.

DB는 FK를 통해 다른 테이블을 참조하는 것이고, 객체는 .으로 참조하는 것이다.

DB에서는 STUDENT 테이블에서 club이라는 컬럼을 FK로 사용해 CLUB테이블을 참조하고 있다.

반면에 JAVA는 getClub()을 통해 조회한 데이터에서 .으로 쭉쭉 참조해나가야 한다.

애초에 패러다임이 안 맞는다. 그리고 이것은 JPA가 해결해줄 것이다. 알아보자~~~


아까와 다른 점이 보이는가? 다른 점은 Student 클래스의 club의 Type이 Club이라는 것이다.

 

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;
//    private Long club; // 원래 club Type
}

 

엔티티를 보면 Club 위에 @ManyToOne과 @JoinColumn이라는 수상한 어노테이션을 볼 수 있다.

@JoinColumn은 이 필드가 Table 조인을 위한 컬럼이라고 JPA에게 알려주기 위한 어노테이션이다.

name 파라미터로 Join을 위한 컬럼의 컬럼명을 적어주면 된다.

@ManyToOne은 관계를 나타내주는 것으로, Student와 Club의 관계가 N:1이므로 @ManyToOne을 사용하면 된다.

(1:N일 경우에는 @OneToMany를 사용하고, 1:1일 경우에는 @OneToOne을 사용하면 된다!! 궁금하면 직접 해보자~~)

 

 

자 그러면 마지막으로 이렇게 Club 타입으로 객체를 설계하면 얼마나 편한지 체감해보자~~~

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);

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

    Student firstStudent = em.find(Student.class, codeMania.getId());
    System.out.println("first student = " + firstStudent.getName());
    System.out.println("club of first student = " + firstStudent.getClub().getName());

    tx.commit();
} catch (Exception e) {
    tx.rollback();
} finally {
    em.close();
}

 

값을 insert할 때도 select할 때도 한결 편해진 모습을 볼 수 있다!!!!

 

[전체 코드 github 주소, 브랜치: blog/ManyToOne]

'JPA' 카테고리의 다른 글

영속성 전이(CASCADE)와 고아객체  (0) 2021.08.24
@MappedSuperclass  (0) 2021.08.19
기본키 매핑  (0) 2021.02.15
DB 스키마 자동 생성  (0) 2021.02.14
객체와 Table 매핑  (0) 2021.02.14