출처 : https://www.inflearn.com/course/ORM-JPA-Basic
자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 - 인프런
현업에서 실제로 JPA로 개발을 하고 있습니다. 그런 입장에서보면 지금 작성하고 있는 코드들이 어떻게 작동하는지 이해하는데 큰 도움을 주는 강의입니다. 다음은 제가 느낀 이 강의의 장점들
www.inflearn.com
수정중
1. 연관관계가 필요한 이유
예제 시나리오
회원과 팀이 있다.
회원은 하나의 팀에만 소속될 수 있다.
회원과 팀은 다대일 관계다.
회원(N) : 팀(1)
객체를 테이블에 맞추어 모델링 (참조 대산에 외래 키를 그대로 사용)
Member 클래스
@Entity
public class Member{
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@Column(name = "TEAM_ID")
private Long teamId;
...
}
Team 클래스
@Entity
public class Team{
@Id @GeneratedValue
private Long id;
private String name;
...
}
JPA 클래스
저장
// 팀을 영속성 컨텍스트에 저장
Team tema = new Team();
team.setName("TeamA");
em.persist(team);
// 회원을 영속성 컨텍스트에 저장
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId()); // member를 team에 소속 시킴
em.persist(member);
조회
Member findMember = em.find(Member.class, member.getId());
Long findTeamId = findMember.getTemaId();
Team findTeam = em.find(Team.class, findTeamId);
멤버의 팀 id를 가져오려면 멤버를 조회한 후 가져온 멤버 id를 통해 team을 조회해야 team의 정보를 가져올 수 있다.
=> 객체를 테이블에 맞추어 데이터 중심으로 모델링하면 협력 관계를 만들 수 없다.
테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
객체는 참조를 사용해서 연관된 객체를 찾는다.
=> 테이블과 객체 사이에는 큰 간극이 있다.
- 단방향 연관관계
객체 지향 모델링은
Member에 team_id가 아니라 Team team이 들어가야 함.
Member |
id Team team username |
Member 클래스
@Entity
public class Member{
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
// Long team id가 아니라 Team을 저장
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
일대다(N:1) 관계에서 N의 입장인 클래스는 @ManyToOne를 사용해서 내가 N이라는 것을 JPA에게 알려야 한다.
그리고 어떤 컬럼을 사용해서 조인하는지 매핑해야함 @JoinColumn 을 사용하고 name에 조인하는 컬럼 이름 작성.
이렇게만 하면 JPA가 연관관계를 보고 알아서 해줌.
@ManyToOne(fetch = FetchType.LAZY) db에서 데이터를 가져올 때 조인해서 가져오지 않고 따로 select함. (지연로딩, 추후에 다시 설명)
저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // Member의 Team을 저장할 때 Team 객체를 그대로 저장하면 된다.
em.persist(member);
조회
Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();
멤버의 팀 정보를 찾고 싶을 때 member의 id로 team의 id를 가져온 후 team의 정보를 가져올 필요없이
member.getTeam()으로 팀 정보를 가져올 수 있다!
member의 팀 변경
// 멤버 가져옴
Member findMember = em.find(Member.class, member.getId());
// 기본키가 2인 팀 가져옴
Team newTeam = em.find(Team.class, primaryKey:2L);
// 멤버에 새로운 팀 저장
findMember.setTeam(newTeam);
setTeam으로 team만 변경해도 foreign key가 변경됨.
- 양방향 연관관계와 연관관계의 주인
양방향 매핑 : 반대 방향으로 객체 그래프 탐색
[양방향 객체 연관관계]
Member <-----------> Team
id * 0..1 id
Team team name
username List members
테이블의 연관관계는 조인하면 Member와 Team 양쪽에서 서로의 정보를다 알 수 있는데
객체의 연관관계에서는 Team에서 Member의 정보를 알 수 없다. Team에서 Member를 알려면 List<> member를 추가해야함.
Team class
@Entity
public class Team{
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
...
}
@OneToMany : N:1 연관관계에서 1이라는 뜻
mappedBy : 함께 연관되어 있는 변수 이름 (Member 클래스에서 @ManyToOne으로 연관관계 매핑 되어 있는 변수 team)
Jpa Class
// 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setTeam(team);
em.persist(member);
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);
}
tx.commit();
연관관계의 주인 (mappedBy를 사용하는 이유)
객체와 테이블의 관계 차이
- 객체 연관관계 = 2개
회원 -> 팀 연관관계 (단방향)
팀 -> 회원 연관관계 (단방향)
= 객체의 양방향 관계는 서로 다른 단방향 관계 2개다.
객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
A -> B (a.getB())
B -> A(b.getA())
- 테이블 연관관계 = 1개
회원 <-> 팀 연관관계 (양방향)
= 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
MEMBER.TEAM_ID 외래 키 하나로 조인해서 양쪽의 정보를 알 수 있다.
SELECT *
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
SELECT *
FROM TEAM T
JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID
이때 발생할 수 있는 문제가 객체에서 양방향 연관관계를 위해 참조를 강제로 두개로 변경했고
만약 Member가 team을 변경했다면 Member의 Team을 변경해야 할지 Team의 members를 변경해야 할지 알 수 없다.
그래서 둘 중 하나로만 외래 키를 관리해야한다.
양방향 매핑 규칙
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리(등록, 수정)
- 주인이 아닌 쪽은 읽기만 가능
- 주인은 mappedBy 속성 사용하지 않음.
- 주인이 아니면 mappedBy 속성으로 주인을 지정.
주인을 지정할 때는 외래키와 매핑되어 있는 컬럼을 선택하는 게 좋다.
MEMBER 테이블에 외래키로 TEAM_ID가 들어있으니 Member 클래스의 Team team을 주인으로 선택하는 것을 추천한다고 하심.
(만약 Team에 List members를 주인으로 지정했을 때 잘못되면 Team을 변경했는데 Member가 변경될 수도 있고
Team에 member를 저장할 때 team은 insert 쿼리가 실행되고 member는 update 쿼리가 실행돼서 성능 저하 발생함.)
양방향 매핑시 주의할 점
1. 연관관계 주인의 값을 입력하지 않음
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 테이블의 TEAM_ID 컬럼의 값이 null이 됨.
양방향 매핑 시 연관관계 주인에 값을 입력해야 한다. 양쪽 다 입력하는게 좋음
잊어버리지 않기 위해 Member 클래스에 setTeam 메소드에 team에 member를 입력하는 코드를 작성해놓자 (연관관계 편의 메서드라고 부른다고 하심)
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
2. 양방향 매핑시에 무한 루프를 조심하자 (toString(), lombok, JSON 생성 라이브러리)
lombok, toString 사용시 주의
컨트롤러에서 Entity를 Dto로 변환해서 반환할 것
단방향 매핑으로 설계하고 필요하다면 양방향 매핑을 추가할 것
'코딩 > JPA' 카테고리의 다른 글
[JPA] 상속관계 매핑 (0) | 2023.12.04 |
---|---|
[JPA] 다양한 연관관계 매핑 (0) | 2023.12.04 |
[JPA] JPA의 엔티티 매핑 (0) | 2023.12.02 |
[JPA] JPA 실습 (0) | 2023.12.01 |
[JPA] JPA (0) | 2023.11.28 |