JPA - (다대일) 단방향 연관관계

이유석·2023년 1월 5일
1

JPA - Entity

목록 보기
4/14
post-thumbnail

단방향 연관관계의 이해를 위해, 다대일(N:1) 단방향 관계로 설명을 해보겠습니다.

객체 및 테이블 모델링

이해를 돕기 위해, 회원(Member)과 팀(Team)의 관계를 예시로 들어보겠습니다.

  • 회원과 팀이 있다.
  • 회원은 하나의 팀에만 소속될 수 있다.
  • 다수의 회원은 하나의 팀에 소속될 수 있습니다.
  • 즉, 회원관 팀은 다대일(N:1)의 관계입니다.

위 조건에 다대일 단방향 관계를 위한 추가 조건은 아래와 같습니다.

  • 회원 객체와 팀 객체는 단방향 관계입니다.
  • 회원 객체(Member)는 Member.team 필드를 통해서 회원이 속한 팀 객체(Team)에 접근할 수 있습니다.
  • 팀 객체(Team)는 팀에 속한 회원 객체(Member)에 접근할 수 없습니다.

위 관계를 통해서 객체 및 테이블 모델링을 한 결과는 아래와 같습니다.

객체 관계 매핑

해당 객체 모델링을 코드로 나타내어 보도록 하겠습니다.

코드 설명

Member 클래스 (다대일에서 에 해당합니다.)

@Entity
public class Member {
   @Id
   @Column(name = "MEMBER_ID)
   private Long id;
 
   @Column(name = "USERNAME")
   private String username;
 
   @ManyToOne
   @JoinColumn(name = "TEAM_ID")
   private Team team;
  
  // Getter, Setter, Constructor...
}

Team 클래스 (다대일에서 에 해당합니다.)

@Entity
public class Team {
	@Id
    @Column(name = "TEAM_ID)
    private Long id;
    
    @Column(name = "NAME")
    private String name;
    
    // Getter, Setter, Constructor
}

다대일 단방향 매핑에서는 에 해당하는 클래스에 에 해당하는 클래스를 참조 필드로 작성해주시면 됩니다.
이때, 해당 참조 필드위에 @ManyToOne@JoinColumn(name = "외래키 이름")을 추가하여 줍니다.

@JoinColumn

외래키를 매핑할 때 사용합니다.

속성기능기본값
name매핑할 외래 키 이름"필드명" + "_" + "참조하는 테이블의 기본 키 컬럼명"
referencedColumnName외래 키가 참조하는 대상 테이블의 컬럼명참조하는 테이블의 기본 키 컬럼명
foreignKey(DDL)외래 키 제약조건을 직접 지정할 수 있다.
이 속성은 테이블을 생성할 때만 사용한다.
unique@Column의 속성과 같다
nullable@Column의 속성과 같다
insertable@Column의 속성과 같다
updatable@Column의 속성과 같다
columnDefinition@Column의 속성과 같다
table@Column의 속성과 같다

JoinColumn 어노테이션은 생략 가능합니다.
@JoinColumn을 생략하면 외래 키를 찾을 때 기본 전략을 사용합니다.
기본 전략 : 필드명 + _ + 참조하는 테이블의 컬럼명

@ManyToOne

다대일 관계에서 사용합니다.

속성기능기본값
optionalfalse로 설정하면 연관된 엔티티가 항상 있어야 한다true
fetch글로벌 패치 전략을 설정한다.
(자세한 내용은 추후에)
@ManyToOne=FetchType.EAGER (즉시 로딩), @OneToMany=FetchType.LAZY (지연 로딩)
cascade영속성 전이 기능을 사용한다.
(자세한 내용은 추후에)
targetEntity연관된 엔티티의 타입 정보를 설정한다.
이 기능은 거의 사용하지 않음

연관관계 사용

연관관계를 등록, 수정, 삭제, 조회하는 예제를 통해 연관관계를 어떻게 사용하는지 알아보겠습니다.

저장

public void teamSave() {
	
    // 팀 1 저장
    Team team1 = new Team(0L, "팀1");
    entityManager.persist(team1); // 영속화
    
    // 회원 1 저장
    Member member1 = new Member(0L, "회원1");
	member1.setTeam(team1); // 연관관계 설정 member1 → team1
	entityManager.persist(member1);
    
    // 회원 2 저장
    Member member2 = new Member(1L, "회원2");
    member2.setTeam(team1); // 연관관계 설정 member2 → team1
    entityManager.persist(member2);
}
  • JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태이어야 합니다.

  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.

INSERT INTO TEAM (TEAM_ID, NAME) VALUES (0, '팀1');
INSERT INTO MEMBER (MEMBER_ID, USERNAME, TEAM_ID) VALUES (0, '회원1', 0);
INSERT INTO MEMBER (MEMBER_ID, USERNAME, TEAM_ID) VALUES (1, '회원2', 0);

조회

연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지가 있습니다.

객체 그래프 탐색
member.getTeam() 을 사용해서 member와 연관된 team 엔티티를 조회할 수 있습니다.

Member member = entityManager.find(Member.class, 0L);
Team team = member.getTeam(); // 객체 그래프 탐색
System.out.println("팀 이름 = " + team.getName());

// 출력 결과 : 팀 이름 = 팀1
  • 위 코드를 실행하였을 때, 실행되는 SQL 문은 아래와 같습니다.
SELECT m.MEMBER_ID, m.TEAM_ID, m.USERNAME, t.TEAM_ID, t.NAME
FROM 
	Member m
	LEFT OUTER JOIN
	Team t
	ON m.TEAM_ID = t.TEAM_ID
WHERE m.MEMBER_ID = 0;

객체지향 쿼리 (JPQL) 사용

String jpql = "select m.team from Member m where m.id = :memberId";

Team team = entityManager.createQuery(jpql, Team.class)
                .setParameter("memberId", 0L)
                .getSingleResult();
System.out.println("팀 이름 = " + team.getName());

// 출력 결과 : 팀 이름 = 팀1
  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.
SELECT t.*
FROM Team t
	JOIN Member m ON t.TEAM_ID = m.TEAM_ID
WHERE m.MEMBER_ID = 0;

수정

팀1 소속이던 회원1 을 새로운 팀2 에 소속하도록 수정해보겠습니다.

// 새로운 팀2 영속화
Team team2 = new Team(1L, "팀2");
entityManager.persist(team2);

// 회원1에 새로운 팀2 설정
Member member = entityManager.find(Member.class, 0L);
member.setTeam(team2);

수정은 entityManager.update(); 와 같은 메서드가 없으며,
조회한 엔티티의 값만 변경해두면 트랜잭션을 커밋할 때, 플러시가 일어나면서 변경 감지 기능이 작동합니다.
이때, 변경사항을 데이터베이스에 자동으로 반영해줍니다.

  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.
UPDATE MEMBER
SET
	TEAM_ID = 1, ...
WHERE
	MEMBER_ID = 0;

연관관계 제거

회원1 을 어느 팀에도 소속하지 않도록 변경해보겠습니다.

Member member1 = entityManager.find(Member.class, 0L);
member1.setTeam(null);

위 코드를 실행했을 때, 실행되는 SQL 은 아래와 같습니다.

UPDATE MEMBER
SET
	TEAM_ID = null, ...
WHERE
	MEMBER_ID = 0;

삭제

다대일 연관관계에서 에 연관된 엔티티인 Team 객체들 중, 팀1 을 제거해보겠습니다.

이때, 기존에 있던 연관관계를 먼저 제거하고 삭제를 수행해야 합니다.
그렇지 않으면 외래 키 제약 조건에 의해 데이터베이스에서 오류가 발생합니다.

// 나머지 회원2 에 대한 연관관계를 제거한다.
Member member2 = entityManager.find(Member.class, 1L);
member2.setTeam(null);

Member team = entityManager.find(Team.class, 0L);
entityManager.remove(team);
DELETE 
FROM TEAM
WHERE TEAM_ID = 0;

소스코드

profile
https://github.com/yuseogi0218

0개의 댓글