엔티티 매핑, 연관관계

2025. 12. 12. 23:19·JPA

객체와 테이블 매핑

@Entity

- @Entity가 붙은 클래스는 JPA가 관리하는 클래스이다.

- JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 어노테이션을 필수로 붙여줘야 한다.

* 기본 생성자는 필수 (public or protected)

 

@Table

- @Table은 엔티티와 매핑할 테이블을 지정할 때 사용한다.

- name, catalog, schema, uniqueConstraints(DDL) 속성이 있다.

 

데이터베이스 스키마 자동 생성 (DDL)

- create (기존 테이블 삭제 후 다시 생성)

- create-drop (create와 같으나 종료시점에 테이블 drop)

- update (변경부분만 반영, 운영 DB엔 사용X)

- validate (엔티티와 테이블이 정상 매핑 되었는지만 확인)

- none (사용하지 않음)

 

* 운영 장비에는 절대 create, update 사용 X

로컬 환경에서 개발할 땐 사용해도 되지만 스테이징과 운영 서버에서는 validate 또는 none 사용

 

필드와 컬럼 매핑

- @Column : 컬럼 매핑 (다양한 속성은 검색)

- @Temporal :  날짜 타입 매핑 (LocalDate, LocalDateTime 사용시 생략 가능)

- @Enumerated : enum 타입 매핑

- @Lob : BLOB, CLOB 매핑

- @Transient : 특정 필드를 컬럼에 매핑하지 않음

 

- @Enumerated : enum 타입을 매핑할 때 사용 

*ORDINAL 사용 금지, 순서 꼬일 수 있음. STRING을 사용할 것

 

기본 키 매핑

- @Id : 직접 할당

- @GeneratedValue 

1. IDENTITY : DB에 위임 (MYSQL)

2. SEQUENCE : DB 시퀀스 오브젝트 사용 (ORACLE)

3. TABLE : 키 생성용 테이블 사용 (모든 DB 사용가능, @TableGenerator 필요)

4. AUTO : 방언에 따라 자동 지정, 기본 값 사용

 

IDENTITY

- 기본 키 생성을 DB에 위임 (MySQL, PostgreSQL, DB2에서 사용, MySQL의 AUTO_INCREMENT)

- JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행

- AUTO_INCREMENT는 DB에 INSERT 쿼리를 실행한 이후에 id값 확인 가능.

- em.persist() 시점에 즉시 INSERT 쿼리 실행 후 DB에서 식별자를 조회 

 

영속화때 1차 캐시를 사용한다고 했는데, IDENTITY전략은 PK값을 모르기 때문에

persist -> insert -> pk 생성 -> 다시 영속성 컨텍스트에 저장하는 과정이 생김

SEQUENCE

- DB 시퀀스는 유일한 값을 순서대로 생성하는 특별한 DB 오브젝트 (오라클 시퀀스)

- 오라클, postgreSQL, DB2, H2에서 사용

@Entity
@SequenceGenerator(
		name = “MEMBER_SEQ_GENERATOR",
		sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
		initialValue = 1, allocationSize = 1)
public class Member {
	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE,
	generator = "MEMBER_SEQ_GENERATOR")
	private Long id;

 

 

간단하게 설명하자면 시퀀스 전략은 em.persist()를 해도 insert 쿼리가 실행되지 않음.

커밋 시점에 한번에 insert쿼리를 실행하기 때문에 성능적으로 identity전략보다 우위임

그리고 identity전략과 다르게 DB에서 미리 PK 범위를 확보해두고 사용하기 때문에 PK를 미리 알고 있음.(allocationSize)

즉 DB가 만든 PK 범위를 메모리에 캐싱해서 쓰는것이다. 그렇기 때문에 DB접근 횟수가 줄고 insert쿼리를 한번에 날릴 수 있는 장점이 있는 것. (그런데 강사는 큰 성능 차이는 잘 모르겠다고 한다)

 

TABLE 

키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략

모든 DB에서 사용 가능하나 성능이 떨어진다.

 

 

권장하는 식별자 전략

기본 키 제약 조건 : null이 아니고, 유일하며, 변하면 안되는 값

미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.

예를 들어 주민등록번호도 기본 키로 적절하지 않다.
권장 : Long + 대체키 + 키 생성 전략 사용

 

 

연관관계 매핑 기초

객체와 테이블 연관관계의 차이를 이해해야한다.

객체의 참조와 테이블의 외래 키를 매핑

 

연관관계가 필요한 이유

예제 시나리오

- 회원과 팀이 있다.

- 회원은 하나의 팀에만 소속될 수 있다.

- 회원과 팀은 다대일 관계이다.

 

객체를 테이블에 맞추어 모델링

 

객체를 테이블에 맞추어 모델링 한 경우이다.

외래 키 식별자를 직접 다룬다. 

 

//조회
Member findMember = em.find(Member.class, member.getId());
//연관관계가 없음
Team findTeam = em.find(Team.class, team.getId());

 

계속해서 식별자로 조회하게 된다. 객체 지향적인 방법이 아님

 

객체를 테이블에 맞춰 데이터 중심으로 모델링하면 협력 관계를 만들 수 없다.

 

테이블은 외래 키 (FK)로 조인을 사용해서 연관된 테이블을 찾는다.

객체는 참조를 사용해서 연관된 객체를 찾는다.

 

테이블과 객체는 이런 차이가 있다.

 

객체 지향 모델링

@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;

 

// 팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

// 회원 저장
Member member = new Member();
member.setUsername("member1");
member.setTeam(team); // 단방향 연관관계, 참조 저장
em.persist(member);

 

참조로 연관관계 조회 - 객체 그래프 탐색

Member findMember = em.find(Member.class, member.getId());
// 참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();

 

결론은

식별자 중심 사고 -> 객체 참조 중심 사고로 바뀌는 것이다.

객체의 참조를 사용해 연관관계를 조회할 수 있는 편리함이 포인트이다. 객체 그래프 탐색이 가능하고, 비즈니스 로직이 깨끗해진다.

(그래프 탐색은 객체가 가지고 있는 참조를 따라가면서 연관된 객체를 조회 및 사용하는 것)

 

 

양방향 연관관계와 연관관계의 주인

 

@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();

 

반대 방향으로 객체 그래프 탐색 가능

Member findMember = em.find(Member.class, member.getId());
List<Member> members = findMember.getTeam().getMembers();

Member -> Team -> Member

 

연관관계 주인과 mappedBy

객체와 테이블이 관계를 맺는 차이

- 객체 연관관계 = 2개

회원 -> 팀 연관관계 1개 (단방향)

팀 -> 회원 연관관계 1개 (단방향)

 

- 테이블 연관관계 = 1개

회원 <-> 팀 연관관계 1개 (양방향)

 

객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개다.

객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야한다.

A -> B(a.getB())

B -> A(b.getA())

테이블의 양방향 연관관계

테이블은 외래키 하나로 두 테이블의 연관관계를 관리한다.

MEMBER.TEAM_ID 외래 키 하나로 양방향 연관관계를 가진다. (양쪽으로 조인할 수 있다)

SELECT *
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
SELECT *
FROM TEAM T
JOIN MEMBER M ON T.TEAM_ID = M.TEAM_ID

 

둘 중 하나로 외래키를 관리해야하는데, 외래 키를 어디서 관리해야하나?

 

연관관계의 주인

객체의 두 관계중 하나를 연관관계의 주인으로 지정

연관관계 주인만이 외래 키를 관리 (등록, 수정 가능)

주인이 아닌쪽은 읽기만 가능.

mappedBy는 주인이 아닌 객체에!

 

외래키가 있는 곳을 주인으로 정해라 (N 방향)

 

양방향 매핑 시 연관관계의 주인만이 외래 키를 관리할 수 있다고 했다. 주인에 값을 입력해야 한다.

- 순수한 객체 관계를 고려하면 항상 양쪽 다 값을 입력해야 함

flush, clear를 하면 상관없지만 안하게 되었을 경우 문제 발생할 수 있음.

Team team = new Team();
team.setName("TeamA");
em.persist(team);
Member member = new Member();
member.setName("member1");
team.getMembers().add(member);
//연관관계의 주인에 값 설정
member.setTeam(team); //**
em.persist(member);

 

양방향 매핑 정리

- 단방향 매핑만으로도 이미 연관관계 매핑은 완료된 것이다.

- 양방향 매핑은 역방향 조회 (객체 그래프 탐색) 기능이 추가된 것 뿐이다.

- JPQL에서 역방향 탐색할 일이 많다.

- 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 된다.

 

연관관계의 주인은 비즈니스 로직 기준으로 연관관계의 주인을 선택하면 안된다.

연관관계의 주인은 외래 키의 위치를 기준으로 정해야한다.

 

 

 

연관관계 매핑 핵심 정리

1. 연관관계 매핑의 본질

연관관계 매핑 = 객체 참조 <-> DB FK를 맞추는 작업

 

- DB는 FK 하나로 관계를 표현

- 객체는 참조를 통해 관계를 표현

이 차이를 맞추는게 JPA 연관관계 매핑

 

2. 연관관계의 주인

FK를 관리하는 쪽이 주인이다.

 

- FK를 INSERT/UPDATE 하는 쪽

- @JoinColumn이 있는 쪽

- DB 기준으로 FK가 있는 테이블이 주인이다.
- mappedBy쪽은 읽기 전용

 

3. 단방향 vs 양방향

 

- 단방향

Order -> Member

- 객체에서 한쪽만 참조

- 설계 단순, 관리 쉬움

- 기본 권장하는 전략

 

- 양방향

Order <-> Member

- 객체에서 양쪽 참조

- 편하지만 관리 포인트가 증가

- 연관관계 주인 반드시 지정 필요 

* DB 관점에서는 양방향 개념이 없다. 객체의 편의를 위한 개념

 

4. mappedBy의 의미

@OneToMany(mappedBy = "member")

- 나는 주인이 아니다. FK는 상대방의 member 필드가 관리한다.

- 테이블 이름, 컬럼이름 X

- 객체 필드 이름 O

 

5. FK 위치 설계 원칙

다대일 (N:1) 권장

 

- FK는 항상 N쪽

- INSERT 1번으로 관계 설정

- 불필요한 UPDATE가 없음. (1에 FK가 있을 때를 생각해보자)

 

6. 일대일 매핑

기본 원칙 : FK는 더 자주 조회되고, 비즈니스상 중심인 테이블에 둔다.

Order 1: Deliver 1
-> Order에 delivery_id FK

- 확장 가능성 고려

- 나중에 1:N으로 바뀌어도 구조 변경 최소화

 

7. 연관관계 편의 메서드

JPA는 한쪽만 세팅해도 FK는 정상이지만 객체 그래프는 깨짐

public void setMember(Member member) {
    this.member = member;
    member.getOrders().add(this);
}

- 객체 상태와 DB 상태를 동기화 하기 위함, (DB용이 아닌 객체 모델 관리용이다)

 

8. 연관관계 컬렉션 동작 원리

team.getMembers();

- FK를 가진 주인 쪽 테이블을 조회

내부적으로

SELECT * FROM member WHERE team_id = ?

- 컬렉션은 DB 조회 시점에 채워진다.

- LAZY 로딩이 기본

- FK기반 SELECT

 

9. 객체 그래프 탐색

order.getMember().getTeam().getName();

- 식별자 조회 X

- SQL 직접 작성 X

- 객체 참조만으로 연관 데이터를 접근

'JPA' 카테고리의 다른 글

JPA의 값 타입  (0) 2025.12.27
프록시와 연관관계 관리  (0) 2025.12.26
고급 매핑 - 상속 관계, Mapped Superclass  (0) 2025.12.25
영속성 관리 - 내부 동작 방식  (0) 2025.12.05
데이터 접근 기술 - JPA  (0) 2025.10.24
'JPA' 카테고리의 다른 글
  • 프록시와 연관관계 관리
  • 고급 매핑 - 상속 관계, Mapped Superclass
  • 영속성 관리 - 내부 동작 방식
  • 데이터 접근 기술 - JPA
공부처음하는사람
공부처음하는사람
  • 공부처음하는사람
    lazzzykim
    공부처음하는사람
  • 전체
    오늘
    어제
    • 분류 전체보기 (159)
      • Kotlin (31)
      • Java (56)
      • Spring (44)
      • JPA (6)
      • Algorithm (3)
      • TroubleShooting (1)
      • 내일배움캠프 프로젝트 (14)
      • Setting (2)
      • ... (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 인기 글

  • 태그

    jpa
    싱글톤
    Di
    캡슐화
    빈 생명주기
    김영한
    배열
    kotlin
    중첩클래스
    내일배움캠프
    예외처리
    언체크예외
    다형성
    제네릭
    spring
    김영한의 실전 자바
    김영한의 실전자바
    트랜잭션
    OCP
    java
  • hELLO· Designed By정상우.v4.10.3
공부처음하는사람
엔티티 매핑, 연관관계
상단으로

티스토리툴바