-
[JPA] 테이블 1:N 관계 만들기 (ERD 연습 1)JAVA/JPA 2021. 4. 14. 00:13
'스프링 부트와 AWS로 혼자 구현하는 웹서비스'라는 책을 보면서 한 테이블의 CRUD를 구현해 보았다.
최대한 책 내용을 안 보고 짜고 싶었고, 틀린 부분과 잘 안되는 부분들을 생각 해보고 또 다른 책인 김영한님의 JPA 책을 읽어가며 해결 하려고 했다.
궁금증을 해결하지 못한 것들은 메모로 남겨두고, 좀 더 깊이 공부하면서 알아갈 것이다.
책에서는 한 테이블에서의 CRUD만 구현했다. 기초적인 것을 여러번 안 보고짜는 연습을 한 뒤, 테이블의 관계는 어떻게 설정을 할 지 궁금증이 생겼고, 김영한님의 JPA 책도 보고, 구글링을 하면서 테이블의 연관 관계를 나타 내어 보았다.
여러번 테이블들의 조회나 테스트 코드 작성을 통해 어떤식으로 동작하고, data.sql에 데이터를 미리 넣어둠으로써 데이터가 잘 들어가는 지 확인도 해보았다.
아래의 그림과 같이 간단한 ERD를 만들어 보았다.
지금은 Member 와 Team 테이블간의 1:N 관계를 알아보도록 해보자
관련된 소스는 이 github을 참고하면 될 것 같다. github.com/KIMJINOH97/JPA_practice
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관련해서는 나오지 않는 것으로 보인다.
나중에 이 어노테이션과 관련해 블로그를 쓸 예정이다.
참고자료:
'JAVA > JPA' 카테고리의 다른 글
[JPA] 영속성 컨텍스트란? (개념, 장점) (0) 2021.04.23