ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] 테이블 1:N 관계 만들기 (ERD 연습 1)
    JAVA/JPA 2021. 4. 14. 00:13

    '스프링 부트와 AWS로 혼자 구현하는 웹서비스'라는 책을 보면서 한 테이블의 CRUD를 구현해 보았다.

    최대한 책 내용을 안 보고 짜고 싶었고, 틀린 부분과 잘 안되는 부분들을 생각 해보고 또 다른 책인 김영한님의 JPA 책을 읽어가며 해결 하려고 했다.

     

    궁금증을 해결하지 못한 것들은 메모로 남겨두고, 좀 더 깊이 공부하면서 알아갈 것이다.

     

    책에서는 한 테이블에서의 CRUD만 구현했다. 기초적인 것을 여러번 안 보고짜는 연습을 한 뒤, 테이블의 관계는 어떻게 설정을 할 지 궁금증이 생겼고, 김영한님의 JPA 책도 보고, 구글링을 하면서 테이블의 연관 관계를 나타 내어 보았다.

    여러번 테이블들의 조회나 테스트 코드 작성을 통해 어떤식으로 동작하고, data.sql에 데이터를 미리 넣어둠으로써 데이터가 잘 들어가는 지 확인도 해보았다.

     

    아래의 그림과 같이 간단한 ERD를 만들어 보았다.

    지금은 Member 와 Team 테이블간의 1:N 관계를 알아보도록 해보자

    ERD

    관련된 소스는 이 github을 참고하면 될 것 같다. github.com/KIMJINOH97/JPA_practice

     

    KIMJINOH97/JPA_practice

    ERD를 토대로 테이블 작성 해보는 연습. Contribute to KIMJINOH97/JPA_practice development by creating an account on GitHub.

    github.com

    Team 과 Member 은 1:N 관계로 한 팀은 여러 Member을 가질 수 있다.

    관계를 형성 해주면 Member에는 team_id의 컬럼이 FK로 생기게 된다.

    1:N 관계에서는 단방향과 양방향으로 맵핑을 할 수 있지만, 여기서는 양방향 맵핑으로 코드를 작성 하겠다.

     

    - 먼저 관계를 형성한다는 말이 무엇일까??

    객체에서 관계를 생성한다는 말이 무슨 말인 지 알아내는데 오랜 시간이 걸렸다.

    한 객체에서 다른 테이블의 FK를 가지고 있을 때는 FK의 값만 가지는게 아니라 다른 테이블의 객체를 가지고 있어야한다.

    이 부분은 코드로 설명하는 것이 더 낫겠다.

     

    - 단방향과 양방향???

    단방향 맵핑이란 두 객체 관계에 대해 한 객체에서만 관계를 정의 해준 것이다.

    사실 양방향 맵핑단방향 맵핑이 두개로 이루어 진 것이다. 즉, Team 과 Member가 서로 1:N 관계가 있다고 했을 때

    Team 쪽에서 1:N 관계를 설정해주고, Member에서 N:1의 관계를 설정해주는 것이다.

     

    JPA 책을 보면서 주로 실무에서는 가능한 단방향 맵핑을 위주로 작성하고, 필요할 때 양방향 맵핑을 활용한다고 한다.

    이유는 양방향 맵핑이 관리도 힘들고 자칫하다간 쓸 데없는 JOIN연산이 생길 수 있기 때문이다.

     

    한 FK에 대해 두 Entity 모두 관리 해야 하므로 update, delete 연산을 할 때 두 곳 다 제거를 해줘야한다.

    한 곳만 해주게 된다면 물론 지연로딩에 의해 flush를 하면서 정상적인 값이 들어 올 수 있지만, 그 전에는 원하는 값이 안 올 수 있다.

    이 부분은 나중에 설명하겠다.

     

    Member 테이블

    package com.relation.jpa_practice.domain;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    import javax.persistence.*;
    import java.util.ArrayList;
    import java.util.List;
    
    @NoArgsConstructor
    @Getter
    @Entity
    public class Member {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "member_id")
        private Long id;
        private String name;
        private int age;
    
        // 관계로 FK를 가진 테이블(Team)을 맵핑 -> 객체로 접근
        @ManyToOne
        @JsonIgnore
        @JoinColumn(name = "team_id")
        private Team team;
    
        public Member(String name, int age){
            this.name = name;
            this.age = age;
        }
    
        public void setTeam(Team team){
            this.team = team;
        }
    
    }
    

    Member들은 어떤 한 Team에 속해 있으므로 해당하는 팀들을 참조하는 형식으로 Entity를 구성해 준다.

    이때 DB안에는 FK@JoinColumn 어노테이션의 name에 해당하는 값이 들어간다. [ 뭐든 직접 구현해보고 확인 해보자! ]

     

    사실은 setTeam을 해주면서 해당하는 팀에 멤버 객체도 넣어줘야한다.

    즉, member.teamList.add(this)를 작성해줘야 테스트 코드 작성 할 때나, 제대로 값이 들어갔는 지 알 수 있다.

    < 이 말이 모호한? 감은 있지만 그냥 넘어가도 괜찮다. >

    나중에 트랙잭션과 관련해 포스팅 하겠다.

     

    롬복(lombok) 관련 어노테이션

    @Getter : 각 변수들의 Getter를 자동으로 생성해준다.

    @NoArgsConstructor : 말 그대로 인자가 없는 생성자를 자동으로 만들어 준다.

     

    JPA 관련 어노테이션

    @Id : 현재 Entity에서 PK(Primary Key)에 해당하는 것을 어노테이션으로 지정해준다.

    @Column: 컬럼을 지정해주며 굳이 쓰지 않더라도 적용된다. name속성으로 해당하는 컬럼의 이름을 변경 할 수 있다.

    @GeneratedValue : 주키의 자동생성 전략에 해당하는 어노테이션이다. Type을 IDENTITY로 하면 DB의 IDENTITY의 컬럼을 이용한다.

    @ManyToOne : Team과 관계를 설정하기 위해서 선언 해주었다.

    @JoinColumn : name 속성을 통해 외래키의 이름을 지정해준다. Join을 통해 어떤 컬럼을 쓸 지 명시해준다.

     

    Jackson 관련 어노테이션

    @JsonIgnore : 나중에 Team 테이블을 Json으로 보고자 할 때 필요한 어노테이션이다. 나중에 블로그에 정리하여 링크로 남기겠다.

     

    Member Repository를 통해 save()를 할 때

    Member member = new Member("이름", 24);  를 통해 객체를 생성한 뒤

    setTeam(team) 을 통해 어떤 팀을 참조 할 것인지 정해주고, (team 은 findbyId(id) 를 통해 들고 와도 된다.)

    memberRepository.save(member)을 하면 된다.

     

     

    Team 테이블

    package com.relation.jpa_practice.domain;
    
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    import javax.persistence.*;
    import java.util.ArrayList;
    import java.util.List;
    
    @NoArgsConstructor
    @Getter
    @Entity
    public class Team {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "team_id")
        private Long id;
    
        private String name;
    
        @OneToMany(mappedBy = "team")
        private List<Member> members = new ArrayList<>();
    
        public Team(String name){
            this.name = name;
        }
    
    }
    

     

    @OneToMany : 1:N의 관계를 위한 어노테이션이다. mappedBy 를 통해 Member테이블에 있는 Team 객체와 맵핑을 한다.

     

    1:N의 관계에서 주인은 항상 FK쪽에 있다.

     

    Team객체에서는 읽기만 할 수 있다.

    데이터 베이스 입장에서도 FK 기준으로 JOIN 연산을 하기 때문에 Team 객체의 members를 수정하거나 삭제 해 봤자,

    적용이 되지 않는다.

     

    FK가 있는 Member객체는 CRUD의 작업이 가능하다.

     

    임의로 data.sql을 통해 dummy data를 넣어 Controller에 코드를 작성해 Member테이블과 Team테이블을 조회해 보았다.

     

    http://localhost:8080/api/post/teams

     

    이런식으로 조회가 되며, 각 member에 대한 정보가 나온다.

    likeBooks는 나중에 다룰 내용으로 무시하셔도 된다.

     

    http://localhost:8080/api/post/members

    members에 대해 조회가 잘 되는 것을 볼 수 있다.

    @JsonIgnore을 통해 Team을 시리얼라이즈 하지 않았기 때문에? Team관련해서는 나오지 않는 것으로 보인다.

    나중에 이 어노테이션과 관련해 블로그를 쓸 예정이다.

     

     

     

    참고자료:

    ict-nroo.tistory.com/122

    'JAVA > JPA' 카테고리의 다른 글

    [JPA] 영속성 컨텍스트란? (개념, 장점)  (0) 2021.04.23

    댓글

Designed by Tistory.