연관관계 매핑 기초
- 객체와 테이블 연관관계의 차이를 이해가 필요
- 객체의 참조와 테이블의 외래키 매핑
- 방향(Direction) : 단방향, 양방향
- 다중성(Multiplicity) : 다대일(N : 1), 일대다 (1 : N), 일대일 (1 : 1), 다대다(N : M)
- 연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리 주인이 필요
연관관계가 필요한 이유
- 객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
- 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
- 객체는 참조를 사용해서 연관객체를 찾는다.
- 테이블과 객체 사이에는 이런 큰 간격이 있다.
단방향 연관 관계
도메인 모델링
@Entity
@Getter @Setter @NoArgsConstructor
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "name")
private String username;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
@Getter @Setter @NoArgsConstructor
public class Team {
@Id @GeneratedValue
@Column(name = "team_id")
private Long id;
@Column(name = "name", length = 50)
private String name;
}
실행코드
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team team = new Team();
team.setName("team A");
em.persist(team);
Member member = new Member();
member.setUsername("bong");
member.setTeam(team);
em.persist(member);
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();
System.out.println("findTeam.name : " + findTeam.getName());
tx.commit();
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
- member.getTeam() 으로 team을 찾을 수 있다.
연관관계수정 실행코드
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team team = new Team();
team.setName("team A");
em.persist(team);
Member member = new Member();
member.setUsername("bong");
member.setTeam(team);
em.persist(member);
Member findMember = em.find(Member.class, member.getId());
System.out.println("findTeam.name : " + findMember.getTeam().getName());
Team newTeam = new Team();
newTeam.setName("updateTeam");
em.persist(newTeam);
findMember.setTeam(newTeam);
System.out.println("update findTeam.name : " + findMember.getTeam().getName());
tx.commit();
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
- 객체만 변경해주면 DB에도 업데이트 된다.
## 양방향 연관관계와 연관관계의 주인
## 도메인 모델링
@Entity
@Getter @Setter @NoArgsConstructor
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@Column(name = "name")
private String username;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
@Getter @Setter @NoArgsConstructor
public class Team {
@Id @GeneratedValue
@Column(name = "team_id")
private Long id;
@Column(name = "name", length = 50)
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
- 연관관계의 주인은 주로 관계의 다수 인쪽에 설정하는것이 좋다.
- 바꿔 말하면 @ManyToOne인쪽이 주인
- mappedBy는 연관관계의 주인이 아닌쪽에 설정한다.
- 바꿔 말하면 @OneToMany인쪽에 mappedBy
테스트 실행에 앞서 알아야 할 내용들이 있다.객체의 양방향 관계
-
객체 연과관계 = 단방향 연관관계 2개
- 회원 -> 팀 연관관계 1개(단방향)
- 팀 -> 회원 연관관계 1개(단방향)
-
객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개이다.
-
객체를 양방향으로 참조하려면 단방향 연관관계 2개를 만들어야 한다.
-
테이블의 양방향 연관관계
-
테이블 연관관계 1개
- 회원 <->팀 연관관계 1개(양방향)
-
테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
-
MEMBER.TEAM_ID 외래 키 하나로 양방향 연관관계 가짐(양쪽으로 조인 가능)
-
외래 키 관리
-
객체의 경우 두개의 단방향 관계이기 때문에 한족에서 외래 키를 관리해야한다.
-
외래키를 관리하는 객체를 연관관계의 주인(Owner)이라고 한다.
한쪽은 읽기만 가능한 가짜 매핑이 될 수 밖에 없다.
연관관계의 주인(Owner)
양방향 매핑 규칙을 기준으로 ..
- 객체의 두 관계중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리(등록, 수정)
- 주인이 아닌쪽은 읽기만 가능
- 주인은 mappedBy 속성 사용 X
- 주인이 아니면 mappedBy 속성으로 주인 지정
- 주인쪽에서 현재 자신으로 매핑된 필드 이름을 입력한다.
누구를 주인으로?
- 외래키가 있는 곳을 주인
- DB에서 foreign key는 다인쪽이 갖는다.
- 즉, 다대일 관계에서 다인쪽이 주인이 되는것이 좋다.(헷갈리지 않음)
- 예) team(1) : member(n) -> 주인 member
주의사항
- 양방향 매핑 시, 연관관계의 주인에 값을 입력 또는 수정해야 DB에 반영된다.
- 주인이 아닌쪽은 읽기만 가능
실행코드
-
연관관계 주인쪽 입력 (소스코드는 아래에 첨부)
// 연관관계 주인쪽 입력
Team team = new Team();
team.setName("team A");
em.persist(team);
Member member = new Member();
member.setUsername("bong");
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
Team findTeam = em.find(Team.class, 1L);
List<Member> members = findTeam.getMembers();
-
주인이 아닌쪽 입력 (소스코드는 아래에 첨부)
- 정상적으로 DB에 저장되지 않는것을 볼 수 있다.
// 주인이 아닌쪽 입력
Team team = new Team();
team.setName("team A");
em.persist(team);
Member member = new Member();
member.setUsername("bong");
team.getMembers.add(member);
em.persist(member);
em.flush();
em.clear();
Team findTeam = em.find(Team.class, 1L);
List<Member> members = findTeam.getMembers();
주의점
-
양방향 매핑시 가장 많이 하는 실수
-
연관관게의 주인에 값을 입력하지 않음
- 연관관계의 주인은 Member이다.
- Team에서만 Member를 지정해주었다.
- DB에는 반영되어지지 않는다.
-
// 연관관게의 주인에 값을 입력하지 않음
Member member = new Member();
member.setUsername("bong");
em.persist(member);
Team team = new Team();
team.setName("team A");
team.getMembers().add(member);
em.persist(team);
em.flush();
em.clear();
양방향 연관관계 주의
-
순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하는것을 추천한다.
-
객체지향적으로 생각을하면 양쪽에 값을 넣는것 맞기 때문에…
-
flush하지 않고 1차캐시 값을 사용할 때, 값이 정확하지 않다.
-
flush() 사용 -> DB에 바로 반영
-
flush() 사용안함 -> 1차 캐시 값 사용
-
-
test케이스 작성 시, JPA를 사용하지 않고 JAVA코드로 작성할 때 값이 정확하지 않다.
-
연관관계 편의 메소드를 생성하자
- 이런식으로 양방향 값을 변경해주는 메소드를 만들어 놓는것이 좋다.
- 양쪽에 이러한 연관관계 설정 메소드가 있으면 헷갈리수 있으니 한쪽에만 만들어 놓는것을 추천한다.
-
public void changeTeam (Team team) {
this.team = team;
team.getMembers().add(this);
}
-
양방향 매핑시에 무한 루프를 조심하자
- toString()
- 양방향 매핑 필드는 제외해야한다.
- lombok
- 양방향 매핑 필드는 제외해야한다.
- JSON생성
- Controller에서 Entity를 직접 반환하는일은 없도록 해야한다.
- DTO사용하여 반환한는 것이 좋다.
- toString()
양방향 매핑 정리
- 단방향 매핑만으로 이미 연관관계 매핑은 설계는 완료가 되야한다.
- 양방향 매핑은 반대 방향으로 조회기능만 추가된것 뿐
- JPQL에서 역방향으로 탐색할 일이 많을때 양방향이 필요.
- 단방향 매핑을 잘 하고 양방향은 필요할 때만 추가 (테이블에 영향을 주지 않음)
'SPRING > 기본 문법' 카테고리의 다른 글
[SPRING] Spring에서 전역 예외 처리하기 (0) | 2021.04.19 |
---|---|
[SPRING] @Configuration과 바이트코드 조작의 마법 (0) | 2021.04.16 |
[SPRING] JPA 엔티티 매핑 (0) | 2021.03.09 |
[SPRING] JPA 플러시 (0) | 2021.03.09 |
[SPRING] JPA 엔티티 등록, 수정, 삭제 (0) | 2021.03.09 |