객체를 테이블에 맞춰 데이터 중심으로 모델링하면 협력 관계를 만들 수 없다
- 테이블은 외래 키로 조인을 사용해 연관된 테이블을 찾는다
- 객체는 참조를 사용해 연관된 객체를 찾는다
단방향 연관관계
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private int age;
// @Column(name = "TEAM_ID")
// private Long teamId;
@ManyToOne // JPA에 다대일 연관관계가 있음을 알려줌
@JoinColumn(name = "TEAM_ID") // 외래키 매핑
private Team team; // 객체 참조
- teamId 값이 아닌 객체 자체를 참조하고, 그 객체와 테이블의 외래키를 매핑한다
//객체 지향적인 코드 진행 가능
// 팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
// 회원 저장
Member member = new Member();
member.setUsername("member1");
member.setTeam(team);
em.persist(member);
// 조회
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();
// 새로운 팀 B
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
// 회원1에 새로운 팀 B 설정
member.setTeam(teamB);
양방향 연관관계
- 테이블은 단방향과 관계도가 똑같다 -> 외래키 설정 하나로 양쪽 방향 모두 조인 가능 (단방향이란 것이 없음)
// Team 클래스에 컬렉션 추가
@OneToMany(mappedBy = "team") // 일대다 연관관계가 있음과 주인이 아님을 알려줌
private List<Member> members = new ArrayList<>(); // add할때 nullpoint 에외 없도록 ArrayList로 초기화
- mappedBy와 연관관계 주인
- 객체는 사실상 단방향 두개를 합쳐서 양방향이라고 부르는 것이기 때문에 그 양방향 관계에서 DB의 외래키를 관리하는 주인이 필요하다
- 양방향이 되면 멤버가 속한 팀이 바뀔 때 멤버의 team필드가 바뀌어야하는지, 팀의 members필드가 바뀌어야하는지 결정하는 문제가 생긴다.
- 외래키가 있는 MEMBER 테이블과 매핑된 Member 엔티티의 참조값(Member.team)을 주인으로 정하기!
- 주인만이 외래 키를 관리(등록, 수정, 삭제) -> mappedBy 미사용
- 주인이 아닌 쪽은 읽기만 가능 -> mappedBy 사용
- Team.members에 아무리 변화를 줘바야 DB에 반영되는 것은 없고
- Member.team에 변화를 줘야 DB에 반영된다
- 결론! 다대일 양방향 관계에서는 다 쪽이 주인이 된다
주의점!
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
team.getMembers().add(member); // 주인이 아닌 방향에만 연관관계 설정
em.persist(member);
MEMBER_ID | USERNAME | TEAM_ID |
2 | member1 | null |
- 연관관계 주인에 값을 입력하지 않아 DB에 외래키 값이 없다
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // 연관관계 주인에 값 설정
// team.getMembers().add(member);
em.persist(member);
MEMBER_ID | USERNAME | TEAM_ID |
2 | member1 | 1 |
- 하지만 순수한 객체 관계를 고려하면 양쪽에 다 값을 설정해주는 것이 좋다
- 주인이 아닌쪽에서 설정하지 않아 1차 캐시에 캐싱된 데이터를 불러왔을 때 값이 비어있을 수 있다
- 테스트 코드 짤 때 좋다
member.setTeam(team); // 연관관계 주인에 값 설정
team.getMembers().add(member); // 주인이 아닌쪽에 값 설정
- 위와 같이 하면서 실수로 누락할 수 있기 때문에 아래와 같은 연관관계 편의 메소드 생성하여 둘 중 하나만 사용
// Member 클래스
public void changeTeam(Team team) {
this.team = team; // 주인에 값 설정
team.getMembers().add(this); // 역방향 설정
}
// main 클래스
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.changeTeam(team); //**
em.persist(member);
또는
// Team 클래스
public void addMember(Member member) {
member.setTeam(this); // 주인에 값 설정
members.add(member); // 역방향 설정
}
// main 클래스
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
team.addMember(member); //**
em.persist(member);
- 무한 루프 조심
- 예) toString(), lombok, JSON 생성 라이브러리
- 양쪽에서 IDE가 자동생성한 toString() 메소드 호출하여 무한으로 서로를 호출할 수 있음
- 컨트롤러에서 엔티티 자체를 반환하지 말고 Dto 사용하기
- 예) toString(), lombok, JSON 생성 라이브러리
정리
- JPA 연관관계 매핑의 설계 단계에서 단방향으로만 매핑 완료하기 (일대다 관계에서 다 쪽에 필요한 세팅하는 것)
- 이후 개발과정에서 추가로 역방향 조회가 필요하다 싶으면 그때 추가하면 됨 (mappedBy)
order
실전 예제
- 객체 참조를 사용하도록 변경
// Member
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
// Order
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@OneToMany(mappedBy = "order")
private List<OrderItem> orderItems = new ArrayList<>();
// OrderItem
@ManyToOne
@JoinColumn(name = "ORDER_ID")
private Order order;
@ManyToOne
@JoinColumn(name = "ITEM_ID")
private Item item;
// Item
// 변화없음
출처: 인프런 김영한님 JPA 프로그래밍 - 기본편
'course > inflearn' 카테고리의 다른 글
[JPA 프로그래밍 기본편] 고급 매핑 (0) | 2022.09.06 |
---|---|
[JPA 프로그래밍 기본편] 다양한 연관관계 매핑 (0) | 2022.09.03 |
[JPA 프로그래밍 기본편] 엔티티 매핑 (0) | 2022.08.31 |
[JPA 프로그래밍 기본편] 영속성 관리 (0) | 2022.08.30 |
[JPA 프로그래밍 기본편] JPA 시작하기 (0) | 2022.08.30 |