객체 설계를 테이블 설계에 맞춘다면?
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@Column(name = "TEAM_ID")
private Long teamId;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
}
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
// 회원 저장
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId());
em.persist(member);
// 멤버 id로 멤버 정보 찾기
Member findMember = em.find(Member.class, member.getId());
// 그 결과로 teamId 찾기
Long findTeamId = findMember.getTeamId();
// 찾은 teamId로 team정보 가져오기
Team findTeam = em.find(Team.class, findTeamId);
// 가져온 team정보로 team 이름 알아내기
System.out.println("findTeam : " + findTeam.getName());
- 결론
- 번거로움
- 저장된 teamId를 이용해서 팀 조회 -> 객체지향적인 방법이라고 보기 어려움
- member와 team의 연관관계가 없음
객체지향적인 모델링을 위해 연관관계 매핑
🙆♀️ 객체와 테이블 차이
- 테이블 : 외래 키로 조인을 사용해서 연관된 테이블을 찾음
- 객체 : 참조를 사용해서 연관된 객체를 찾음
- 객체지향적으로 모델링하는 법
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name="MEMBER_ID")
private Long id;
@Column(name="USERNAME")
private String name;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
👀 외래키
- 외래키가 포함된 테이블이 자식 테이블
- 기준이 되는 테이블의 내용을 참조해서 레코드가 입력
@Entity
@Getter @Setter
public class Team {
@Id @GeneratedValue
@Column(name="TEAM_ID")
private Long id;
private String name;
}
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("member1");
// member.setTeamId(team.getId());
// 단방향 연관관계 설정, 참조 저장
member.setTeam(team);
em.persist(member);
// 멤버 id로 멤버 정보 찾기
Member findMember = em.find(Member.class, member.getId());
// 멤버 정보에서 객체 team 찾아냄
Team findTeam = findMember.getTeam();
// team의 이름 찾아냄
System.out.println("findTeamName : " + findTeam.getName());
- 결론
- team객체를 넘겨주면 알아서 teamId를 찾아 FK값으로 쓰고 연관관계를 설정
- team을 getTeam을 이용해서 참조로 가져옴
- 단방향 매핑 후, Team의 Member들을 역참조 : 양방향 객체 연관관계
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name="MEMBER_ID")
private Long id;
@Column(name="USERNAME")
private String name;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
@Entity
@Getter @Setter
public class Team {
@Id @GeneratedValue
@Column(name="TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
}
team.getMembers();
회원 -> 팀
, 팀 -> 회원
연관관계mappedBy
mappedBy
: 내가 누군가에 의해 MAPPING 되었다는 뜻@ManyToOne
을 포함하는 클래스가 주인Member.team
이 연관관계의 주인 member.setTeam(team);
try {
Team team = new Team();
team.setName("TeamA");
em.persist(team); // 영속상태
Member member = new Member();
member.setName("member1");
// 연관관계 생성하는 코드 추가
member.setTeam(team);
em.persist(member);
// 양방향 매핑
Member findSideMember = em.find(Member.class, member.getId());
// 팀은 멤버를 가지고 있고
List<Member> members = findSideMember.getTeam().getMember();
for(Member m : members) {
System.out.println("result = " + m.getName());
}
tx.commit(); // db에 적용되는 자리
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
emf.close();
}
em.persist
뒤에 flush로 DB에 쿼리를 날려주고 캐시를 참고하지 않도록 clear까지 해주면 DB에서 가져올 수 있음 try {
Team team = new Team();
team.setName("TeamA");
em.persist(team); // 영속상태
Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);
// 강제로 DB쿼리를 보고 싶을 때 사용
em.flush();
em.clear();
Member findSideMember = em.find(Member.class, member.getId());
List<Member> members = findSideMember.getTeam().getMember();
for(Member m : members) {
System.out.println("result = " + m.getName());
}
tx.commit(); // db에 적용되는 자리
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
emf.close();
}
team.getMembers().add(member);
추가 public void changeTeam(Team team) {
this.team = team;
// this : 나 자신의 인스턴스를 넣어준다.
team.getMember().add(this);
}
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
@Setter(value = AccessLevel.NONE) // setter생성을 막아줌
private Team team;
/*
* 일반적인 setter의 형태(자바에서의 관례 형태를 벗어났다)가 아니면 이름을 바꿔준다.
* 그럼 추후 소스코드를 봤을때 단순 settet작업이 아닌 중요한 작업을 진행하는지를 파악 할 수 있다.
*/
public void changeTeam(Team team) {
this.team = team;
// this : 나 자신의 인스턴스를 넣어준다.
team.getMember().add(this);
}
public void setTeam(Team team) {
this.team = team;
}
}
@Entity
@Getter @Setter
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> member = new ArrayList<>();
public void addMember(Member member) {
member.setTeam(this);
this.member.add(member);
}
}
try{
Team team = new Team();
team.setName("TeamB");
em.persist(team);
Member member = new Member();
member.setName("member2");
//member.changeTeam(team); // 1안 : member를 기준으로 team을 넣는다.
em.persist(member);
team.addMember(member); // 2안 : team을 기준으로 member를 넣는다.
Team findTeam = em.find(Team.class, team.getId());
List<Member> members = findTeam.getMember();
System.out.println("members = " + findTeam);
for( Member m : members ) {
System.out.println("result = " + m.getName());
}
tx.commit();
}catch (Exception e) {
tx.rollback();
} finally {
em.close();
emf.close();
}
JPQL
에서 양방향으로 탐색할 일이 많음결론
- 객체입장에서 양방향 매핑은 이득이 별로 안되므로 필수가 아님.
- 필요시에 생성하기 (옵션)