프로젝트 git url | github.com/rtef23/JpaStudy |
필요 | 1. docker 2. docker-compose |
프로젝트 정보 | mysql : 5.7 - docker image java : 1.8 |
용어 정리
- 방향(Direction) : 단방향, 양방향
- 다중성(Multiplicity) : 다대일(N : 1), 일대다(1 : N), 일대일(1 : 1), 다대다(N : M)
- 연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리 주인이 필요
예시
- Member와 Team의 관계가 N : 1의 경우
- insert 예시
Team team = new Team();
team.setName("team-1");
entityManager.persist(team);
Member member = new Member();
member.setUsername("user-name-1");
member.setTeamId(team.getId());
entityManager.persist(member);
- select 예시
Team findTeam = entityManager.find(Team.class, 1L);
Member findMember = entityManager.persist(Team.class, findTeam.getTeamId());
- 저장시 각 엔티티마다 별도로 저장을 하거나, 조회시 각 엔티티의 식별자로 조회를 해야한다.
- 객체는 참조를 사용를 사용하여 연관된 객체를 찾고 테이블은 외래키로 연관된 테이블을 찾는다는 점에서 차이점이 존재
- => 객체 지향스럽지 않게 된다.
개선 - 단방향 연관관계
@Entity
public class Member {
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
...
}
- select 예시
Member findMember = entityManager.find(Member.class, 1L);
Team findTeam = findMember.getTeam();
//@ManyToOne, @JoinColumn 어노테이션을 통해 team_id로 조인된 Team instance를 가져올 수 있게 된다.
- update 예시
Member member = entityManager.find(Member.class, 1L);
Team newTeam = entityManager.find(Team.class, 2L);
member.setTeam(newTeam);
entityTransaction.commit();
//DB에서 Member 테이블에 team_id 값이 업데이트 되게 된다.
- @ManyToOne annotation
- Member 클래스와 다음의 필드(Team)가 N : 1 관계임을 나타냄
- @JoinColumn
- Member 클래스와 Team 클래스는 team_id 컬럼을 통해서 조인된다는 것을 나타냄
개선 - 양방향 연관관계
- Team을 통해서도 Member를 참조할 수도 있고 Member를 통해서도 Team을 참조할 수 있다.
public class Team {
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
//mappedBy에는 연관 관계의 주인에서 참조한 필드명을 입력해야 한다.
...
}
public class Member {
@ManyToOne
private Team team;
...
}
- 객체에서의 연관 관계
- Member -> Team ; 연관관계 1개(단방향)
- Team -> Member ; 연관관계 1개(단방향)
- 객체에서의 양방향 관계는 서로 다른 단방향 관계 2개이다.
Member member = entityManager.find(Member.class, 1L);
member.getTeam();
Team team = entityManager.find(Team.class, 1L);
team.getMembers();
- 테이블에서의 연관 관계
- Member <-> Team ; 연관관계 1개(양방향), 외래키를 통해서 연관 관계 관리
//MEMBER 테이블을 기준으로 TEAM을 조인하여 조회하는 쿼리
SELECT *
FROM MEMBER M
INNER JOIN TEAM T
ON M.team_id = T.team_id
//TEAM 테이블을 기준으로 MEMBER를 조인하여 조회하는 쿼리
SELECT *
FROM TEAM T
INNER JOIN MEMBER M
ON T.team_id = M.team_id
- Member의 Team을 바꿔야 할 때
- Member 객체의 Team을 바꿔야 할지 아니면 Team에서 Member 리스트를 바꿔야 할지?
- 연관관계의 주인(Owner)
- 객체의 두 단방향 관계중 하나를 연관관계의 주인으로 지정
- 연관 관계의 주인만이 외래키를 관리(등록, 수정)
- 주인이 아닌쪽은 읽기만 가능
- 주인이 아닌 쪽에서 mappedBy 속성으로 주인을 지정한다.
- 주인(Owner) 선정 가이드
- 테이블에서 외래키를 가지고 있는 클래스를 기준으로 주인 선정하는 것을 추천
- 현재 예시에서는 Member 테이블에 team_id인 외래키를 가지고 있기 때문에 Member가 연관관계의 주인으로 선정
- 테이블에서 외래키를 가지고 있는 클래스를 기준으로 주인 선정하는 것을 추천
- 양방향 매핑시 가장 많이 하는 실수
- 연관관계의 주인에서만 외래키를 등록, 수정 하도록 해야한다.
- 문제
Member member = new Member();
member.setUsername("user-1");
entityManager.persist(member);
Team team = new Team();
team.setName("team-1");
team.getMembers().add(member);
entityManager.persist(team);
/*
SELECT * FROM MEMBER;
| MEMBER |
| id | username | team_id |
|----|----------|---------|
| 1 | user-1 | null |
쿼리 실행시 insert로 들어간 member 데이터의 team_id는 null로 들어가 있게 된다.
* 연관관계의 주인은 Member이다.
JPA에서 저장시 @OneToMany의 경우, persist API 실행시 참조하지 않는다.
*/
-
- 개선
Team team = new Team();
team.setName("team-1");
entityManager.persist(team);
Member member = new Member();
member.setUsername("user-1");
member.setTeam(team);
entityManager.persist(member);
/*
SELECT * FROM MEMBER;
| MEMBER |
| id | username | team_id |
|----|----------|---------|
| 1 | user-1 | 1 |
* 연관관계의 주인은 Member이다.
* 문제 없이 실행은 된다.
*/
- 팁
- 양방향 연관관계의 경우 양쪽의 객체에 추가 로직을 넣는 것이 좋다.
- 팁
Team team = new Team();
team.setName("team-1");
entityManager.persist(team);
Member member = new Member();
member.setUsername("user-1");
member.setTeam(team); //**
entityManager.persist(member);
team.getMembers().add(member); //**
/*
team 객체는 영속성 컨텍스트의 1차 캐시에 있기 때문에
다시 DB를 갔다오지 않는 경우(flush(), clear() api를 사용하지 않는 경우),
조회했던 Member 리스트에는 추가한 Member가 존재하지 않기 때문에
위 코드를 추가해주는 것이 좋다.
*/
/*
SELECT * FROM MEMBER;
| MEMBER |
| id | username | team_id |
|----|----------|---------|
| 1 | user-1 | 1 |
* 연관관계의 주인은 Member이다.
*/
- setTeam() 메소드에 양쪽에 객체를 추가하는 로직을 추가하면 좋다.
- 둘 중에 한 곳에서 처리할 수 있도록 하는 것이 좋다.
- 무한 루프를 조심해야한다.([example] toString(), lombok, JSON 라이브러리 ....)
- Controller에서는 엔티티를 반환하지 않도록 하는 것이 좋다. 대신 DTO를 별도로 만들어 리턴하는 것이 좋다.
- setTeam() 메소드에 양쪽에 객체를 추가하는 로직을 추가하면 좋다.
public class Member {
private Team team;
public void setTeam(Team team){
this.team = team;
}
public void changeTeam(Team team){
this.team = team;
team.getMembers().add(this); //**
}
...
}
public class Team {
private List<Member> member;
public void addMember(Member member){
this.member.add(member);
member.setTeam(this);
}
...
}
- 양방향 매핑은 필요한 경우에만 추가하는 것을 추천
'JPA' 카테고리의 다른 글
[JPA] 9. 연관관계 매핑 - 일대다(1 : N) (0) | 2021.01.11 |
---|---|
[JPA] 8. 연관관계 매핑 - 다대일(N : 1) (0) | 2021.01.10 |
[JPA] 6. 엔티티 매핑 (0) | 2021.01.05 |
[JPA] 5. 영속성 컨텍스트 - 2 (0) | 2020.12.31 |
[JPA] 4. 영속성 컨텍스트 - 1 (0) | 2020.12.31 |