일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch14
- 게시글 목록 api
- 스프링 mvc2 - 타임리프
- jpa 활용2 - api 개발 고급
- 타임리프 - 기본기능
- 스프링 db1 - 스프링과 문제 해결
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch12
- 자바의 정석 기초편 ch7
- 스프링 mvc1 - 서블릿
- 2024 정보처리기사 시나공 필기
- @Aspect
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch13
- 자바의 정석 기초편 ch1
- 자바의 정석 기초편 ch2
- 스프링 mvc1 - 스프링 mvc
- 자바의 정석 기초편 ch3
- 자바의 정석 기초편 ch5
- jpa - 객체지향 쿼리 언어
- 자바의 정석 기초편 ch8
- 자바의 정석 기초편 ch9
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch6
- 스프링 입문(무료)
- 자바의 정석 기초편 ch4
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch11
- 2024 정보처리기사 수제비 실기
- Today
- Total
나구리의 개발공부기록
연관관계 매핑 기초, 단방향 연관관계, 양방향 연관관계와 연관관계 주인(기본/주의점 및 정리), 실전 예제 - 연관관계 매핑 시작 본문
연관관계 매핑 기초, 단방향 연관관계, 양방향 연관관계와 연관관계 주인(기본/주의점 및 정리), 실전 예제 - 연관관계 매핑 시작
소소한나구리 2024. 10. 8. 15:36출처 : 인프런 - 자바 ORM 표준 JPA 프로그래밍 - 기본편(유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 연관관계 매핑 기초
** 실전예제 전까지는 DB URL 설정을 test로 설정하여 실습
<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
1) 목표
- 객체와 테이블 연관관계의 차이를 이해
- 객체의 참조와 테이블의 외래 키의 매핑 방법
2) 용어 이해
- 방향(Direction) : 단방향, 양방향
- 다중성(Multiplicity) : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)이해
- 연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리주인이 필요함
3) 연관관계가 필요한 이유
- 객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다 - 조영호(객체지향의 사실과 오해)
- 테이블 하나로 표현하고자 하는 것을 표현할 수 없기 때문에 여러 테이블이나 객체가 연관관계로 묶어서 표현해야 함
- 객체지향스럽게 설계하는 것이 무엇인지 근본적으로 공부하는 책 등을 읽어서 공부하는 것을 권장함
4) 예제 시나리오
- 회원과 팀이 존재
- 회원은 하나의 팀에만 소속될 수 있으며 회원과 팀은 다대일 관계
5) 객체를 테이블에 맞추어 모델링
(1) 연관관계가 없는 객체 설계
- MEMBER 테이블과 TEAM 테이블의 관계에서 외래키인 TEAM_ID가 MEMBER테이블에 있음
- 일대다 관계에서 외래키를 가지고 있는 테이블이 항상 다에 해당함 즉, 여러명의 MEMBER는 1개의 TEAM에 소속될 수 있다는 뜻
(1) Member
@Entity
public class Member {
@Id @GeneratedValue // GeneratedValue 전략을 기본값으로 설정(Auto)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@Column(name = "TEAM_ID")
private Long teamId;
// ... getter, setter
}
(2) Team
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
// ... getter, setter
}
(3) JpaMain - 저장, 조회
- 팀과 회원을 저장해보면 회원 저장시 외래 키 식별자를 직접 다루게 됨
- 회원이 속한 팀을 조회하고자 할 때에도 테이블간의 연관관계가 없다보니 계속 DB에서 값을 가져와야만 조회가 가능함
package jpabook;
public class JpaMain {
public static void main(String[] args) {
// ... 기존 코드 동일
try {
// 팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
// 회원 저장
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId());
em.persist(member);
// 연관관계가 없이 조회
Member findMember = em.find(Member.class, member.getId());
Long findTeamId = findMember.getTeamId();
Team findTeam = em.find(Team.class, findTeamId);
tx.commit();
} catch (Exception e) {
// ... 기존 코드 동일
}
(4) 문제점
- 객체를 테이블에 맞춰 데이터 중심으로 모델링을 하면 협력 관계를 만들 수 없음
- 테이블은 외래 키로 조인을 해서 연관된 테이블을 찾고 객체는 참조를 사용해서 연관된 객체를 찾는 큰 간격이 있음
- 객체 지향적 설계의 이점을 활용하지 못하여 추가 쿼리가 발생하게 되면 성능도 저하되고 코드도 많아져서 유지보수하기가 어려워지며 비즈니스 로직 구현의 복잡성이 증가됨
- 애플리케이션의 규모가 커지먼 커질수록 더욱 복잡해짐
2. 단방향 연관관계
1) 단방향 매핑
- Member에서 TeamId가 아니라 Team 객체를 매핑함
(1) Member 수정
- @ManyToOne 애노테이션으로 Member객체와 Team의 객체가 다대일 관계임을 명시함
- @JoinColumn으로 연관관계 매핑을할 외래키를 지정
// @Column(name = "TEAM_ID")
// private Long teamId;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
// ... 기존 코드들
(2) JpaMain 수정 - 저장, 조회, 수정
- 연관관계 설정으로 참조를 사용하여 회원 저장시 Team 객체의 참조를 저장
- 조회시에도 DB에서 값을 조회하는 것이 아니라 조회한 Member에서 바로 Team 정보를 가져올 수 있게 됨
- 수정을 하고자 할 때에도 팀을 조회할 필요 없이 조회한 회원객체에 새로 입력할 팀 객체의 참조를 입력하면 바로 수정이 됨
- Member에서 Team을 조회할 때 즉시로딩, 지연로딩 설정을 할 수 있는데 해당 설명은 이후에 자세히 설명함
package jpabook;
public class JpaMain {
public static void main(String[] args) {
// ... 기존 코드 동일
// 회원 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); // 단방향 연관관계를 설정하여 참조를 저장할 수 있음
em.persist(member);
// 참조를 사용해서 연관관계를 조회
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();
System.out.println("findTeam.getName() = " + findTeam.getName());
// TeamB를 추가
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
// 조회된 멤버의 팀을 변경
findMember.setTeam(teamB);
Team updateTeam = findMember.getTeam();
System.out.println("findTeam.getName() = " + updateTeam.getName());
tx.commit();
// ... 기존 코드 동일
}
- 출력결과를 보면 참조를 사용해서 멤버의 팀을 조회한 결과와, 새로운 팀을 생성하여 TeamB로 수정한 결과과 정상적으로 반영됨
- Member 조회시 JPA가 영속성 컨텍스트에 저장된 값을 반환하여 DB에서 조회하는 select쿼리가 보이지 않음
- 만약 DB에서 조회하는 쿼리를 보고싶다면 em.flush()와 em.clear()를 사용하여 commit전에 SQL을 즉시 DB에 전송하고 영속성 컨텍스트를 초기화하여 조회단계에서부터 새로운 영속성 컨텍스트로 시작하면 DB에서 값을 조회하는 쿼리를 볼 수 있음
// 회원 저장 후 flush()와 clear()를 호출하여 SQL을 DB에 반영하고 영속성 컨텍스트를 초기화
em.flush();
em.clear();
// 이후에 조회하는 쿼리를 날리면 DB에서 가져오는 쿼리를 확인할 수 있음
3. 양방향 연관관계와 연관관계의 주인 - 기본
1) 양방향 매핑
- 객체를 양방향으로 매핑하려고 보면 테이블의 구조는 다이어그램을 단방향 매핑을 할때와 비교해봐도 변하는 것이 없음
- 테이블의 연관관계는 방향이라는 개념이 없고 외래키로 두 테이블을 조회가 가능한데 객체는 각각 연관관계를 매핑해주어야 서로 탐색이 가능함
- 이 부분이 객체와 테이블의 가장 큰 패러다임의 차이임
(1) Team 수정
- @OneToMany로 일대다 매핑을 적용하여 @Member의 team 필드와 양방향 매핑관계가 적용됨
- 관례상 일대다 매핑을 할때 ArrayList를 사용함(add시 NullPointerException 에러가 발생하지 않기 때문)
- mappedBy로 어떤 필드와 매핑할 것인지 설정 -> 아래에서 자세히 설명
package jpabook.jpashop.domain;
@Entity
public class Team {
// ... 기존 코드 생략
// Member 객체에 일대다 매핑 추가
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
// ... 기존 코드 생략
}
(2) JpaMain 수정
- 양방향 매핑이 적용되어 em.find로 조회한 회원에서 팀을 조회하면 회원이 속한 Team의 정보도 가져올 수 있음
package jpabook;
public class JpaMain {
public static void main(String[] args) {
try {
// ... 기존코드 생략
em.flush();
em.clear();
// 연관관계 매핑을 활용하여 회원이 속한 팀의 멤버들을 조회
Member findMember = em.find(Member.class, member.getId());
List<Member> members = findMember.getTeam().getMembers();
for (Member m : members) {
System.out.println("m = " + m.getName());
}
tx.commit();
// ... 기존코드 생략
}
** 참고
- 실제 설계시 가급적이면 단방향 설계가 좋음
- 양반향 설계는 신경쓸 포인트가 많아져서 구조가 복잡해지고 규모가 커지면 더욱 유지보수하기가 어려움
2) 연관관계의 주인과 mappedBy
(1) mappedBy
- JPA가 어려운 이유중 하나이기에 단번에 이해하기가 어려움
- 중요한 포인트는 객체와 테이블간에 연관관계를 맺는 차이를 이해해야 함
(2) 객체와 테이블이 관계를 맺는 차이
- 객체는 양방향 연관관계를 맺을 때 매핑 포인트가 2개임
- 연관관계를 맺을 엔터티가 각각 서로 참조를 해야함
- 객체에서 양방향 연관관계를 맺었다는 것은 사실 단방향 연관관계가 서로 1번씩 2번 매핑한 것
- 테이블은 외래키 하나로 두테이블의 양방향 연관관계가 성립이 되어 1번의 연관관계로 테이블들의 연관관계가 매핑 됨
- 즉, 테이블에는 방향이 없이 외래키만 있으면 연관관계가 서로 성립된다는 뜻
(3) 딜레마
- 객체가 서로를 단방향 매핑하고 있어 각각 참조하고 있기에 어떤 객체가 하나뿐인 외래키를 매핑해야 되는지 딜레마가 생김
- 일반적으로 접근하면 Member가 Entity고 Entity가 Table로 설정 되었으니 당연히 Member의 team필드만 Team_ID가 매핑되어야 할것같지만 Team객체가 Member의 team을 참조하고 있기 때문에 Member 테이블의 외래키와 매핑이 되어있음
- 극단적으로 객체를 수정해야 한다고 가정하고 두 객체 중 한쪽에만 값이 있거나 둘다 값이 있을 때 외래키는 계속 매핑 주체가 바뀌거나 어디를 매핑의 주체로 수정을 해야할지 혼란스러워짐
- 그래서 두 객체의 연관관계 중 하나를 주인으로 지정하여 외래 키를 관리해야함
3) 연관 관계의 주인(Owner)
(1) 양방향 매핑 규칙
- 객체의 두 관계중 하나를 연관관계를 주인으로 지정해야 함
- 연관관계의 주인만이 외래 키를 관리(등록, 수정)하며 주인이 아닌쪽은 읽기만 가능함
- 주인은 mappedBy속성을 사용하지 않고, 주인이 아닌쪽에 mappedBy( ~ 에게 매핑이 되었다는 뜻) 속성으로 주인을 지정하여 해당 필드에 매핑이 되었다고 설정을 해야함
(2) 누구를 주인으로 해야하는가
- 일대다 관계에서 다(N쪽)를 주인으로 정해야 함, 즉 외래키가 있는 곳을 주인으로 정해야 함
- 테이블의 일대다 관계에서보면 다(N)쪽이 무조건 외래키를 가지고 있는데 이렇게 설정하면 다양한 문제점들을 해결할 수 있고 헷갈리는 경우를 방지할 수 있음
- 이미지의 구조에서 Team을 연관관계의 주인으로 설정해버리면, Team의 Entity를 수정했는데 실제 DB는 MEMBER TABLE에 Update 쿼리가 반영되는 상황이 발생되어 일반적으로 JPA를 다루는 개념으로 접근 했을 때 혼란이 발생할 수 있음
- Member객체가 연관관계의 주인이되면 한번에 MEMBER테이블에 쿼리를 반영하는데 Team객체가 연관관계의 주인이 되면 Team 객체에서 insert 쿼리가 발생한 후 Member 객체에서 Update 쿼리가 생성되어 MEMBER 테이블에 반영되어 추가적인 동작이 발생되어 성능면에서도 문제가 있음
- Member.team이 연관관계의 주인이 되어 진짜 매핑이라고 표현함
- Team.members는 team필드에 매핑이된 대상으로 가짜 매핑으로 표현함
** 참고
- 연관관계의 주인은 표현때문에 비즈니스 적으로 엄청 중요하다고 생각할 수 있는데 비즈니스 적으로는 중요한 것이 아니라 두 테이블, 주 객체와의 관계에서 외래키를 누가 관리할 것이냐가 포인트임
- 간혹 위와 같은 생각으로 접근하여 외래키가 없지만 비즈니스적으로 중요한 객체라고 판단된 곳에 연관관계의 주인을 설정해버려서 문제가 발생하는 경우가 종종 있음
4. 양방향 연관관계와 연관관계의 주인 - 주의점, 정리
1) 양방향 매핑 시 가장 많이 하는 실수
(1) 연관 관계의 주인에 값을 입력하지 않음 - JpaMain 수정
- 연관관계의 주인이 아닌 Team의 members에 값을 집어넣고 실행해보면 쿼리는 2번이 실행되지만 실제 DB를 보면 값이 저장되어있지 않음
public class JpaMain {
public static void main(String[] args) {
// ... 기존 코드 생략
try {
// 저장
// 연관관계 주인
Member member = new Member();
member.setName("member1");
em.persist(member);
// 연관관계 주인이 아님
Team team = new Team();
team.setName("team1");
// 연관관계 주인이 아닌곳에 값을 저장
team.getMembers().add(member);
em.persist(team);
em.flush();
em.clear();
tx.commit();
} catch (Exception e) {
// ... 기존 코드 생략
}
- 연관관계의 주인에 값을 입력하면 정상적으로 DB에 값이 반영됨
- JPA의 관점으로 보면 연관관계의 주인인 Member에만 값을 설정해도 DB에는 정상적으로 반영됨
public class JpaMain {
public static void main(String[] args) {
// ... 기존 코드 생략
try {
// 저장
Team team = new Team();
team.setName("team1");
// 연관관계 주인이 아닌곳에 값을 저장
// team.getMembers().add(member);
em.persist(team);
Member member = new Member();
member.setName("member1");
// 연관관계 주인에 값을 저장
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
tx.commit();
} catch (Exception e) {
// ... 기존 코드 생략
}
2) 객체지향적으로 양방향 연관관계 설계
(1) 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정해야 함
- JPA의 관점으로는 연관관계의 주인쪽에만 값을 입력하면 DB에 정상 반영이되지만 순수한 객체지향적인 관계를 고려해보면 항상 양쪽 값을 입력해야 객체 그래프 탐색을 온전히 할 수 있음
- 위 코드에서 em.flush()와 em.clear()를 제거하고 실행해보면 값이 정상적으로 조회되지 않는것을 확인할 수 있는데, em.find()로 조회하는 것은 DB에서 값을 조회하는 것인데 em.persist()만 호출된 상태에서 em.find()를 호출해서 조회했으므로 영속성 컨텍스트의 1차 캐시에만 값들이 저장되어있기 때문임
- 테스트 케이스 작성 시 JPA없이 작성하는 경우도 많은데 이런 경우에 JPA에서는 정상적으로 값들이 조회되지만 테스트는 통과되지 않는 문제가 발생할 수도 있음
(2) 연관관계 편의 메소드를 생성
- 이렇게 매번 두 연관관계 객체에 값을 입력하면 실수가 발생할 확률이 매우 높기 때문에 메소드를 만들어서 값을 입력하는데 이를 연관관계 편의 메소드라고 부름
- 관습적인 getter, setter를 이용하지말고 엔터티에 별도의 메서드를 만들어서 값을 저장,수정,삭제 등이 발생했을 때 해당 메소드만 호출하여 양쪽의 객체의 상태가 변경되도록 하는 것이 좋음
- Member클래스에 연관관계 편의 메서드를 작성하여 연관관계의 매핑이된 필드가 변경되었을 경우에는 연관관계 편의 메서드를 호출하면 됨
- 연관관계 편의 메서드는 연관관계의 주인과 달리 꼭 외래키쪽에 꼭 둘 필요는 없어서 애플리케이션 상황과 정해진 룰에 따라서 두 객체중 한곳에 만들어 두고 해당 메서드를 사용하도록 공유하면 됨
- 만약 양쪽에 모두 만들면 고려해야할 사항이 더욱 많아지고 잘못 사용하면 무한루프에 빠져 애플리케이션이 종료될 수 있으므로 둘 중 한곳을 정해서 만드는 것을 권장함
@Entity
public class Member {
// ... 기존 코드들 생략
// Member 클래스에 연관관계 편의 메서드 생성
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
public class JpaMain {
public static void main(String[] args) {
// ... 기존 코드들 생략
Member member = new Member();
member.setName("member1");
// 연관관계 편의 메소드를 호출하여 객체의 상태를 양쪽에 한번에 저장
member.changeTeam(team);
em.persist(member);
// em.flush();
// em.clear();
// ... 기존 코드들 생략
}
/*
연관관계 편의 메서드는 외래키가 있는 곳에 둘 필요는 없으므로 상황에 따라 원하는 곳에
연관관계 편의 메서드를 만들어서 사용하면 됨
둘다 만들경우 잘못 사용하면 무한 루프에 빠질수도 있고 고민해야할 부분이 많아지므로 한곳에만 만들어서 사용
*/
@Entity
public class Team {
// ... 기존 코드들 생략
public void changeMembers(Member member) {
member.setTeam(this);
members.add(member);
}
}
(3) 양방향 매핑시에는 무한 루프를 조심해야함
- 양방향 매핑이 된 객체들에 lombok 라이브러리 등을 사용하여 @ToString을 사용하거나 IDE의 toString() 생성기능을 활용하여 toString()를 생성하고 해당 객체를 사용하게 되면 서로의 toString에서 양쪽의 객체를 계속 조회하게 되어 무한루프에 빠져 스택오버플로우에러가 발생하고 프로그램이 종료가 됨
- JSON 생성 라이브러리를 사용할 때도 엔터티를 JSON으로 변환하는 순간 무한 루프에 빠져버리게 되는데, 이를 방지하기 위하여 컨트롤러에서는 절대 엔터티를 직접 반환하지 말고 DTO를 사용해야 함
** 참고
- 컨트롤러에서 엔터티를 사용하게되면 무한 루프 외에 또다른 문제가 발생함
- JSON으로 변환하는 이유가 API 통신을 하기 위하여 JSON 스펙을 정의하고 API를 사용하는 쪽에서 해당 스펙에 맞춰서 데이터를 요청함
- 그러나 비즈니스 요구사항이 변경되는 등의 이유로 엔터티는 변경이 될 수 있는데 이렇게 되면 JSON 스펙이 변경되어 버리기 때문에 서로 통신이 안되는 문제가 발생하기 때문에 컨트롤러에서는 꼭 직접 엔터티를 사용하지말고 DTO를 통해서 필요한 것만 사용해야 함
2) 양방향 매핑 정리
(1) 단방향 매핑만으로도 이미 연관관계 매핑은 완료된 것임
- 양방향 매핑은 반대방향으로 조회(객체 그래프 탐색) 기능이 추가된 것일 뿐임
(2) 처음 설계시에는 단방향 매핑으로만 설계
- JPA 모델링을 할때는 가급적 단방향 매핑으로 설계를 끝내는 것을 권장함
- 실제 실무에 들어가면 테이블이 적으면 20개 많으면 100개 이상이 될 수도 있는데 처음부터 양방향 관계를 고려해서 설계를 하면 객체 관점에서는 고려해야할 부분만 많아지기 때문임
- JPA를 사용하기로 결정 되었다면 우선 단방향 매핑으로만 테이블설계와 객체설계를 동시에 진행하면 테이블 관계에서 대략적인 외래키가 나오게 되고 일대다, 일대일 관계도 나오게 됨
- @ManyToOne으로 단방향 매핑만 적용해두고 초기 설계를 진행
(3) 실무에서는 역방향으로 탐색할 일이 많음 - 양방향 관계 발생
- 실무에서는 JPQL 쿼리 작성시 역방향으로 탐색하는 경우가 자주 발생함
- 단방향 매핑을 전부 해두었으니 mappedBy를 자바 코드에 추가만하면 양방향 매핑을 비교적 간단하게 추가할 수 있음
- 단방향 매핑이 되어있는 곳에서 양방향 매핑을 추가한다고 하여 테이블에 영향을 주는 것이 아니기 때문에 단방향 설계로 먼저 진행 후 양방향 관계 설정이 필요하면 그때 추가하는 것을 권장함
(4) 연관관계의 주인은 외래키의 위치를 기준으로 정해야 하고 연관관계 편의 메서드는 둘 중 아무곳에 정의해도 상관없음
** jdbc.url 설정을 jpashop으로 변경해서 진행
<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/jpashop"/>
5. 실전 예제 - 연관관계 매핑 시작
1) 객체 구조 수정
- 지난번 글에서 진행한 테이블(데이터)을 기반으로 작성한 코드로 연관관계 매핑을 적용하여 실습
- 테이블 구조는 동일하며 객체 구조에서 외래키가 아닌 참조를 사용하도록 변경함
2) 단방향으로 먼저 설계
- 위에서 설명했듯이 테이블과 객체의 구조를 보고 외래키와 일대다 관계를 보고 먼저 단방향으로만 매핑을 진행
(1) Order 수정
- 기존에 설정했던 MemberId필드를 제거하고 Member객체를 연관관계로 설정
- Member와 Order는 1대다 관계이고 Order가 외래키를 가지고 있으므로 Order에 @ManyToOne으로 설정하고 @JoinColumn(name = "MEMBER_ID")로 외래키를 지정
package jpabook.jpashop.domain;
@Entity
@Table(name = "ORDERS")
public class Order {
// ... memberId를 제외한 기존 코드 동일
// Member를 연관관계로 설정
@ManyToOne()
@JoinColumn(name = "MEMBER_ID")
private Member member;
// ... getter, setter
}
(2) OrderItem 수정
- orderId와 itemId 필드를 제거하고 Order와 Item을 연관관계로 설정
- 외래키를 모두 OrderItem을 가지고 있고 연관관계로 설정한 객체들과 모두 다대일 관계이므로 @ManyToOne과 @JoinColumn을 설정
package jpabook.jpashop.domain;
@Entity
@Table(name = "ORDER_ITEM")
public class OrderItem {
// ... orderId, itemId를 제외한 기존 코드 동일
@ManyToOne
@JoinColumn(name = "ORDER_ID")
private Order order;
@ManyToOne
@JoinColumn(name = "ITEM_ID")
private Item item;
// ... getter, setter
}
3) 양방향 관계 설정
- 애플리케이션 개발 중 멤버 입장에서 주문 목록이 중요해졌다고 가정
** 참고
- 지금은 예제이기 때문에 위와 같은 상황을 가정하여 양방향 관계를 설정하지만 대부분의 실무에서는 Member가 orders를 가지도록 설계하지 않고 설계할 필요가 없음
- 특정 회원의 주문을 보고싶다고 쿼리를 한다고 가정을 하면 Order가 가지고 있는 member_id외래키를 이미 가지고 있기 때문에 바로 해결이 가능함
- Member를 조회해서 해당 주문정보를 찾도록 설계하는 것은 관심사를 적절하게 끊어내지 못하고 복잡하게 설계가 되어 근본적으로 설계가 잘못 되었다고 볼 수도 있음
- 회원은 회원만 가지고있고 주문은 주문만 가지도록 객체 관점으로 설계하는 것이 중요함
- Order에서 OrderItem을 가지고있는 경우는 종종 발생하긴하지만 이 경우도 양방향 연관관계 설정 없이 개발이 가능함
(1) Member - Order 양방향 연관관계 설정
- Member와 Order는 일대다 관계이므로 @OneToMany로 연관관계를 설정 - readOnly 매핑이 됨
- 연관관계의 주인을 외래키를 가지고 있는 Order의 member필드로 설정
@Entity
public class Member {
// 기존 코드 생략
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
(2) OrderItem - Order 양방향 연관관계 설정
- @OneToMany를 활용하여 일대다 연관관계 매핑을 설정하고 연관관계의 주인을 OrderItem의 order필드로 설정
@Entity
@Table(name = "ORDERS") // 주문은 ORDERS로 관례상 많이 씀
public class Order {
//... 기존 코드 생략
@OneToMany(mappedBy = "order")
private List<OrderItem> orderItems = new ArrayList<>();
}
(3) 연관관계 메서드 예시코드
- 새로운 주문이 생성되면 OderItem도 함께 생성되도록 Order에 연관관계 메서드를 작성
- 실무에서는 연관관계 메서드를 생성할 때 더 고려해야할 상황이 많지만 간단하게 적용할때에는 이정도로도 괜찮으며 외래키 소유 여부와 관계없이 비즈니스 상황등을 고려해서 Order에 연관관계 메서드를 생성한 모습
public class JpaMain {
public static void main(String[] args) {
// ... 기존 코드 생략
// 새로운 주문이 생성되었다고 가정
Order order = new Order();
order.addOrderItem(new OrderItem());
tx.commit();
}
// OrderItem과 Order의 연관관계 편의 메서드 생성
public class Order {
// ... 기존 코드 생략
public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.setOrder(this);
}
}
4) 실무에서 양방향 관계 설정
- 단방향 설계만 잘해두면 대부분의 문제는 코드로 해결할 수 있음
- 양방향 관계는 개발상의 편의를 위해 설계하는 경우가 많음
- 실무에서는 JPQL 쿼리를 자주 사용하게 되는데 JPQL 쿼리를 사용하다보면 조회를 좀 더 편하게 하기 위하여 양방향 관계를 고려하는 경우가 발생함
- 좀 더 객체지향적으로 개발해야 하거나 비즈니스 로직상으로 양방향 관계를 설계해야만 하는 상황일 때 선택해서 양방향 관계를 적용하면 됨
'인프런 - 스프링부트와 JPA실무 로드맵 > 자바 ORM 표준 JPA 프로그래밍 - 기본편' 카테고리의 다른 글
고급 매핑, 상속관계 매핑, Mapped Superclass - 매핑 정보 상속, 실전 예제 - 상속관계 매핑 (1) | 2024.10.11 |
---|---|
다양한 연관관계 매핑, 다대일[N:1], 일대다[1:N], 일대일[1:1], 다대다[N:M] 실전 예제 - 다양한 연관관계 매핑 (3) | 2024.10.10 |
엔터티 매핑, 객체와 테이블 매핑, 데이터베이스 스키마 자동 생성, 필드와 컬럼 매핑, 기본 키 매핑, 실전 예제1 - 요구사항 분석과 기본 매핑 (1) | 2024.10.07 |
영속성 관리 - 내부 동작 방식, 영속성 컨텍스트, 플러시, 준영속 상태 (2) | 2024.10.05 |
JPA 소개, SQL 중심적인 개발의 문제점, JPA 소개, JPA 시작하기, 프로젝트 생성, 애플리케이션 개발 (1) | 2024.10.04 |