- 연관관계의 필요성
: 연관관계가 왜 필요한지 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
를 사용해서 반환해야한다!