
- 연관관계의 필요성
: 연관관계가 왜 필요한지 2가지 예시를 통해 설명
객체 -> 테이블에 맞춘 설계객체 -> 객체지향설계
- 객체와 테이블의 차이
- 참조 vs 외래키
- 양방향 연관관계
- 양방향 연관관계 필요성 & 주의점
[ 테이블에 맞춘 설계 (연관관계 X) ]
Member객체에는teamId라는 필드값이 존재Member와Team간 객체 연관관계는 없다- DB관점에서 보면 외래키가 있어 보이지만, 객체 관점에서는 아무관계도 X
[ 코드 ]
Member객체
Team객체
Member객체에 있는Team에 대한name을 알려면?
Member에 있는teamId를 가져온다teamId로Team테이블에서name을 조회한다
--> 2번의 과정이나 걸림, 만약 객체 참조라면 바로될텐데..
[ 객체지향 설계 (단방향 연관관계) ]
Member테이블에 단순teamId가 아닌Team객체를 참조시킴- 객체 참조로 인해 객체간
단방향 연관관계가 생겼다[ 코드 ]
Member객체
Member객체에Team객체 자체를 참조시킴- 관계는
@ManyToOne--> 여러 멤버가 하나의 팀에 소속될 수 있으니까Member객체에 있는Team에 대한name을 알려면?
Member에 있는Team객체 자체에서name을 조회!
[ 차이점 ]
객체지향 설계를 하면 단순한 value가 아닌 객체 자체를 참조시킨다- 객체 자체를 참조 --> 객체간 연관관계 형성
- 참조하는 연관관계의 데이터를 가져오기가 훨씬 편함!
(테이블에 맞춘 설계라면 쿼리를 한번 더 쏴야함)/* 테이블에 맞춘 설계 */ //팀 저장 Team team = new Team(); team.setName("TeamA"); em.persist(team); //회원 저장 Member member = new Member(); member.setName("member1"); member.setTeamId(team.getId()); em.persist(member); /* 객체 지향 설계 */ //팀 저장 Team team = new Team(); team.setName("TeamA"); em.persist(team); //회원 저장 Member member = new Member(); member.setName("member1"); member.setTeam(team); //단방향 연관관계 설정, 참조 저장 em.persist(member);
[ 설명 ]
- 객체와 테이블은 관계를 맺는 차이가 존재
- 객체는 2개의 단방향으로 양방향을 만들어줘야 하지만.
테이블은FK하나로 양방향 연관관계가 형성된다!
-> 즉, 객체와 테이블간 차이가 있고, 객체에서 양방향을 만들기 위해서는
추가적인 작업이 필요
-->Team객체에서복수의 Member를 저장할 필드 변수가 추가되어야함!
- 객체 연관관계 (총 2개의
단방향 연관관계)
Member->Team연관관계 1개 (단방향)Team->Member연관관계 1개 (단방향)- 테이블 연관관계 (총 1개의
양방향 연관관계)
Member<->Team연관관계 1개 (양방향)
[ 과정 ]
양방향 연관관계를 만들기 위해서는 3가지 작업이 필요
- 추가적인 필드 생성(한쪽만)
연관관계 주인설정연관관계 편의 메소드작성 --> 추후 설명
[ 의문 ]
누구를 주인으로 ?/어떤쪽에 추가필드 생성?
주인: 외래키가 있는 곳을 주인으로 설정 (권장)
(1 : N 관계에서 항상N에 외래키가 생긴다 -->N이 주인)추가필드: 외래키가 없는 곳에 추가필드를 생성
- 이유는 ?
- 만약
Team을연관관계의 주인으로 설정하면Team수정시Member가 수정되는 쿼리가 생김
--> 부자연스럽다(다른 테이블의 쿼리가 날라감;)Member가연관관계 주인이면Member수정시Member수정 쿼리가 생김
--> 자연스럽다
[ 필요 이유 ]
- 객체는 테이블과 달리 기본적으로
단방향 연관관계상태- 객체의
양방향 연관관계를 만들기 위해서 필드변수를 추가해야 한다!
(Listmembers)- 그랬더니, 하나의
DB column값이 객체에 있는2개의 필드에 영향을 주는 상황을 볼 수 있다
--> DB입장에서는Member테이블의FK가 도대체 두 필드중 어떤 값에 의해 변경되어야 하는지 혼돈에 빠지는 것
-->2개의 필드간주인이 되는 필드를 정해서FK가 참조하도록 해야한다- 즉, 우리는 객체간
양방향 연관관계를 만들기 위해연관관계의 주인(Owner)을 만들어야는 이유를 깨달음
[ 설명 ]
- 객체의 두 관계중 하나를
연관관계 주인으로 지정연관관계의 주인만이외래키를 관리(등록,수정)- 주인이 아닌 쪽은
읽기만 가능- 주인이 아닌 쪽에
mappedBy속성을 사용해서주인의 필드지정
[ 코드 ]
Member객체 (단방향때와 변동 X)
Team객체
양방향 연관관계를 해주기 위해 2가지 수행
@OneToMany: 하나의 team에는 여러 member가 들어가기 때문mappedBy = "team"
:연관관계 주인인 Member객체의team필드를 가리킨다
(mappedBy로연관관계 주인을 가리켜줘야 한다)
[ 연관관계 주인 사용시 ]
연관관계 주인이 아닌 쪽에 값을 추가하면 ?
--> 실제 DB에는 원하는 값이 안들어감
(FK는연관관계 주인을 참조해서 변경되기 때문)/* Team과 Member는 양방향 연관관계가 설정되어 있다 */ Team team = new Team(); team.setName("TeamA"); em.persist(team); Member member = new Member(); member.setName("member1"); team.getMembers().add(member); /* 연관관계 주인이 아닌 team에서 memberlist에 member를 추가함 --> DB의 FK는 연관관계 주인을 보고 참조하므로 실제 Member DB에 TeamId는 설정이 안된것으로 간주되어 null이 들어간다 */ em.persist(member);
연관관계 주인에 값을 넣은 올바른 상황/* Team과 Member는 양방향 연관관계가 설정되어 있다 */ Team team = new Team(); team.setName("TeamA"); em.persist(team); Member member = new Member(); member.setName("member1"); member.setTeam(team); /* 연관관계의 주인에 값 설정 --> FK가 바라보는 필드기 때문에 알아서 내부적으로 해당 PK를 빼서 FK값으로 넣어준다 */ em.persist(member);
[ 고민해야할 점 ]
- 위 예제를 보았을때, 우리는 그러면
연관관계 주인이 아닌 값에는 직접 추가를 안해줘도 되는게 확실한지 고민해봐야 한다.Team team = new Team(); team.setName("TeamA"); em.persist(team); Member member = new Member(); member.setName("member1"); /* 이부분 주석 없애면 밑에 iter 출력에 출력이 된다 */ //team.getMembers().add(member); member.setTeam(team); System.out.println("==============="); /* 아직 flush() 되지 않았기 때문에 team.members에는 해당 값이 들어가있지 않다! */ for (Member m : team.getMembers()) { System.out.println("m.getName() = " + m.getName()); } System.out.println("==============="); em.persist(member); tx.commit();
- 물론
연관관계 주인인 member에만 설정하고 등록하면 DB상문제가 없음
--> 하지만, DB에 등록된다는 것은flush()된 이후라는 것
--> 그 전에team.members에는 값이 들어가 있지 않다!
- 즉, 순수한 객체에도 값을 추가해주는 과정이 필요하다!
--> 위처럼team.getMembers().add(member);로 해줘도 되지만,
번거롭기도하고 가시적으로 좋지 않다.
--> 이것을member.setTeam(team)의 내부에서 처리하면 깔끔해짐!
--> 이것이 바로의존관계 편의 메서드
[ 연관관계 편의 메서드 ]
연관관계 주인과순수 객체모두 동기화해주는 편의 메서드set과 같은 함수 이름은 너무관례적이니change로 바꾸어 내부에 로직이 있음을 알림/* Member에 setTeam을 아래로 변경! */ ... public void chanheTeam(Team team) { this.team = team; /* 여기에서 순수 객체에도 추가해주는 코드를 추가!! */ team.getMembers().add(this); } ...사용
Team team = new Team(); team.setName("TeamA"); em.persist(team); Member member = new Member(); member.setName("member1"); //team.getMembers().add(member); member.chanheTeam(team); System.out.println("==============="); /* 아직 flush() 되지 않았기 때문에 team.members에는 해당 값이 들어가있지 않다! */ for (Member m : team.getMembers()) { System.out.println("m.getName() = " + m.getName()); } System.out.println("==============="); em.persist(member); tx.commit();결과
- 순수 객체에도 값이 동기화되어 있다!
[ 필요성 ]
- 양방향 매핑을 하기 위해서 발생하는 비용
- 추가 필드 & 연관관계 편의 메서드 작성
- 연관관계 주인 설정
양방향 연관관계는 사실 객체의 반대 방향으로객체 그래프 탐색기능이 추가된 것
(역방향 탐색)- 필요할 때만 추가적으로 만들어서 사용하는 것이 좋다.(권장)
- 즉, 기본적으로
단방향 연관관계로 만든 후 필요할 때 추가!
[ 주의점 ]
- 순수 객체 상태를 고려해서 반드시
연관관계 메소드작성!무한루프조심! (자주 발생)
toString()
: 객체에서toString()을 생성하면객체 자체를 출력할 수 있게 된다.
이 때,객체 안에 참조 객체를 출력하기 위해 또 그객체에 대한toString()을 출력 --> 계속무한루프가 돈다! -->StackOverFlow 발생JSON생성 라이브러리
:response에 객체를 보낼 때 자동으로 JSON 생성 라이브러리로 해당 객체를
JSON화 하게 된다! 이 때 JSON화 되는 객체안에 또 다른 객체를 JSON화 하게되고 계속 계속 타고 들어간다! (무한루프)
-->Entity를 반환하지 말고,DTO를 사용해서 반환해야한다!