Spring boot

단방향, 양방향 매핑

ryeonng 2024. 9. 30. 16:52

JPA는 객체지향적 접근 방식이다.

  • SQL은 데이터베이스의 테이블 간 관계를 정의하는 언어이다. 테이블과 테이블의 관계는 외래 키를 통해 설정되며, 주로 데이터베이스 관점에서 관리된다.
  • JPA는 객체 간의 관계를 정의하는 자바의 ORM(객체-관계 매핑) 기술이다. JPA에서는 클래스와 객체를 사용해 테이블과 데이터 간의 관계를 표현한다.

둘의 중요한 차이점은 SQL은 테이블 간의 관계를 직접 정의하는 반면, JPA는 객체지향적인 관계를 통해 테이블 간의 관계를 간접적으로 정의한다는 점이다.

 

JPA 사용해 보기
package com.example.demo.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Address {
	
	// @Id: 이 필드는 데이터베이스 테이블의 기본 키(primary key)를 나타냅니다.
	// 이 어노테이션은 해당 필드가 엔티티의 고유 식별자임을 나타내며, 
	// 데이터베이스 테이블에서 이 필드를 기준으로 레코드를 구분합니다.
	
	
	// @GeneratedValue(strategy = GenerationType.IDENTITY):
	// 이 어노테이션은 기본 키가 자동으로 생성되도록 설정합니다. 
	// GenerationType.IDENTITY 전략은 데이터베이스의 "AUTO_INCREMENT" 기능을 사용하여 
	// 기본 키 값을 자동으로 생성합니다. 
	// 따라서, 새로운 엔티티가 데이터베이스에 삽입될 때마다 
	// 고유한 ID 값이 자동으로 생성되고 할당됩니다.
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id; 
	private String city; 
	private String street; 
}

 

객체-관계 매핑(ORM : Object-Relational Mapping)은 객체 지향 프로그래밍 언어에서 사용하는 객체 간의 관계를 관계형 데이터베이스의 테이블 간의 관계로 변환하는 과정이다. JPA는 이러한 객체 간의 관계를 데이터베이스에 적절히 매핑할 수 있도록 다양한 매핑 방식을 지원한다.

 

참조 방향성(FK의 주인은 누구인가)

  • 단방향 매핑 (Unidirectional Mapping) : 한 쪽에서만 참조하는 관계
  • 양방향 매핑 (Bidirectional Mapping) : 양쪽에서 서로 참조하는 관계

엔티티 간 관계

  • 일대일 매핑 (One-to-One Mapping) : 하나의 엔티티가 다른 하나의 엔티티와 연결되는 관계
  • 일대다 매핑 (One-to-Many Mapping) : 한 엔티티가 여러 엔티티와 연결되는 관계
  • 다대일 매핑 (Many-to-One Mapping) : 여러 엔티티가 하나의 엔티티와 연결되는 관계
  • 다대다 매핑 (Many-to-Many Mapping) : 두 엔티티가 서로 여러 개의 다른 엔티티들과 연결되는 관계

User와 Address 사이의 One-to-One 관계를 SQL로 표현할 때, 외래 키(FK)는 User 테이블이 Address 테이블을 참조하는 방식으로 설정된다. 즉, User 테이블이 Address 테이블의 기본 키(PK)를 외래 키로 참조하게 된다. 이로 인해 User는 특정 Address와 연결되고, Address는 User에 대해 아무런 정보를 가지지 않게 된다.

 

연관관계의 주인(FK를 가지는 클래스)

1. 참조 방향성과 연관관계의 주인 :
- 단방향 매핑에서는 하나의 엔티티만 다른 엔티티를 참조하기 때문에, 외래 키(FK)를 누가 가지는지가 명확하다. 한쪽이 다른 쪽을 참조하며, 참조하는 엔티티가 외래키를 가진다.
- 양방향 매핑에서는 두 엔티티가 서로를 참조한다. 이때 데이터베이스에서는 한쪽만이 실제로 외래 키를 가지고 있어야 하므로, 어느 한쪽이 연관관계의 주인이 된다.

2. 연관관계의 주인은 FK를 관리하는 엔티티 :
- 연관관계의 주인
은 데이터베이스 테이블에서 외래 키(Foreign Key)를 직접 관리하는 클래스이다.
- 반면, 연관관계의 비주인(Non-owner)은 외래 키를 직접 관리하지 않으며, 주로 매핑된 관계를 반영할 뿐이다. 비주인은 외래 키를 설정하거나 수정하지 않고, 단순히 주인의 관계를 조회한다.

 

시나리오 코드 2 - Post, Comment 엔티티 만들어 보기

자바의 객체 지향적인 관점에서 관계 살펴보기

Post 객체는 여러 개의 Comment 객체를 참조할 수 있다. 이는 List<Comment>를 통해 구현되며, 이를 통해 Post 객체는 자신과 연결된 여러 개의 댓글을 관리한다. 반대로, Comment 객체는 각각 하나의 Post 객체를 참조한다. 즉, 댓글은 항상 하나의 게시글에 속하게 된다.

 

SQL 관점에서 관계 살펴보기

Post와 Comment의 관계는 SQL 데이터베이스에서 1:N (일대다)관계로 정의된다. 즉, 하나의 게시글(Post)은 여러 개의 댓글(Comment)과 연결될 수 있다.

 

1. Post 테이블

Post 테이블은 각 게시글에 대한 정보를 담고 있으며, 각 게시글은 고유한 ID로 식별된다. SQL에서 이를 나타내기 위해 기본 키(PK)를 설정한다.

CREATE TABLE Post (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,  -- 게시글의 고유 ID (PK)
    title VARCHAR(255),                    -- 게시글 제목
    content TEXT                           -- 게시글 내용
);

 

2. Comment 테이블

Comment 테이블은 각 댓글에 대한 정보를 담고 있으며, 각 댓글은 고유한 ID로 식별된다. 댓글은 어느 게시글에 속하는지를 나타내기 위해 외래 키(FK)를 사용한다. 이 외래 키(FK)는 Post 테이블의 ID를 참조하며, 이를 통해 댓글이 어느 게시글에 속하는지 데이터베이스에서 관리된다.

CREATE TABLE Comment (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,  -- 댓글의 고유 ID (PK)
    text TEXT,                             -- 댓글 내용
    post_id BIGINT,                        -- 어느 게시글에 속하는지 참조 (FK)
    FOREIGN KEY (post_id) REFERENCES Post(id)  -- Post 테이블의 id를 참조 (FK)
);

 

참조의 방향성
Post와 Comment 사이에서 참조의 방향성을 단방향 또는 양방향으로 설정할 수 있으며, 두 방식은 각각의 장단점이 있다. 실무에서는 보통 위와 같은 경우 양방향 참조를 많이 선택할 수 있지만 단방향성으로 만들더라도 잘못된 부분은 없다.

 

1. 단방향 참조

장점

  1. 구현이 단순 : 한쪽에서만 참조하기 때문에 설계가 간단하다. 필요하지 않은 방향으로의 참조를 구현할 필요가 없어서 코드가 더 단순해진다.
  2. 성능 최적화 : 불필요한 참조를 하지 않으므로 메모리 사용을 줄일 수 있으며, 필요 없는 객체를 로드하지 않아도 된다. 특히 지연 로딩(Lazy Loading) 전략과 결합하면 성능이 최적화될 수 있다. 
  3. 메모리 사용 절약 : 단방향으로만 참조할 경우, 반대 방향으로의 객체를 유지할 필요가 없기 때문에 메모리 사용을 줄일 수 있다.

단점

  1. 양방향 탐색 불가 : 한쪽에서만 참조하기 때문에, 참조되지 않는 방향에서는 해당 객체를 참조하거나 조회할 수 없다. 예를 들어, Comment에서 Post를 참조할 수 없으므로, Comment에서 속한 Post에 접근할 수 없다.
  2. 관계 복잡성이 증가 : 만약 반대쪽에서 객체를 참조해야 할 경우, 별도의 쿼리를 작성해야 하므로 이로 인해 코드의 복잡성이 증가할 수 있다.

 

2. 양방향  참조

 

장점

  1. 양방향 탐색 가능 : 양방향 참조를 통해, Post와 Comment 모두 서로를 참조할 수 있다. 예를 들어, Post에서 Comment 리스트를 참조할 수 있고, Comment에서 자신이 속한 Post를 참조할 수 있어 관계탐색이 매우 용이하다.
  2. 더 직관적인 객체 탐색 : 양쪽에서 데이터를 쉽게 탐색할 수 있기 때문에, 개발자가 두 엔티티 간의 관계를 더 쉽게 관리하고 탐색할 수 있다. 이로 인해 코드 가독성이 좋아지고 유지보수가 쉬워진다.
  3. 더 적은 쿼리 : 데이터베이스에서 한쪽을 조회할 때 반대쪽 객체도 쉽게 접근할 수 있어, 필요에 따라 추가 쿼리 작성이 줄어들 수 있다.

단점

  1. 설계의 복잡성 증가 : 양방향 참조에서는 어느 엔티티가 연관관계의 주인인지를 명확히 설정해야 하고, 이로 인해 설계가 복잡해질 수 있다. 특히 mappedBy와 같은 설정을 올바르게 사용해야 한다.
  2. 메모리 사용 증가 : 양방향으로 참조를 유지하기 때문에, 두 엔티티가 서로를 참조할 경우 메모리 사용량이 늘어날 수 있다.
  3. 순환 참조의 위험 : 양쪽에서 서로를 참조하면서 순환 참조(Circular Reference)가 발생할 수 있다. 이는 직렬화(Serialization)나 JSON 변환에서 무한 루프를 일으킬 수 있다. 이런 문제를 해결하기 위해 @JsonIgnore와 같은 어노테이션을 사용해야 할 때도 있다.
package com.example.demo.model;

import java.util.List;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity(name = "tb_comment")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Comment {
	
	@Id // PK 지정 
	@GeneratedValue(strategy = GenerationType.IDENTITY) // 코드 --> db 위임
	private Long id; 
	private String text; 
	
	
	@ManyToOne
	@JoinColumn(name = "post_id")
	private Post post; 
	
}
package com.example.demo.model;

import java.util.List;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity(name = "tb_post")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Post {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id; 
	private String title; 
	private String content; 
	
	// mappedBy : post - 연관 관계의 주인이 Comment 엔티티에 post(속성) 필드 임을 나타냅니다. 
	// 객체 필드 기준으로 생각해야 합니다. 
	
	// CascadeType.ALL - 제약을 설정하게 되면 Post 엔티티에 대한 모든 상태 변경(저장, 삭제 등)이 
	// 관련된 Commnet 엔티티에 전파 된다. 
	@OneToMany(mappedBy = "post" , cascade = CascadeType.ALL)
	private List<Comment> comments; 
	
}

'Spring boot' 카테고리의 다른 글

블로그2 - 엔티티 만들기  (0) 2024.10.01
블로그 프로젝트 만들기1  (4) 2024.10.01
영속성 컨텍스트  (0) 2024.09.30
엔티티 매니저  (0) 2024.09.30
JPA 와 하이버네이트  (0) 2024.09.30