연관관계 매핑
테이블에서는 외래키만을 통해 연관 테이블을 찾는다. 즉, 외래키만으로 양방향의 연관관계를 갖는다. (사실 테이블에는 방향 개념이 존재하지 않는다.)
그러나 객체에서는 참조를 통해서 연관 객체를 찾는다. 따라서 하나의 참조는 단방향만의 관계를 갖는다.
이와 같이 테이블과 객체는 연관관계에서의 차이점이 분명하다.
이러한 차이점을 최소화 시켜주는 것이 JPA에서의 연관관계 매핑이다.
연관관계 매핑은 객체의 참조와 테이블의 외래키를 매핑하는 것이다.
연관관계를 맺어주지 않는다면 객체의 참조가 아닌 테이블에서의 외래키를 그대로 사용한다. 이때는 외래키 식별자를 직접 다루기 때문에 객체지향스럽지 못하다. 또한 조회에서도 마찬가지로 연관관계가 없으므로 계속 DB에서 끄집어 내야 한다. 따라서 연관관계 매핑이 필요하다.
용어
- 방향 : 단방향, 양방향
- 다중성 : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)
- 연관관계의 주인 : 객체의 양방향 연관관계는 데이터 조회 뿐 아니라 관리가 가능한 주인이 필요
예시 테이블, 엔티티
- 객체 (엔티티)
// Member 엔티티
@Entity
public class Member{
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
// 아래에서 Team에 대한 객체 참조 추가
}
// Team 엔티티
@Entity
public class Team{
@Id @GeneratedValue
private Long id;
private String name;
}
- 테이블
단방향 연관관계
객체에서의 "참조" 와 테이블에서의 "외래키" 를 매핑 !
// Member 엔티티
@ManyToOne // 다대일 관계
@JoinColumn(name="TEAM_ID") // 참조와 외래키(TEAM_ID)를 매핑
private Team team;
데이터 저장
// 팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
// 회원 저장
Member member = new Member();
member.setName("User1");
member.setTeam(team); //단방향 연관관계 설정, 참조 저장
// 단방향 연관관계가 없었다면 아래와 같이 외래키 식별자를 직접 넣어줘야 함
// member.setTeam(team.getId());
em.persist(member);
데이터 조회 (객체 그래프 탐색)
// 조회
Member findMember = em.find(Member.class, member.getId());
// 참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();
데이터 수정
// 새로운 팀 저장
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
// User1을 팀 B로 수정
member.setTeam(teamB);
양방향 연관관계
양방향 연관관계 : 객체 양쪽으로 참조해서 갈 수 있는 관계
단방향 연관관계 2개를 동시에 설정함으로써 구현
// Member 엔티티 (단방향 연관관계와 동일)
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
// Team 엔티티
@OneToMany(mappedBy = "team") // 팀 입장에서는 1대다 관계
List<Member> members = new ArrayList<Member>(); // 컬렉션 추가 (new ArrayList<>()로 초기화가 관례)
// 초기화 이유 : add할 때 NullPointerException 발생하지 않게!
데이터 조회
// 팀 조회
Team findTeam = em.find(Team.class, team.getId());
// 팀으로 멤버 조회 (역방향 조회)
List<Member> findMembers = team.getMembers();
mappedBy = "team"
- Member 클래스에서 team(변수명) 과 연결되어 있다는 뜻 (team으로 매핑되어 있다는 뜻)
- Member 클래스의 team이 연관관계 주인 !
- mappedBy가 적힌 곳은 읽기만 가능 (값을 저장해도 아무 일 발생하지 않음, 조회만 가능)
연관관계 주인
위의 예시에서 예를 들면 테이블의 외래키 (TEAM_ID) 를 관리해야 할 하나의 참조가 필요하다.
따라서 객체의 두 관계 중 하나를 연관관계의 주인으로 지정해야 한다.
즉, Member 엔티티의 team이 관리할 것인지, Team 엔티티의 members 가 관리할 것인지 정해야 한다는 것이다.
만약 둘 다 관리하게 되면 데이터적인 오류가 발생할 수 있기 때문이다.
외래키가 있는 곳을 연관관계의 주인으로 지정한다.
- 연관관계의 주인만이 외래키를 관리(등록, 수정)
- 주인이 아닌쪽은 데이터 조회만 가능 (값 입력 시 연관관계의 주인에 값을 입력해야 반영된다!)
- 주인이 아닌쪽은 mappedBy 속성으로 주인 지정
연관관계 편의 메서드
연관관계의 주인에 값을 입력하라고 했지만 아래와 같이 값 설정은 양쪽에 다 설정해야 한다.
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("userA");
member.setTeam(team); // 연관관계의 주인에 값을 저장하는게 맞음!
team.getMembers().add(member); // 객체지향적 => 양쪽에 값 설정해줘야 함!
em.persist(member);
양쪽에 모두 값 설정해야 하는 이유
Team findTeam = em.find(Team.class, team.getId());
List<Member> members = findTeam.getMembers();
위와 같이 팀에 소속된 멤버들을 조회할 때,
em.flush(); em.clear(); 가 없다면 1차 캐시에만 저장되어 있기 때문에 members를 출력해도 빈 리스트가 나올 수 있기 때문
또한 테스트 케이스 작성 시 JPA를 거치치 않게끔 작성하는 경우도 존재하게 되는데 이때는 에러가 발생 가능하기 때문
member.setTeam(team);
team.getMembers().add(member);
위와 같이 값 설정 위해서 각각 메서드를 호출할 필요 없이
연관관계 편의 메서드를 작성해서 원자적으로 하나만 호출(입력)해도 자동으로 모두 값이 설정되게끔 한다.
// Member 엔티티
public void changeTeam(Team team){ // 연관관계 편의 메서드 예시
this.team = team;
team.getMembers().add(this); // 반대편에도 값 설정 추가 !
}
'Spring Boot > JPA' 카테고리의 다른 글
[JPA] 연관관계 매핑 - 상속관계 매핑, @MappedSuperclass (1) | 2023.12.18 |
---|---|
[JPA] 연관관계 매핑 - 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M) (0) | 2023.12.18 |
[JPA] 엔티티 매핑 관련 annotation 정리 (0) | 2023.12.14 |
[JPA] 영속성 컨텍스트, 엔티티 매니저 CRUD (0) | 2023.12.13 |
[JPA] 엔티티 매니저 팩토리, 엔티티 매니저 (0) | 2023.12.13 |