김찬진의 개발 블로그
23/12/25 [다양한 연관관계 매핑 - FK 관리 주체, 양방향 연관관계 여부에 따라] 본문
23/12/25 [다양한 연관관계 매핑 - FK 관리 주체, 양방향 연관관계 여부에 따라]
kim chan jin 2023. 12. 25. 15:05테이블과 객체간 큰 개념 차이가 있다.
테이블은 두 테이블간 어떤 한 테이블만이 FK를 가지더라도 두 테이블들은 서로 연관관계를 맺을 수 있다.
예를 들어 아래와 같이 말이다.
CREATE TABLE Author
(
AuthorID INT PRIMARY KEY,
AuthorName VARCHAR(255) NOT NULL
);
CREATE TABLE Book
(
BookID INT PRIMARY KEY,
Title VARCHAR(255) NOT NULL,
AuthorID INT,
FOREIGN KEY (AuthorID) REFERENCES Author(AuthorID)
);
INSERT INTO Author (AuthorID, AuthorName) VALUES
(1, '작가1'),
(2, '작가2');
INSERT INTO Book (BookID, Title, AuthorID) VALUES
(101, '책1', 1),
(102, '책2', 1),
(103, '책3', 2);
Author | |
AuthorID (PK) | AuthorName |
1 | 작가1 |
2 | 작가2 |
Book | ||
BookID (PK) | Title | AuthorID (FK) |
101 | 책1 | 1 |
102 | 책2 | 1 |
103 | 책3 | 2 |
SELECT * FROM Author a
JOIN Book b ON a.authorID = b.authorID;
SELECT * FROM Book b
JOIN Author a ON b.authorID = a.authorID;
AUTHORID | AUTHORNAME | BOOKID | TITLE | AUTHORID |
1 | 작가1 | 101 | 책1 | 1 |
1 | 작가1 | 102 | 책2 | 1 |
2 | 작가2 | 103 | 책3 | 2 |
BOOKID | TITLE | AUTHORID | AUTHORID | AUTHORNAME |
101 | 책1 | 1 | 1 | 작가1 |
102 | 책2 | 1 | 1 | 작가1 |
103 | 책3 | 2 | 2 | 작가2 |
반면, 두 객체간 어떤 한 객체가 참조변수를 가지더라도 두 객체들은 서로 연관관계를 맺을 수 없다.
참조변수를 가지지 못한 객체는 참조할 수 없기 때문이다.
예를 들어 아래와 같이 말이다.
@Entity
class Author {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long authorID;
priavte String authorName;
// 현재 Author 는 Book 을 참조할 방법이 없다
}
@Entity
class Book {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long bookID;
priavte String title;
@ManyToOne
@JoinColumn(name = 'authorID')
private Author author; // Book 는 Author 를 참조할 수 있다
}
Author 가 본인에게 달린 Book 객체들을 조회하고 싶다면 다음과 같이 Author 클래스를 변경해야 한다.
추가적으로 연관관계 편의 메서드도 달아준다. (Author 것을 택할지 Book 것을 택할지 선택해야 한다.)
@Entity
class Author {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long authorID;
priavte String authorName;
@OneToMany(mappedBy = "author") // 주인 명시
private List<Book> books = new ArrayList<>(); // 대상
public void addBook(Book book) { // 연관관계 편의 메서드1
book.setAuthor(this);
books.add(book)
}
}
@Entity
class Book {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long bookID;
priavte String title;
@ManyToOne
@JoinColumn(name = 'authorID')
private Author author;
public void changeAuthor(Author author) { // 연관관계 편의 메서드2
this.author = author;
author.getBooks().add(this);
}
}
이것이 테이블과 객체간의 차이이다.
단방향일 때에는 객체간 서로 연관관계를 맺을 필요가 없기 때문에 그 차이를 느끼지 못하지만
양방향일 때에는 객체간 서로 연관관계를 맺어야 하므로 객체에 참조변수를 만들어주는 식으로 그 차이를 없앨 수 있다.
그럼 연관관계 매핑을 할 때 고려할 것은 2가지이다.
1. 누가 FK를 관리할 것인지 (누가 주인, 대상이 될 것인지)
- @JoinColumn(name = "칼럼이름")
2. 단방향에서 그칠건지 더 나아가 양방향으로 만들 것인지
- @ManyToOne 주인 <-> @OneToMany 대상
- @OneToMany 주인 <-> @ManyToOne 대상
결론을 정리하면 다음과 같다.
표를 이해하기 위해 표 아래 질문과 정리한 내용을 공부해보자
제일 어려웠던 고민
Q.
다대일 양방향에서
@OneToMany(mappedBy = "team")
private List<Member> members
은 가능하지만
일대다 양방향에서
@ManyToOne(mappedBy = "members")
private Team team
은 불가능하고 (표에 존재하지 않음)
@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
private Team team
만 가능한 이유?
A.
일대다 양방향의 경우, 객체는 Many (Member)가 FK(TEAM_ID)를 관리할 수 없다. (대상)
테이블은 Many (MEMBER)가 FK(TEAM_ID)를 무조건 관리한다 (주인)
그래서 @ManyToOne @JoinColumn(name = "TEAM_ID") 로 객체(Member)가 FK를 가져오는 것이다.
단, 일대다 양방향의 경우 Member는 대상이므로 읽기 전용으로 제한해야 한다
@ManyToOne @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
1-1. 다대일 단방향
멤버는 팀을 알고 싶지만, 팀은 멤버들을 알고 싶지 않다.
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne // 다대일
@JoinColumn(name = "TEAM_ID") // 주인
private Team team;
// getter, setter 생략
}
@Entity
public class Team {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "TEAM_ID")
private Long id;
private String name;
// getter, setter 생략
}
1-2. 다대일 양방향
멤버는 팀을 알고 싶고, 팀도 멤버들을 알고 싶다.
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne // 다대일
@JoinColumn(name = "TEAM_ID") // 주인
private Team team;
public void changeTeam(Team team) { // 양방향 연관관계 편의 메서드1
this.team = team;
team.getMembers().add(this);
}
// getter, setter 생략
}
@Entity
public class Team {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team") // 일대다, 대상
private List<Member> members = new ArrayList();
public void addMember(Member member) { // 양방향 연관관계 편의 메서드2
member.setTeam(this);
members.add(member);
}
// getter, setter 생략
}
2-1. 일대다 단방향 (권장X )
팀은 멤버들을 알고 싶고, 멤버는 팀을 알고 싶지 않다.
@Entity
public class Team {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany // 일대다
@JoinColumn(name = "TEAM_ID") // 주인
private List<Member> members = new ArrayList<>();
// getter, setter 생략
}
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
// getter, setter 생략
}
2-2. 일대다 양방향 (권장X)
팀은 멤버들을 알고 싶고, 멤버도 팀을 알고 싶다.
@Entity
public class Team {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany // 일대다
@JoinColumn(name = "TEAM_ID") // 주인
private List<Member> members = new ArrayList<>();
// getter, setter 생략
}
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne // 다대일
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) // 읽기 전용 주인 (대상)
private Team team;
// getter, setter 생략
}
3-1. 일대일 단방향 - MEMBER에 FK(LOCKER_ID)가 있는 경우
멤버는 사물함을 알고 싶고, 사물함은 멤버를 알고 싶지 않다.
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "LOCKER_ID")
private Long id;
private String name;
}
3-2. 일대일 양방향 - MEMBER에 FK(LOCKER_ID)가 있는 경우
멤버는 사물함을 알고 싶고, 사물함도 멤버를 알고 싶다.
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "LOCKER_ID")
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
private Member member;
}
3-3. 일대일 단방향 - LOCKER에 FK(MEMBER_ID)가 있는 경우
불가능
3-4. 일대일 양방향 - LOCKER에 FK(MEMBER_ID)가 있는 경우
멤버는 사물함을 알고 싶고, 사물함도 멤버를 알고 싶다.
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@OneToOne(mappedBy = "member")
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "LOCKER_ID")
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
}
'1일1배움 > JPA (김영한 님)' 카테고리의 다른 글
23/12/27 [지연로딩과 즉시로딩] (1) | 2023.12.28 |
---|---|
23/12/27 [em.find() 와 em.getReference() 의 차이] (1) | 2023.12.27 |
23/12/25 [연관관계 편의 메서드] (0) | 2023.12.25 |
23/12/25 [@OneToMany 와 @ManyToOne() 즉, 단방향 매핑과 양방향 매핑] (1) | 2023.12.25 |
23/12/24 [단방향 매핑만으로 이미 연관관계 매핑은 완료된 것이다!] (0) | 2023.12.24 |