✅ 핵심 키워드
- 방향:
단방향 - 한 쪽만 참조 / 객체에서만 존재
양방향 - 양쪽 모두 서로 참조 / 테이블 관계는 항상 양방향
- 다중성:
다대일, 일대다, 일대일, 다대다
- 연관관계의 주인:
양방향 연관관계 설정 시 주인을 정해야 함
5.1 단방향 연관관계
회원과 팀 관계를 통해, 다대일 단방향 관계 알아보기
- 회원과 팀이 있음
- 회원은 하나의 팀에만 소속됨
- 회원과 팀은 다대일 관계임
객체 연관관계
- 회원 객체가 Member.team 필드로 팀 객체와 연관관계를 맺습니다.
- 단방향 관계 : 회원이 team 필드를 통해 팀을 알 수 있으나, 팀에서는 회원을 알 수 없습니다.
테이블 연관관계
- 회원 테이블이 TEAM_ID 외래키로 팀 테이블과 연관관계를 맺습니다.
- 양방향 관계 : 회원과 팀이 둘 다 조인 가능
객체 연관관계와 테이블 연관관계의 가장 큰 차이
- 객체에서 양방향을 만들기 위해서는 서로 다른 단방향 2개를 만들어야 합니다.
- 테이블은 원래 양방향으로 조인할 수 있습니다.
// 단방향
class A {
B b;
}
class B {}
// 양방향
class A {
B b;
}
class B {
A a;
}
객체 연관관계 vs 테이블 연관관계 정리
- 객체는 참조로 단방향 연관관계를 맺습니다.
(양방향을 만들기 위해서는 서로 다른 방향으로 단방향 연관관계 2개 필요함)
- 테이블은 외래 키로 양방향 연관관계를 맺습니다.
5.1.1 순수한 객체 연관관계
public static void main(String[] args){
Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");
Team team1 = new Team("team1", "팀1");
member1.setTeam(team1);
member2.setTeam(team2);
Team findTeam = member1.getTeam();
}
멤버1, 멤버2, 팀을 생성했고 멤버들을 team1에 속하도록 연관관계를 맺었습니다.
그리고 마지막으로 member1이 속해있는 팀을 조회했습니다.
✅ 객체 그래프 탐색 : 객체가 참조를 사용해서 연관관계를 탐색
5.1.2 테이블 연관관계
ALTER TABLE MEMBER ADD CONSTRAINT FK_MEMBER_TEAM
FOREIGN KEY (TEAM_ID)
REFERENCES TEAM
위와 같이 외래키로 지정을 해줍니다.
INSERT INTO TEAM(TEAM_ID, NAME) VALUES ('team1', '팀1);
INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME) VALUES ('member1', 'team1', '회원1');
객체를 생성해줍니다.
그리고 회원1이 소속된 팀을 조회할 수 있습니다.
SELECT T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
WHERE M.MEMBER_ID = 'member1'
✅ 조인 : 외래키를 사용해서 연관관계 탐색
5.1.3 객체 관계 매핑
JPA를 사용해서 매핑하는 방법을 알아보겠습니다.
@Entity
public class Member {
@Id
@Column(name="MEMBER_ID")
private String id;
private String username;
@ManyToOne
@JoinColumn(name="TEAM_ID)
private Team team;
public void setTeam(Team team){
this.team = team;
}
...
}
@Entity
public class Team{
@Id
@Column(name="TEAM_ID")
private String id;
private String name;
}
✅ 회원 엔티티에서 연관관계 매핑
- @ManyToOne : 다대열 관계라는 매핑 정보
- @JoinColumn(name="TEAM_ID") : 외래 키를 매핑할 때 사용할 조인컬럼 사용
(name에는 매핑할 외래 키 이름 지정, 생략 가능)
5.1.4 @JoinColumn
✅name
- 기능 : 매핑할 외래 키 이름
- 기본값 : 필드명 + _ + 참조하는 테이블의 기본 키 컬럼명
✅ referencedColumnName
- 기능 : 외래 키가 참조하는 대상 테이블의 컬럼명
- 기본값 : 참조하는 테이블의 기본 키 컬럼명
✅ foreignKey
- 기능 : 외래 키 제약조건을 직접 지정할 수 있음 ( 테이블 생성 시에만 )
기타)
unique, nullable, insertable, updatable, columnDefinition, table
📖 @JoinColumn 생략 시에는 어떻게 될까?
@ManyToOne
private Book book; 일 때, book_BOOK_ID 외래 키를 사용하게 됨.
5.1.5 @ManyToOne
✅ optional
- 기능 : false 설정 시 연관된 엔티티가 항상 있어야 함
- 기본 값 : true
✅fetch
- 기능 : 글로벌 페치 전략 설정
(@ManyToOne = FetchType.EAGER, @OneToMany=FetchType.LAZY)
✅ targetEntity
- 기능 : 연관된 엔티티의 타입 정보를 설정 (거의 사용 안함)
5.2 연관관계 사용
5.2.1 저장
public void testSave(){
Team team1 = new Team("team1"> "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1);
em.persist(member1);
Member member2 = new Member("member2", "회원2");
member2.setTeam(team1);
em.persist(member2);
}
코드를 보면 team1을 생성하고 회원엔티티가 팀 엔티티를 참조하고 저장된 모습을 볼 수 있다.
이 때 JPA는 참조한 팀의 식별자를 외래 키로 사용해서 등록 쿼리를 만든다.
5.2.2 조회
연관관계가 있는 엔티티를 조회하는 방법을 알아봅시다.
✅객체 그래프 탐색 ( 객체 연관관계를 사용한 조회 )
: 객체를 통해 연관된 엔티티를 조회하는 것
ex : member.getTeam()을 통해 member와 연관된 team 엔티티를 조회할 수 있습니다.
✅객체 지향 쿼리 사용 (JPQL)
private static void queryLogicJoin(EntityManager em){
String jpql = "select m from Member m join m.team t where " + "t.name=:teamName";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setParameter("teamName". "팀1")
.getResultList();
for (Member member : resultList){
System.out.println("[query] member.username=" + member.getUsername());
}
}
SQL처럼 조인을 통해서 팀1에 속한 회원을 조회하였습니다.
(:으로 시작되는 것은 파라미터를 바인딩할 수 있음)
JPQL로 조회 시 SQL 보다 간결하며 객체를 대상으로 하고 있다는 점이 다릅니다!
5.2.3 수정
private static void updateRelation(EntityManager em){
Team team2 = new Team("team2", "팀2");
em.persist(team2);
Member member = em.find(Member.class, "member1");
member.setTeam(team2);
}
위와 같은 코드를 실행하게 되면 SQL에서 update 쿼리를 실행하게 됩니다.
이 때에는 엔티티의 값/연관관계를 변경하면 트랜잭션을 커밋할 때 플러시가 일어나므로 별도의 저장이 필요없이
변경 사항이 자동으로 반영됩니다.
5.2.4 연관관계 제거
private static void deleteRelation(EntityManager em){
Member member1 = em.find(Member.class, "member1");
member1.setTeam(null);
}
null 로 값을 변경하여 연관관계를 변경하주면 제거가 됩니다.
SQL에서도 null 값으로 변경해주는 update 쿼리가 실행됩니다.
5.2.5 연관된 엔티티 삭제
연관관계를 제거 한 다음에, 연관된 엔티티를 삭제합니다.
순서를 반대로 한다면 외래 키 제약조건 때문에 데이터베이스 상에서 오류가 발생합니다.
member1.setTeam(null);
member2.setTeam(null);
em.remove(team);
5.3 양방향 연관관계
이제까지는 회원에서 팀으로 접근하는 단방향 연관관계를 알아보았으나
이제 팀에서 회원으로 접근할 수 있게 하는 관계를 추가하여
양방향 연관관계로 매핑해볼 것입니다.
이전에는 다대일관계였는데
일대다관계를 사용하려면 자바컬렉션 List로 추가해야 합니다.
애초부터 데이터베이스는 양방향연관관계였으므로
데이터베이스 상에서 달라지는 점은 없습니다!
5.3.1 양방향 연관관계 매핑
회원 엔티티에서는 이미 다대일 연관관계가 있으므로 수정할 사항이 없습니다.
팀 엔티티에서만 연관관계를 추가해주면 됩니다.
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>():
팀 엔티티에서 List<Member> members 필드를 추가하였고
@OneToMany를 통해 팀과 회원이 일대다임을 알려주었습니다.
mappedBy 속성을 활용해서 반대쪽 매핑의 필드(Member.team)를 지정해주었습니다.
5.3.2 일대다 컬렉션 조회
public void biDirection(){
Team team = em.find(Team.class, "team1");
List<Member> members = team.getMembers();
for (Member member : members){
System.out.println("Member.username = " + member.getUsername());
}
}
이전과 동일하게 객체 그래프 탐색으로 팀에 속해있는 회원들을 출력할 수 있습니다.
5.4 연관관계의 주인
mappedBy 속성에 대해 알아보겠습니다.
테이블의 경우에는 애초에 양방향연관관계이지만
객체는 단방향 2개를 서로다른 방향으로 연결해놓은 것이므로 연관관계를 관리하는 곳이 2곳이 됩니다.
그리고, 객체의 참조는 2개지만 외래 키는 하나입니다.
그러므로 테이블의 외래키를 관리하는 것이 필요합니다.
✅연관관계의 주인: 테이블의 외래키를 관리하는 객체 연관관계
5.4.1 양방향 매핑의 규칙 : 연관관계의 주인
연관관계 주인만 데이터베이스 연관관계와 매핑되고
외래 키를 관리할 수 있습니다.
주인이 아닌 쪽은 읽기만 할 수 있습니다.
- 주인은 mappedBy 속성 사용하지 않음
- 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 지정해야 함
5.4.2 연관관계의 주인은 외래 키가 있는 곳
📍연관관계의 주인은 테이블에 외래 키가 있는 곳으로!
그러므로 여기서는 회원 테이블이 Member.team이 주인이 됩니다.
주인이 아닌 쪽에는 mappedBy를 사용해줍니다.
일대다, 다대일 쪽에서는 다 쪽이 외래키를 가집니다.
(@ManyToOne에는 mappedBy 속성 없음)
5.5 양방향 연관관계 저장
public void testSave(){
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
member.setTeam(team1);
em.persist(member1);
Member member2 = new Member("member2", "회원2");
member2.setTeam(team1);
em.persist(member2);
}
이전에 단방향 연관관계에서 팀과 회원을 저장하는 코드와 같습니다.
연관관계의 주인이 외래키를 관리하므로 주인이 아닌 방향은 외래키 값이 정상 입력됩니다.
5.6 양방향 연관관계의 주의점
연관관계의 주인에도 연관관계를 설정해주어야 한다!
5.6.1 순수한 객체까지 고려한 양방향 연관관계
객체 관점에서는 연관관계의 주인, 주인이 아닌 쪽 모두 값을 입력해야 한다 ㅎㅎ
5.6.2 연관관계 편의 메소드
양쪽 다 신경써야 한다.
member.setTeam(team);
team.getMembers().add(member);
위 코드를 항상 함께 사용해야 한다.
public void setTeam(Team team){
this.team = team;
team.getMembers().add(this);
}
위와 같이 연관관계 설정을 묶은 메서드를 만들어서 활용하는 것이 좋다!
✅연관관계 편의 메소드 : 한 번에 양방향 관계를 설정하는 메소드
5.6.3 연관관계 편의 메소드 작성 시 주의사항
위에서 작성한 연관관계 편의 메소드에서 고쳐야 할 부분은
기존에 team과의 연관관계가 존재한 경우를 고려하지 않았다는 것이다!
그래서 연관관계를 설정하기 전에 기존 관계를 제거해야 한다.
public void setTeam(Team team){
if(this.team != null){
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
}
양방향연관관계를 객체에서 적용하려면 신경써야 할 부분이 많다 ㅠㅠ
5.7 정리
양방향의 장점 : 반대방향으로 객체 그래프 탐색 가능
✅ 단방향 매핑으로 테이블과 객체 연관관계 매핑은 완료된 상태
✅ 단방향을 양방향으로 만들면 반대방향의 객체 그래프 탐색 가능
✅ 양방향 연관관계 매핑 시 객체에서 양쪽 방향을 모두 관리해야 함
우선은 단방향 매핑 사용 -> 반대방향의 객체그래프탐색 필요 시 양방향 사용하도록 코드 추가
'BE > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
[JPA STUDY] 4장. 엔티티 매핑 (0) | 2025.03.30 |
---|---|
[JPA STUDY] 03. 영속성 관리 (0) | 2025.03.22 |
[ JPA STUDY ] 2장.JPA 시작 (0) | 2025.03.15 |
[ JPA STUDY ] 1장. JPA 소개 (0) | 2025.03.15 |