[JPA]연관관계 매핑 - 1

Inung_92·2023년 11월 6일
1

JPA

목록 보기
7/7
post-thumbnail

연관관계 매핑

연관관계에서 중요한 부분은 객체와 테이블의 연관관계 매핑의 방식이 다르다는 것을 인식해야하는 것이다. 어떻게 다른지 간단하게 알아보자.

  • 객체 : 참조를 통해 연관 관계를 맺음.
  • 테이블 : 외래 키를 사용해 연관 관계를 맺음.

위와 같은 객체와 테이블의 연관관계 매핑의 특성을 더 잘 이해하기 위해 아래 사항들을 인지해야한다.

  • 방향 : 테이블을 항상 양방향의 관계를 가지며, 객체는 단방향 또는 양방향으로 구분 될 수 있음.
  • 다중성 : 다대일, 일대다, 일대일, 다대다의 관계를 이야기함.
  • 연관관계의 주인 : 테이블은 해당 외래 키를 보유한 테이블이 주인이지만 객체는 연관관계의 주인을 지정하여 사용해야함.

연관 관계의 중요도 순으로 차근차근 알아가보자.

단방향 연관관계

N:1이라고도 하며 연관 관계를 이해할 때 가장 먼저 이해가 필요한 부분이다.

위 그림을 보면서 이해해보자.

  • 객체
    • Member 객체가 Team 객체를 필드로 보유하여 연관관계를 맺음.
    • Member → Team의 단방향 관계로 member.getTeam() 등의 메소드로 Member와 연관된 Team을 알 수 있지만 Team과 연관된 Member에 대해서는 알 수 없음.
  • 테이블
    • TEAM_ID 형태의 외래 키를 통해 테이블 간의 연관 관계를 맺음.
    • JOIN을 이용하여 어느 방향이든 서로 연관된 테이블의 정보를 조회할 수 있음.

객체와 테이블의 연관 관계의 가장 큰 차이는 참조를 통한 연관 관계는 항상 단방향이라는 점이다. 즉, 객체 입장에서의 양방향은 서로 단방향으로 연관 관계를 맺는 것을 의미한다.

위 그림에서도 볼 수 있듯이 Member → Team으로 화살표가 표시되어있고, 반대로 참조할 경우 Member ← Team 방향으로 화살표가 표시될 것이다. 즉 하나의 선에 화살표가 2개가 표시되는 것이 아닌 각각의 선에 방향이 표시되는 형태라고 이해하면 좋을 것 같다. 코드를 보면서 이해해보자.

// Member -> Team 단방향
class Member {
	private Long id;
	private String memberName;
	private int age;
	
	private Team team; // 참조를 보관
}

// Team -> Member 단방향
class Team {
	private Long id;
	private String teamName;
	
	private List<Member> members; // Team에 연관된 회원들의 참조를 보관
}

위 코드에서 서로 단방향 연관 관계를 맺었다. 이 상태가 위에서 설명한 2개의 단방향 관계이며, 객체 그래프 탐색을 통해 서로를 호출하거나 참조할 수 있기 때문에 이런 형태를 양방향 관계라고 부르는 것이다. 정확하고 엄밀히 따지면 단방향 2개가 맞다.

여기까지 객체의 단방향 연관관계에 대해 알아보았으니 실제 엔티티와 테이블을 어떻게 매핑을 하는지 알아보자.

// Member 엔티티
@Entity
public class Member{
	@Id @GeneratedValue
	@Column(name = "member_id")
	private Long id;
	@Column(name = "name")
	private String memberName;

	// 연관관계 매핑(다대일, N:1)
	@ManyToOne // 다중성 지정
	@JoinColumn(name = "team_id") // 연관관계 컬럼 지정
	private Team team;

	// getter, setter
	...
}

// Team 엔티티
@Entity
public class Team{
	@Id @GeneratedValue
	@Column(name = "team_id")
	private Long id;
	private String name;
	
	// getter, setter
	...
}

위처럼 코드를 작성해주면 Member → Team의 단방향 연관 관계 매핑이 완료된다. 연관관계 매핑에서 사용된 어노테이션에 대해 확인하고 넘어가자.

@JoinColumn

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

주요 속성은 다음과 같다.

속성기능기본값
name매핑할 외래 키 이름필드명_기본키컬럼명
referencedColumnName외래 키가 참조하는 대상 테이블의 컬럼명참조하는 테이블의 기본키 컬럼명
foreignKey외래 키 제약 조건

| unique
nullable
insertable
updatable
columnDefinition
table | @Column 어노테이션의 속성과 동일 | |

아래와 같이 @JounColumn 어노테이션을 생략하면 기본 전략을 사용한다.

// 기본전략 : 필드명_참조하는 컬럼명
@ManyToOne
private Team team;

@ManyToOne

연관 관계의 다중성을 표현 할 때 사용한다. 생략할 수 없다.

주요 속성은 다음과 같다.

속성기능기본값
optionalfalse로 설정 시 연관된 엔티티가 항상 있어야한다.true
fetch글로벌 패치 전략을 사용FetchType.EAGER
FetchType.LAZY
cascade영속성 전이 기능 사용-

fetchcascade는 여기서 다룰 내용은 아니지만 간략히 설명하면 다음과 같다.

  • fetch : 글로벌 패치 전략으로 엔티티가 로드되는 시점에 대한 설정
  • cascade : 영속성 전이 기능으로 객체 변경사항 발생 시 연관된 객체도 함께 변경사항이 적용되는 설정

연관 관계 사용

위에서 다룬 내용을 토대로 연관 관계 사용에 대해서 알아보자.

저장

... 생략
public void save(){
	// 팀 생성
	Team team = new Team();
	team.setId("팀1");

	// 멤버들 생성
	Member member1 = new Member();
	Member member2 = new Member();
	member1.setId("멤버1");
	member2.setId("멤버2");

	// 연관 관계 설정
	member1.setTeam(team);
	member2.setTeam(team);

	// 영속화
	entityManager.persist(member1);
	entityManager.persist(member2);
}

1개의 팀과 2명에 멤버를 연관 관계 설정을 하였다. 이후 영속성 컨텍스트에 영속화하여 저장시키면 자동으로 쿼리가 생성되는데 쿼리는 어떤 모습인지 알아보자.

# 팀 생성
INSERT INTO TEAM (TEAM_ID, NAME) VALUES ('팀1', 생략)
# 멤버 생성
INSERT INTO MEMBER (MEMBER_ID, NAME, TEAM_ID) VALUES ('멤버1', 생략, '팀1')
INSERT INTO MEMBER (MEMBER_ID, NAME, TEAM_ID) VALUES ('멤버2', 생략, '팀1')

멤버를 등록 할 때 JPA가 자동으로 팀 객체의 식별자 값을 입력해준다. 연관 관계를 설정해주고 관계의 주인을 영속화하면 해당 객체의 정보도 같이 영속화가 되는 것이다.

조회

연과 관계를 매핑한 엔티티를 조회하는 방법은 크게 두가지가 있다.

  • 객체 그래프 탐색
  • 객체지향 쿼리 사용(JPQL)

객체 그래프 탐색

// 연관된 객체를 조회
String id = "멤버1";
Member member = entityManager.find(Member.class, id);
Team findTeam = member.getTeam();

먼저 연관 관계의 주인이 되는 객체를 데이터베이스에서 조회하고, 해당 객체와 연관된 객체를 그래프 탐색을 통해 조회 할 수 있다.

객체 지향 쿼리언어(JPQL) 사용

// JPQL 정의
String jpql = "select m from Member m join m.team t where t.id = :teamId";
List<Member> result = entityManager.createQuery(jpql, Member.class)
				.setParameter("teamId", "팀1")
				.getResultList();

위 JPQL에서 Member m join [m.team](http://m.team) 부분이 관계를 가진 필드를 이용하여 조인을 하는 부분이다. 팀 id가 ‘팀1’인 팀의 회원들을 조회한 쿼리문이며 :teamId 이 부분은 쿼리문에 사용될 파라미터를 바인딩하는 문법이다.

수정

수정은 이전에도 한번 보았겠지만 영속화된 엔티티의 필드를 변경해줌으로써 영속성 컨텍스트가 변경 감지를 통하여 변경사항을 데이터베이스에 반영하도록 코드를 작성하면된다.

...생략
Team team2 = new Team();
team.setId("팀2");

// 새로운 팀으로 관계 변경
Member member = entityManager.find(Member.class, "멤버1");
member.setTeam(team2); // 수정

member는 기존에 데이터베이스에 ‘팀1’을 연관 관계로 매핑했던 엔티티이다. 해당 엔티티를 조회하여 필드의 참조를 수정하면 영속성 컨텍스트가 변경을 감지하여 데이터베이스에 자동으로 반영한다.

제거

...생략
Member member1 = entityManager.find(Member.class, "멤버1");
member1.setTeam(null); // 연관관계 제거

연관 관계를 제거하는 것은 객체가 참조하는 연관 관계를 없애버리면 된다.

추가적으로 연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야한다. 외래 키 제약조건에 의해 삭제되지 않는 상황을 해결하기 위해서이다.


마무리

테이블과 객체의 연관관계에 대한 특징을 알아 볼 수 있었다. 책을 읽기 전에는 제대로 구분할 수 없었고, 어떤 영향을 미치는지 정확히 알 수 없었던 부분들이 이해가 되기 시작했다. 설계 단계부터 객체 그래프가 가능 할 수 있도록 고려하는 것이 중요하고, 중간중간 엔티티가 변경될 수 있지만 큰 틀이 벗어나는 것은 연관관계 매핑에 불편한 상황을 만드는 것이라는 것을 항상 생각해야한다.

다음 포스팅에서는 더욱 다양한 연관관계에 대해서 알아보자.

그럼 이만.

profile
서핑하는 개발자🏄🏽

0개의 댓글