인프런 JPA 기본편 - 김영한 강의 학습내용 정리
자바세상과 데이터베이스 세상은 다르기 때문에 자바언어로 테이블관계로
변환하려고 생각하면 생각보다 까다롭고 복잡하다.
어떻게 자바세상에서 연관관계르 만들면 좋을지 공부해보자
객체지향스러운 연관관계를 가질 수 있는 코드로 DB를 사용하자
방향 : 단방향, 양방향
다중성 : 다대일 , 일대다 , 일대일
연관관계의 주인 : 객체 양방향 연관관계는 관리 감독이 필요 (이게 어렵다고하는데 가장 중요하다고 본다.)
객체를 테이블에 맞추는 모델링 나또한 이렇게 했다.
하지만 객체는 참조를 활용해서 연관관계를 만들어야한다.
@Entity
public class User {
@Id @GeneratedValue
private Long id
//객체를 가져보자
@ManyToOne // 각 USER는 동일한 팀에 속할 수 있기 때문
@JoinColumn(name = "team_id") //ManyToOne 관계를 가질 때 조인할 컬럼을 명시
private Team team;
다른 테이블의 Pk 값을 가지는것이 아닌 관계와 어떤 객체와 관계인지를 어노테이션으로 명시하면된다.
테이블 관계는 다대다 관계를 잘 정리하는게 중요하다.
위 단방향관계에서는 user → Team 을 확인할 수 있지만 Team → user는 불가능 했다.
엥 아닌데 user가 Team을 알고있기 Join하면 서로를 알 수 있을텐데?
테이블 연관관계에서는 가능했다. Team PK로 user의 Team FK 와 조인하면 됐다.
하지만 객체지향 세상에서는 불가능하다.
그렇다면 어떻게 양방향으로 만들어 볼까?
//Team에도 User로 갈 수 있게 List<User>를 추가하자
@Getter
@Entity
public class Tema {
@Id @GenerateValue
private Long id;
@OneToMany(maapedBy = "team")
private List<User> users = new ArrayList<>();
User와는 다르게 @JoinColumn
대신 mappedBy
를 사용했다. 뭐가 다른거지?
mappedBy는 너무 중요하다.
이것을 이해하기 위해서는 객체와 테이블이 관계를 맺는 차이를 이해하는 것이 중요하다.
자바세상과 테이블세상의 차이가 중요함
회원 → 팀
팀 → 회원
단방향 연관관계가 둘
위에서는 양방향으로 만들어볼까? 라고 했지만 단방향 관계를 각각 만들어서
억지로 양방향관계를 만든것이다.
Team ↔ User
상대방의 PK를 참조키로 가지고있다면 Join하여 관계를 형성할 수 있다.
외래 키하나도 해결이 가능한 특징이있다.
객체는 둘 중 하나로 외래 키를 관리해야 한다.
현재 User는 팀을 가지고 있고 Team 또한 User 객체를 가지고 있다.
이때 어떠한 user가 팀을 바꾸고 싶을 때 user의 팀을 바꿔야할 지 Team에 속한 user를
바꿀지 고민이된다.
복잡하게 생각하지 말고 둘 중하나로 외래키를 관리하면 된다.
양방향 매핑 규칙
mappedBy
속성 사용 XmappedBy
속성으로 주인 지정누구를 주인으로 해야하는데?
테이블 관계에서 외래 키가 있는 곳(테이블)을 주인으로 정하자
왜냐하면 Team은 List<User> user
를 가지고 있는데 여기서 팀 객체에
set 설정을 했는데 update 쿼리는 user가 가지고 있는 Team명을 바꿔야하기에
직관적이지 않다.
DB 입장에서 보면 외래키가 있는 곳이 다 인경우가 많다. 여기서 다는 1 : 다 관계를 의미한다.
(연관관계의 주인에 값을 입력하지 않는다)
현재 위에서 정리한 코드를 보면 User
는 Team 객체와의 관계 형성에서 mappedBy를 사용하지 않았다. 따라서 User
객체를 주인이고 mappedBy
를 사용한 Team
객체는 주인이 아니다.
이런 경우에 Team이 소유하고 있는 List<User>
에 User
객체를 Add 하더라고 이것은 데이터베이스에 반영되지 않는다.
데이터베이스에 반영하기위해서는 연관관계의 주인인 User
이기 때문에 User
에만 반영하면 되지만
객체지향적으로 보았을 때는 그리 좋지 않다.
그리고 팀을 바꾸는 (팀을 업데이트) 하는 로직과 그팀의 유저 명단을 출력하는 것이 한 로직에 있다면
DB에 동기화되지 않고 영속성 컨텍스트에만 적용되어 있는 상태기 때문에 팀을 수정한 유저는 조회할 수 없다는 문제가 발생하다.
이러한 문제를 해결하기 위해서 항상 양쪽을 모두 처리하도록하자. Team 입장에서는 List<User>.add(User)
를 수행하면 된다.
⭕️ 순수 객체 상태를 고려해서 양쪽에 모두 값을 넣자 미래에 발생할 수 있는 문제를 줄이자!! ⭕️
💡아래 방법 활용하기
//연관 관계 주인 User 클래스
public void setTeam(Team team) {
// 기존 team List에서 삭제하는 연산을 추가해도 된다.
this.team = team;
team.getUsers().add(this);
}
// 또는 Team 객체에 addMember() 메소드를 두어 처리 가능
// 하지만 둘 중 하나만 사용할 것
setTeam()을 호출할 때 한번에 처리할 수 있도록!!
메소드 명을 changeTeam()
과 같이 할 수 있다.
양방향 매핑 시에 무한 루프를 조심하자
등의 상황에서 순환참조, 무한 루프가 발생할 수 있다.
단방향으로 데이터베이스 설계를 끝내고 양방향은 개발단계에서 고려해도 늦지 않는다.