이 글은 김영한 님의 저서 「자바 ORM 표준 JPA 프로그래밍」을 학습한 내용을 정리한 글입니다. 모든 출처는 해당 저서에 있습니다.
회원(Member)과 팀(Team)을 통한 관계 이해
- 회원은 하나의 팀에만 소속될 수 있다.
- 회원과 팀은 다대일 관계이다.
객체 연관관계
Member.team
필드(멤버변수)로 팀 객체와 연관관계를 맺음Member.team
필드를 통해 팀을 알 수 있지만, 반대로 팀은 회원을 알 수 없음member
→ team
: member.getTeam()
team
→ member
: x테이블 연관관계
TEAM_ID
외래 키로 팀 테이블과 연관관계를 맺음TEAM_ID
외래키를 통해 회원과 팀, 팀과 회원 조인 가능MEMBER
)과 팀(TEAM
) 조인TEAM
)과 회원(MEMBER
) 조인객체 연관관계 vs 테이블 연관관계
A → B (a.b)
), 테이블의 연관관계는 양방향(A JOIN B
, B JOIN 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();
}
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
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');
//조인을 이용한 연관관계 탐색
SELECT T.* FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
WHERE M.MEMBER_ID = 'member1'
@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
속성 | 설명 | 기본값 |
---|---|---|
optional | false로 설정 시 연관된 엔티티가 항상 존재해야 함 | true |
fetch | 글로벌 패치 전략 설정 | FetchType.EAGER |
cascade | 속성 전이 기능 사용 | |
targetEntity | ◾ 연관된 엔티티의 타입 정보 설정 ◾ 거의 사용하지 않음 ◾ 컬렉션 사용해도 제네릭으로 타입 정보 알 수 있음 ex) @OneToMany private List<Member> members; |
@JoinColumn
속성 | 설명 | 기본값 |
---|---|---|
name | 매핑할 외래 키 이름 | 필드명 + _ + 참조하는 테이블의 기본 키 컬럼명 ex) team_TEAM_ID |
referencedColumnName | 외래 키가 참조하는 대상 테이블의 컬럼명 | 참조하는 테이블의 기본 키 컬럼명 |
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 ...
}
연관관계를 등록, 수정, 조회, 삭제하는 예제를 통해 연관관계 사용 방식에 대해 알아보자.
💡 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')
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 |
Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); //객체 그래프 탐색
System.out.println("팀 이름 = " + team.getName();
//출력 결과 : 팀 이름 = 팀1
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
SELECT M.* FROM MEMBER MEMBER
INNER JOIN
TEAM TEAM ON MEMBER.TEAM_ID = TEAM1_.ID
WHERE
TEAM1_.NAME = '팀1'
참조하는 대상만 변경하면 나머지는 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);
}
UPDATE MEMBER
SET
TEAM_ID='team2', ...
WHERE
ID='member1'
private static void deleteRelation(EntityManager em) {
Member member1 = em.find(Member.class, "member1");
member1.setTeam(null); //연관관계 제거
}
UPDATE MEMBER
SET
TEAM_ID=null, ...
WHERE
ID='member1'
member1.setTeam(null); //회원1 연관관계 제거
member2.setTeam(null); //회원2 연관관계 제거
em.remove(team); //팀 삭제
📖 참고