hyeon.s
개발로그
hyeon.s
전체 방문자
오늘
어제
  • 분류 전체보기 (151) N
    • Web 및 인프라 (1)
      • Web (1)
      • Terraform (2)
      • Docker (1)
    • Android (1)
      • 공부 (28)
      • 트러블슈팅 (12)
      • 프로젝트 개발 (10)
      • Compose (2)
      • 우테코 프리코스 (0)
    • Server (6) N
      • 공부 (1)
      • Spring (5) N
    • 알고리즘 (68)
      • 문제풀이 (C++,Kotlin) (54)
      • 공부 (13)
    • 디자인 (3)
      • UI (3)
    • Language (5)
      • Kotlin (5)
      • JAVA (0)
    • IT 동아리 (8)
      • UMC 3기 (Android) (7)
      • Sopt 32기 (Android) (1)

Github

글쓰기 / 관리자
hELLO · Designed By 정상우.
hyeon.s

개발로그

[Spring/공부] Spring JPA와 프로젝트 구조
Server/Spring

[Spring/공부] Spring JPA와 프로젝트 구조

2024. 11. 20. 09:21
728x90

JPA

객체 지향 언어와 관계형 DB

자바는 객체지향 언어이다. 하지만, 데이터를 관리할 때는 관계형 DB를 많이 사용한다.

이 때 객체지향 언어와 RDB 패러다임의 불일치가 발생한다.

  • 객체지향적 언어 : 캡슐화/상속/다형성 활용하는 것이 목표
  • RDBMS (관계형 DB) : 데이터를 정교하게 구선하는 것이 목표

→ 따라서 CRUD 쿼리를 다 짜고, Java를 SQL로 바꾸고, SQL을 Java로 바꿔야 하는. 즉 하나의 테이블을 만들거나 변경할 때마다 수십개의 쿼리를 짜야하는 문제 발생.

이러한 문제를 해결하기 위해서 JPA 기술이 나오게 되었다.

JPA란?

Java Persistence API : 자바 진영에서 Object-Relational Mapping 기술의 표준으로 사용되는 인터페이스의 모음이다.

ORM 기술이란?

: 애플리케이션의 클래스와 RDB의 테이블을 매핑한다는 뜻이다.

즉 반복적인 CRUD 쿼리문을 짤 필요 없이 자동으로 매핑하여 날려준다.

인터페이스인 만큼, 실제로 구현된 것이 아니라, 개발자가 구현한 클래스와 매핑을 해주기 위해 사용되는 프레임워크로 JPA를 구현한 대표적인 오픈소스로는 Hibernate가 있다.

Spring 프로젝트 설계와 구조

Domain 패키지

  • JPA에서 사용하기 위한 엔티티 클래스 저장 패키지

Controller 패키지

  • Http 요청이 오면, 그에 대한 응답을 내려주는 클래스 → 응답을 위한 과정은 Servie에서 처리

Service 패키지

  • 비즈니스 로직이 필요한 클래스들의 모임 → 가장 복잡한 코드가 들어간다.

Repository 패키지

  • database와 통신하는 계층으로, Spring Data JPA를 이용해서 만든 repository를 이용할 것이다.

Dto 패키지

  • 클라이언트가 body에 담아서 보내는 데이터를 받기 위한 클래스 또는 데이터베이스에서 받은 데이터를 클라이언트에게 보여주기 위한 클래스
  • Database에서 받은 엔티티를 그대로 응답을 주게되면 → 데이터베이스 테이블 설계가 바뀌게되어 엔티티 변경이 생길 시 → 프론트엔드 개발자의 dto가 변경되는 영향을 주게 된다. 따라서 dto를 통해 응답 데이터를 결정하여 좋은 설계를 만들 수 있다.

Converter 패키지

  • entity to dto를 converter에서 진행한다.
  • 추가적인 Entity생성도 converter에서 수행한다.
  • 엔티티 생성을 service에서 하는 경우도 있지만, converter에서 수행할 경우 service는 순수하게 비즈니스 로직에 집중할 수 있어 단일 책임 원칙 측면에서 좋다.

사용 위치

  • service에서 dto 생성 → service에서 converter를 통해 dto를 controller에게 리턴.
  • controller에서 dto생성 → service에서 entity 리턴하고, controller에서 converter를 통해 dto만들어서 응답 주기도 한다.

Domain의 Entity 매핑

Entity 만들기

사용 어노테이션

  • @ Getter : Getter만들어주는 어노테이션
  • @ Entity : 어노테이션을 통해 해당 클래스가 JPA의 엔티티임을 명시
  • @ Builder, @ NoArgsConstructor(access = AccessLevel.PROTECTED)
    @AllArgsConstructor
    • 빌더 패턴을 사용하기 위함 → 생성자를 사용하는 것보다 더욱 편리한 코딩이 가능하다.

Entity내에 enum 타입 사용하기

@Enumerated(EnumType.STRING)으로 사용할 수 있다.

  • EnumType.ORDINAL 사용 시 enum 순서가 저장되는데, 만약 순서가 바뀌게 될 경우 에러 생길 수 있다.

JpaAuditing

Entity 연관 관계 매핑

  • member_prefer 테이블의 경우 member와 food_category 모두에 대한 외래키 갖고 있다.
  • 1:N의 경우 N에 해당하는 테이블이 외래키를 가지며 N에 해당하는 엔티티가 연관관계의 주인이 된다.
  • 1:1의 경우 둘 중 하나만 외래키를 가지면 되므로 원하는 엔티티를 연관관계의 주인으로 설정하면 된다.

@ManytoOne 어노테이션

  • N:1에서 N에 해당하는 엔티티가 1에 해당하는 엔티티와 연관관계를 매핑할 때 사용하는 어노테이션이다.
  • (fetch = FetchType.LAZY)는 지연로딩을 설정하는 것이다.

@JoinColumn 어노테이션

  • 실제 데이터베이스에서 해당 칼럼(외래키)의 이름을 설정하는 것이다.

연간관계의 주인이란?

  • JPA에서는 DB처럼 외래키를 갖는 쪽을 연관관계의 주인이라고 한다.
  • Review가 외래키(member_id)를 갖고 있으므로 Review가 주인
  • mappedBy = "member" → Member는 연관관계의 주인이 아님을 표시

단방향 매핑

연관 관계 주인에게만 연관관계를 주입하는 것.

  • 즉 한쪽만 상대를 참조하는 관계
  • Review는 Member를 알지만, Member는 Review가 있는지 모름.
  • 단점 : 객체 그래프 탐색이 어려움 (ex. member.getReviews()가 불가능)

양방향 매핑

연관 관계 주인이 아닌 엔티티에게도 연관 관계를 주입하는 것.

  • 양쪽이 서로를 참조하는 관계

장점

  • 객체 그래프 탐색으로 인한 이점과, Cascade 설정이 가능하다는 점이다.

Cascade 설정

  • 원래 DB에서의 Cascade는 연관관계의 주인인 테이블에 설정을 해서, 참조 대상인 테이블의 컬럼이 삭제될 때 같이 삭제되거나 변경될 때 같이 변경되는 기능이다.
  • JPA에서는 연관관계의 주인이 아닌, 참조의 대상이 되는 엔티티에 설정해야 한다.

단방향에서 Cascade쓰면 위험한 이유

  • 만약 단방향 매핑만 적용된 상태에서 cascade를 설정할 시, member를 참조하는 review가 삭제될 때 member도 같이 삭제되는 상황이 발생한다.
  • 따라서 양방향 매핑이 있어야 올바르게 cascade의 적용이 가능하다.

Column별 세부적 설정

  • 각 엔티티의 멤버변수 별로 세부적인 설정을 해야한다.
  • Member의 name은 table에서는 varchar인데, JPA는 String으로 되어있다. 따라서 세부적인 설정을 해줘야 한다.
  • @Column(nullable = false, length = 20) 를 통해서 세부적인 설정을 할 수 있다.
  • @Column(columnDefinition = "VARCHAR(15) DEFAULT 'ACTIVE'") Enum은 이렇게 직접 지정할 수 있다.

키워드 정리

Domain

  • 소프트웨어가 다루는 현실 세계의 문제 영역.
  • 비즈니스의 개념, 규칙, 행동을 코드로 추상화한 것.
  • 데이터(상태)와 행동(로직)을 함께 가진다.
  • Entity, Value Object(VO), Repository 등이 도메인을 구성하는 대표적인 것들.
  • 도메인 주도 설계(DDD)에서는 도메인을 중심으로 소프트웨어를 설계한다.

예시)

  • 도서 대여 시스템
  • 회원(Member), 책(Book), 대여 기록(Rental)이 각각 도메인이다.
  • 단순히 "데이터"가 아니라, "대여할 수 있는가?" 같은 비즈니스 로직도 포함한다.
@Entity
public class Member {
    @Id
    private Long id;
    private String name;
    private boolean availableRental; // 대여 가능 여부

    public boolean canRent() {
        return availableRental;
    }
}
  • 여기서 Member는 단순 데이터가 아니라 "대여 가능 여부"라는 비즈니스 로직도 갖고 있음.

양방향 매핑

  • 두 객체가 서로를 참조하는 관계를 설정하는 것.
  • 예를 들어, 부모 → 자식, 자식 → 부모 모두 접근할 수 있도록 매핑.
  • JPA에서는 @OneToMany와 @ManyToOne을 함께 사용. → 단방향 두개 쓰는 것.
  • 연관관계 주인(Owner)를 지정해야 함. (외래키를 실제로 관리하는 쪽)
  • 주의: 무한 순환 참조 (toString(), JSON 변환 시) 가능성 있음.

예시)

부모(Parent)와 자식(Child) 관계

@Entity
public class Parent {
    @Id @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "parent") // 연관관계의 주인: Child
    private List<Child> children = new ArrayList<>();
}

@Entity
public class Child {
    @Id @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "parent_id") // 외래키 컬럼
    private Parent parent;
}
  • Child가 연관관계 주인(Owner) → 실제 parent_id 외래키를 관리함.
  • Parent는 읽기 전용(Non-Owner).

⭐️중요한 점

  • 값을 넣을 때 양쪽 모두 세팅해줘야 함.
Child child = new Child();
Parent parent = new Parent();

child.setParent(parent);
parent.getChildren().add(child);

안 하면 둘 간의 객체 그래프가 일치하지 않아서 문제가 생길 수 있음.

 

N + 1 문제

  • 1번의 메인 쿼리로 N개의 데이터를 조회한 후,
  • 각 데이터마다 1번씩 추가 쿼리를 보내는 현상.
  • 총 쿼리 수: 1 + N
  • → 데이터가 많아질수록 성능이 심각하게 저하된다.
  • 발생 이유: JPA 기본 로딩 전략이 지연 로딩(LAZY)이기 때문.

예시)

회원(Member)와 주문(Order) 관계

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
}

@Entity
public class Order {
    @Id @GeneratedValue
    private Long id;

    private String productName;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;
}

쿼리 발생 흐름

// 모든 회원 조회
List<Member> members = memberRepository.findAll();

// 회원별 주문 조회
for (Member member : members) {
    List<Order> orders = member.getOrders(); // 여기서 N번 추가 조회 발생!
}
  • 1번: SELECT * FROM member
  • N번: 각각 SELECT * FROM order WHERE member_id = ?

→ 총 쿼리 수: 1 + N


해결 방법

  1. Fetch Join 사용
    • 처음부터 연관된 데이터를 함께 가져온다.
    • @Query("SELECT m FROM Member m JOIN FETCH m.orders") List<Member> findAllWithOrders();
  2. EntityGraph 사용
    • 특정 필드를 로딩하도록 명시한다.
    • @EntityGraph(attributePaths = {"orders"}) List<Member> findAll();
  3. Batch Size 조정
    • 다건 조회 시, N개의 쿼리를 IN 절로 묶어서 보낸다.
    • application.properties
    • spring.jpa.properties.hibernate.default_batch_fetch_size=100

구분설명예시 요약

Domain 현실 세계 문제를 소프트웨어로 추상화. 비즈니스 로직도 포함. 회원이 책을 대여할 수 있는지 판단하는 메서드 포함
양방향 매핑 객체 간 서로 참조. 연관관계 주인 설정 필수. Parent-Child 간 @OneToMany / @ManyToOne 사용
N+1 문제 1번 조회 후 N번 추가 조회로 성능 저하 발생. 회원 조회 후, 각 회원 주문을 추가로 N번 조회
728x90
저작자표시 (새창열림)

'Server > Spring' 카테고리의 다른 글

[Spring/프로젝트] Process Builder로 Python 코드 스케줄링  (0) 2025.05.11
[Spring/프로젝트] 카드뉴스 생성하기 Python Pillow + Open AI API  (0) 2025.05.09
[Spring/공부] Spring JPA 고급 활용 (JPQL, Query DSL)  (0) 2024.12.05
[Spring/공부] Spring Boot 코어 개념 정리  (0) 2024.11.05
'Server/Spring' 카테고리의 다른 글
  • [Spring/프로젝트] Process Builder로 Python 코드 스케줄링
  • [Spring/프로젝트] 카드뉴스 생성하기 Python Pillow + Open AI API
  • [Spring/공부] Spring JPA 고급 활용 (JPQL, Query DSL)
  • [Spring/공부] Spring Boot 코어 개념 정리
hyeon.s
hyeon.s
이유있는 코드를 짜자

티스토리툴바