본문 바로가기

Spring boot/Blog 프로젝트 만들기(JPA)

단방향, 양방향 매핑에 대한 이해

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

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

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

 

gradle 확인
plugins {
	id 'java'
	id 'org.springframework.boot' version '3.2.10'
	id 'io.spring.dependency-management' version '1.1.6'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(21)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

 

 

application.yml
spring:
  application:
    name: demo

  server:
    port: 8080
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password: 
  
  jpa:
    hibernate:
      ddl-auto: create  # 하이버네이트의 DDL 자동 실행 옵션 설정

  h2:
    console:
      enabled: true  # H2 콘솔 웹 인터페이스 활성화 
      path: /h2-console  # H2 콘솔에 접근할 수 있는 경로 설정   
# 디버깅을 위한 로깅 설정
  logging:
    level:
      org:
        springframework:
          jdbc: DEBUG
        hibernate:
          SQL: DEBUG

 

 

 

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; 
}

 

 


 

 

학습 목표

JPA를 사용하여 객체-관계 매핑(ORM)의 개념을 이해하고, 단방향 및 양방향 매핑을 학습해보자. 

객체-관계 매핑(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에 대해 아무런 정보를 가지지 않게 된다.

 

 

 


 

 

시나리오 코드 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)
);

 

 

 

참조의 방향성

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

 


 

1. 단방향 참조

장점

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

단점

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

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 demo.demo1.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 엔티티에 대한 모든 상태 변경(저장, 삭제 등)이
    // 관련된 Comment 엔티티에 전파된다.
    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL)
    private List<Comment> comments;

}

 

 


 

시나리오 코드 3 

 

Order 엔티티 만들어 보기

주문 서비스에서는 사용자(User)가 배송지(Address)로 물품을 주문(Order)하는 과정이 필요하다.

 

프로젝트 요구 조건에 따라서 참조의 방향성을 선택할 수 있지만 여기서는 User와 Address 엔티티와의 단방향 참조 관계를 설정해서 코드를 작성해 보자.

package demo.demo1.model;

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
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String orderNum;
    private String orderName;
    
    @ManyToOne
    @JoinColumn(name = "order_id")
    private User user;

    @ManyToOne
    @JoinColumn(name = "address_id")
    private Address address;

}
728x90