[JPA] Chapter 5. 연관관계 매핑 기초 1 - 단방향 연관관계
들어가기 앞서
이 글은 김영한 님의 저서 「자바 ORM 표준 JPA 프로그래밍」을 학습한 내용을 정리한 글입니다. 모든 출처는 해당 저서에 있습니다.
- 엔티티는 대부분 다른 엔티티와 연관관계가 존재함
- 객체의 참조와 테이블의 외래 키를 매핑하는 것이 핵심
🔎 핵심 키워드
- 방향(Direction) : 엔티티의 관계가 어느쪽을 참조하고 있는가
- 종류
- 단방향 : 엔티티의 관계가 한 쪽만 참조하는 것
ex) 회원 → 팀, 팀 → 회원
- 양방향 : 엔티티의 관계가 양쪽 모두 참조하는 것
ex) 회원 ↔ 팀
- 단방향 : 엔티티의 관계가 한 쪽만 참조하는 것
- 객체 관계에만 존재
- 테이블 관계는 항상 양방향
- 종류
- 다중성(Multiplicity) : 연관 관계가 있는 여러 개의 클래스가 있을 때 실제로 연관을 가지는 객체의 수
- 다대일(N:1) ex) 회원(N) : 팀(1)
- 일대다(1:N) ex) 팀(1) : 회원(N)
- 일대일(1:1)
- 다대다(N:N)
- 연관관계의 주인 : 객체가 양방향 연관관계일 때 테이블의 외래키를 관리하는 객체
5.1 단방향 연관관계
-
회원(Member)과 팀(Team)을 통한 관계 이해
- 회원은 하나의 팀에만 소속될 수 있다.
- 회원과 팀은 다대일 관계이다.
-
객체 연관관계
- 회원 객체는
Member.team
필드(멤버변수)로 팀 객체와 연관관계를 맺음
- 회원 객체와 팀 객체는 단방향 관계
→ 회원은 Member.team
필드를 통해 팀을 알 수 있지만, 반대로 팀은 회원을 알 수 없음
ex) member
→ team
: member.getTeam()
ex) team
→ member
: x
-
테이블 연관관계
- 회원 테이블은
TEAM_ID
외래 키로 팀 테이블과 연관관계를 맺음
- 회원 테이블과 팀 테이블은 양방향 관계
→ 회원 테이블의 TEAM_ID
외래키를 통해 회원과 팀, 팀과 회원 조인 가능
- 회원(
MEMBER
)과 팀(TEAM
) 조인
- 팀(
TEAM
)과 회원(MEMBER
) 조인
-
객체 연관관계 vs 테이블 연관관계
- 객체는 참조(주소)로, 테이블은 외래 키로 연관관계를 맺는다.
- 객체의 연관관계는 단방향(
A → B (a.b)
), 테이블의 연관관계는 양방향(A JOIN B
, B JOIN A
)이다.
- 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
- A → B (a.b)
- B → A (b.a)
5.1.1 순수한 객체 연관관계
✅ 클래스 관계
회원(Member)과 팀(Team)을 통한 관계 이해
- 회원은 하나의 팀에만 소속될 수 있다.
- 회원과 팀은 다대일 관계이다.
-
객체 연관관계
- 회원 객체는
Member.team
필드(멤버변수)로 팀 객체와 연관관계를 맺음 - 회원 객체와 팀 객체는 단방향 관계
→ 회원은Member.team
필드를 통해 팀을 알 수 있지만, 반대로 팀은 회원을 알 수 없음
ex)member
→team
:member.getTeam()
ex)team
→member
: x
- 회원 객체는
-
테이블 연관관계
- 회원 테이블은
TEAM_ID
외래 키로 팀 테이블과 연관관계를 맺음 - 회원 테이블과 팀 테이블은 양방향 관계
→ 회원 테이블의TEAM_ID
외래키를 통해 회원과 팀, 팀과 회원 조인 가능- 회원(
MEMBER
)과 팀(TEAM
) 조인
- 팀(
TEAM
)과 회원(MEMBER
) 조인
- 회원(
- 회원 테이블은
-
객체 연관관계 vs 테이블 연관관계
- 객체는 참조(주소)로, 테이블은 외래 키로 연관관계를 맺는다.
- 객체의 연관관계는 단방향(
A → B (a.b)
), 테이블의 연관관계는 양방향(A JOIN B
,B JOIN A
)이다. - 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
- A → B (a.b)
- B → A (b.a)
public class Member {
private String id;
private String username;
private Team team; //팀의 참조를 보관
public void setTeam(Team team) {
this.team = team;
}
//Getter, Setter ...
}
public class Team {
private String id;
private String name;
//Getter, Setter ...
}
✅ 인스턴스 관계
public static void main(String[] args) {
//생성자(id, 이름)
Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");
Team team1 = new Team("team1", "팀1");
//member1과 member2를 team1에 소속
member1.setTeam(team1);
member2.setTeam(team1);
//객체 그래프 탐색을 통한 연관관계 탐색
Team findTeam = member1.getTeam();
}
5.1.2 테이블 연관관계
💻 테이블 DDL
CREATE TABLE MEMBER {
MEMBER_ID VARCHAR(255) NOT NULL,
TEAM_ID VARCHAR(255),
USERNAME VARCHAR(255),
PRIMARY KEY (MEMBER_ID)
}
CREATE TABLE TEAM {
TEAM_ID VARCHAR(255) NOT NULL,
NAME VARCHAR(255),
PRIMARY KEY (TEAM_ID)
}
//회원 테이블의 TEAM_ID에 외래 키 제약조건 설정
ALTER TABLE MEMBER ADD CONSTRAINT FK_MEMBER_TEAM
FOREIGN KEY (TEAM_ID)
REFERENCES TEAM
💻 회원1과 회원2을 팀1에 소속
INSERT INTO TEAM(TEAM_ID, NAME) VALUES('team1', '팀1');
INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME)
VALUES('member1', 'team1', '회원1');
INSERT INTO MEMBER(MEMBER_ID, TEAM_ID, USERNAME)
VALUES('member2', 'team1', '회원2');
💻 회원1이 소속된 팀 조회
//조인을 이용한 연관관계 탐색
SELECT T.* FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
WHERE M.MEMBER_ID = 'member1'
5.1.3 객체 관계 매핑
✅ 연관관계 매핑 코드 분석
💻 매핑한 회원 엔티티
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
prviate String id;
private String username;
//연관관계 매핑
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
//연관관계 설정
public void setTeam(Team team) {
this.team = team;
}
//Getter, Setter ...
}
@ManyToOne
- 다대일(N:1) 관계를 나타내는 매핑 정보
- 연관관계 매핑 시 사용 필수
- 속성
속성 설명 기본값 optional false로 설정 시 연관된 엔티티가 항상 존재해야 함 true fetch 글로벌 패치 전략 설정 FetchType.EAGER
cascade 속성 전이 기능 사용 targetEntity ◾ 연관된 엔티티의 타입 정보 설정
◾ 거의 사용하지 않음
◾ 컬렉션 사용해도 제네릭으로 타입 정보 알 수 있음
ex)@OneToMany
private List<Member> members;
@JoinColumn
- 외래 키 매핑 시 사용
- 생략 가능
- 속성
속성 설명 기본값 name 매핑할 외래 키 이름 필드명 + _ + 참조하는 테이블의
기본 키 컬럼명
ex) team_TEAM_IDreferencedColumnName 외래 키가 참조하는
대상 테이블의 컬럼명참조하는 테이블의 기본 키 컬럼명 foreignKey
(DDL)◾ 외래 키 제약조건 직접 지정
◾ 테이블 생성 시에만 사용unique,
nullable,
insertable,
columnDefinition,
table@Column
의 속성과 같음
💻 매핑한 팀 엔티티
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
//Getter, Setter ...
}
5.2 연관관계 사용
연관관계를 등록, 수정, 조회, 삭제하는 예제를 통해 연관관계 사용 방식에 대해 알아보자.
5.2.1 저장
💡 JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 한다.
💻 회원과 팀을 저장하는 코드
public void testSave() {
//팀1 저장
Team team1 = new Team("team1", "팀1");
em.persist(team1);
//회원1 저장
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); //연관관계 설정 member1 → team1
em.persist(member1);
//회원2 저장
Member member2 = new Member("member2", "회원2");
member2.setTeam(team1); //연관관계 설정 member2 → team1
em.persist(member2);
}
✅ 분석
JPA는 참조한 팀의 식별자(Team.id
)를 외래 키로 사용하여 적절한 등록 쿼리를 생성한다.
INSERT INTO TEAM (TEAM_ID, NAME) VALUES ('team1', '팀1')
INSERT INTO MEMBER (MEMBER_ID, NAME, TEAM_ID) VALUES ('member1', '회원1', 'team1')
INSERT INTO MEMBER (MEMBER_ID, NAME, TEAM_ID) VALUES ('member2', '회원2', 'team1')
다음과 같은 SQL문을 입력하여 실행하면 데이터가 잘 입력되었는지 확인할 수 있다.
SELECT M.MEMBER_ID, M.NAME, M.TEAM_ID, T.NAME AS TEAM_NAME
FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
💻 결과
MEMBER_ID | NAME | TEAM_ID | TEAM_NAME |
---|---|---|---|
member1 | 회원1 | team1 | 팀1 |
member2 | 회원2 | team1 | 팀1 |
5.2.2 조회
✅ 객체 그래프 탐색
- 객체를 통해 연관된 엔티티 조회
Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); //객체 그래프 탐색
System.out.println("팀 이름 = " + team.getName();
//출력 결과 : 팀 이름 = 팀1
✅ 객체지향 쿼리(JPQL) 사용
- 예를 들어 팀1에 소속된 회원만 조회하려면 회원과 연관된 팀 엔티티를 검색 조건으로 사용해야 함
- SQL은 연관된 테이블을 조인하여 검색조건을 사용하며, JPQL도 조인을 지원함
💻 JPQL 조인 검색
private static void queryLogicJoin(EntityManager em) {
//회원과 팀 간 관계가 존재하는 필드(m.team)를 통해 Member와 Team 조인
String jpql = "select m from Member m join m.team t where " +
"t.name=:teamName"; //조인한 t.name을 검색조건으로 사용
//파라미터 바인딩
List<Member> resultList = em.createQuery(jpql, Member.class)
.setParameter("teamName", "팀1") //팀1에 속한 회원만 검색
.getResultList();
for (Member member : resultList) {
System.out.println("[query] member.username=" +
member.getUsername());
}
}
//결과 : [query] member.username=회원1
//결과 : [query] member.username=회원2
💻 실행 SQL
SELECT M.* FROM MEMBER MEMBER
INNER JOIN
TEAM TEAM ON MEMBER.TEAM_ID = TEAM1_.ID
WHERE
TEAM1_.NAME = '팀1'
5.2.3 수정
참조하는 대상만 변경하면 나머지는 JPA가 자동으로 처리한다.
💻 연관관계 수정
private static void updateRelation(EntityManger em) {
//새로운 팀2
Team team2 = new Team("team2", "팀2");
em.persist(team2);
//회원1에 새로운 팀2 설정
Member member = em.find(Member.class, "member1");
member.setTeam(team2);
}
💻 실행 SQL
UPDATE MEMBER
SET
TEAM_ID='team2', ...
WHERE
ID='member1'
5.2.4 연관관계 제거
💻 연관관계 삭제
private static void deleteRelation(EntityManager em) {
Member member1 = em.find(Member.class, "member1");
member1.setTeam(null); //연관관계 제거
}
💻 실행 SQL
UPDATE MEMBER
SET
TEAM_ID=null, ...
WHERE
ID='member1'
5.2.5 연관된 엔티티 삭제
- 기존에 존재하는 연관관계를 먼저 제거 후 삭제
why? 외래 키 제약조건으로 인해 데이터베이스에서 오류 발생
member1.setTeam(null); //회원1 연관관계 제거
member2.setTeam(null); //회원2 연관관계 제거
em.remove(team); //팀 삭제
📖 참고
Author And Source
이 문제에 관하여([JPA] Chapter 5. 연관관계 매핑 기초 1 - 단방향 연관관계), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@yu-jin-song/JPA-Chapter-5.-연관관계-매핑-기초저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)