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
해결 방법
- Fetch Join 사용
- 처음부터 연관된 데이터를 함께 가져온다.
- @Query("SELECT m FROM Member m JOIN FETCH m.orders") List<Member> findAllWithOrders();
- EntityGraph 사용
- 특정 필드를 로딩하도록 명시한다.
- @EntityGraph(attributePaths = {"orders"}) List<Member> findAll();
- 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번 조회 |
'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 |