[JPA] 객체와 테이블은 다르다!

드코딩·2024년 7월 30일
0

JPA

목록 보기
3/4
post-thumbnail

인프런 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는 너무 중요하다.

이것을 이해하기 위해서는 객체와 테이블이 관계를 맺는 차이를 이해하는 것이 중요하다.

자바세상과 테이블세상의 차이가 중요함

  • 객체 연관관계 2개
    • 회원 → 팀

    • 팀 → 회원

      단방향 연관관계가 둘

      위에서는 양방향으로 만들어볼까? 라고 했지만 단방향 관계를 각각 만들어서
      억지로 양방향관계를 만든것이다.

  • 테이블 연관관계
    • Team ↔ User

      상대방의 PK를 참조키로 가지고있다면 Join하여 관계를 형성할 수 있다.

      외래 키하나도 해결이 가능한 특징이있다.

객체는 둘 중 하나로 외래 키를 관리해야 한다.

현재 User는 팀을 가지고 있고 Team 또한 User 객체를 가지고 있다.

이때 어떠한 user가 팀을 바꾸고 싶을 때 user의 팀을 바꿔야할 지 Team에 속한 user를
바꿀지 고민이된다.

복잡하게 생각하지 말고 둘 중하나로 외래키를 관리하면 된다.

연관관계의 주인 정하기

양방향 매핑 규칙

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리(수정, 등록)
  • 주인이 아닌쪽은 읽기만 가능
  • 주인은 mappedBy 속성 사용 X
  • 주인이 아니면 mappedBy 속성으로 주인 지정

누구를 주인으로 해야하는데?

테이블 관계에서 외래 키가 있는 곳(테이블)을 주인으로 정하자

왜냐하면 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() 과 같이 할 수 있다.

양방향 매핑 시에 무한 루프를 조심하자

  • toString() toString() 으로 모든 필드멤버를 출력하지 말고
    양방향 관계의 객체는 빼도록 하자.
  • lombok 롬복에서 toString() 만드는거는 최대한 사용하지말고 사용하더라도
    exclude를 활용하자.
  • JSON 생성 라이브러리 엔티티를 직접 컨트롤러에서 리스폰스로 보내는 경우 JSON으로 변환하는 과정에 장애 발생

등의 상황에서 순환참조, 무한 루프가 발생할 수 있다.

정리

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료 최초 설계할 때 일단 단방향 매핑으로 설계를 끝내자 테이블 설계할 때 객체적으로 생각하지말자 양방향 관계가 생겨서 꼬임
  • 양방향 매핑은 반대 방향으로 조회 기능이 추가된 것 뿐 어렵게 생각하지 말자 🚨 순환참조가 발생할 수 있는 로직이 있는지 잘 확인하자.
  • JPQL에서 역방향으로 탐색할 일이 많다.
  • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 된다.

단방향으로 데이터베이스 설계를 끝내고 양방향은 개발단계에서 고려해도 늦지 않는다.

  • 비즈니스 로직을 기준으로 연관관계의 주인을 선택 ❌❌❌
  • 연관관계의 주인은 외래 키를 가지고 있는 위치를 기준 ⭕️⭕️⭕️

⭐틀린 내용 수정,지적은 언제나 환영합니다.⭐

0개의 댓글

관련 채용 정보