티스토리 뷰
개요
- ORM(Object-Relational Mapping)은 객체 지향 프로그래밍 언어의 객체와 관계형 데이터베이스의 데이터를 매핑해주는 기술
- 이를 통해 개발자는 데이터베이스 테이블 대신 자바 객체로 비즈니스 로직을 구현할 수 있으며, SQL 대신 객체 지향 코드로 데이터 작업을 수행할 수 있음
장점
생산성 향상
- 반복적인 SQL 작성 없이 객체 조작으로 개발 가능
유지보수성 개선
- 데이터베이스와 비즈니스 로직이 분리되어 요구사항 변경 시 유연하게 대응
데이터베이스 독립성
- SQL 추상화 계층을 제공하여 특정 벤더에 종속되지 않는 코드를 작성할 수 있음
단점
성능 이슈
- 복잡한 쿼리나 대용량 데이터 처리 시 직접 SQL을 사용한 최적화보다 한계가 있을 수 있음
러닝 커브
- ORM 프레임워크의 내부 동작(영속성 관리, 캐싱, 연관관계 매핑 등)에 대한 이해가 필요
ORM의 기본 개념 및 활용
1. 객체와 테이블의 매핑
- ORM은 클래스와 테이블, 객체의 필드와 컬럼을 매핑
- 예를 들어, 자바의 User 클래스를 데이터베이스의 users 테이블에 대응시키면, 클래스의 각 필드가 테이블 컬럼과 매핑되어 자동으로 CRUD 작업이 수행
2. 영속성(Persistence) 관리
- ORM 프레임워크는 객체의 상태를 관리하고 데이터베이스와 동기화하는 역할
- 객체를 영속성 컨텍스트에 저장하면 프레임워크가 자동으로 INSERT, UPDATE, DELETE 쿼리를 실행하여 데이터베이스에 반영
3. 캐싱과 지연 로딩
캐싱
- ORM은 동일한 데이터를 여러 번 조회할 때 1차(세션 단위) 또는 2차(애플리케이션 단위) 캐시를 활용해 데이터베이스 접근을 줄임
지연 로딩(Lazy Loading)
- 필요할 때만 연관 데이터를 로드하는 방식으로 초기 로딩 속도를 높이고 불필요한 데이터를 미리 조회하지 않아 메모리 사용을 최적화
자바 예시 코드 (JPA 활용)
1. User 엔티티 클래스
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Column;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(unique = true, nullable = false)
private String email;
// 기본 생성자
public User() {}
// 생성자
public User(String name, String email) {
this.name = name;
this.email = email;
}
// getter & setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
2. 연관관계 매핑 예시: User와 Order 엔티티
import javax.persistence.*;
import java.util.List;
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String product;
@Column(nullable = false)
private int quantity;
// 단방향 매핑: 하나의 주문은 한 명의 사용자에 속함
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
// 기본 생성자, getter, setter 등 생략
}
보충 설명
연관관계 매핑: 위 예시에서 @ManyToOne 어노테이션을 통해 Order 엔티티와 User 엔티티 간의 관계를 설정
- fetch 속성 - FetchType.LAZY를 사용하여 연관 데이터인 User 객체는 실제로 필요할 때만 로딩하도록 설정
3. DAO (Repository) 클래스 예시
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import java.util.List;
public class UserDAO {
@PersistenceContext
private EntityManager entityManager;
// 사용자 저장
@Transactional
public void save(User user) {
entityManager.persist(user);
}
// ID로 사용자 조회
public User findById(Long id) {
return entityManager.find(User.class, id);
}
// 전체 사용자 조회
public List<User> findAll() {
return entityManager.createQuery("SELECT u FROM User u", User.class)
.getResultList();
}
// 사용자 삭제
@Transactional
public void delete(User user) {
entityManager.remove(user);
}
}
연관관계 매핑
개념
- 객체 간의 연관관계를 데이터베이스의 외래키 관계로 매핑하는 작업
- 예를 들어, 1:1, 1:N, N:M 등 다양한 관계를 설정할 수 있음
주의사항
양방향 vs 단방향
- 양방향 매핑은 두 객체 모두 서로를 참조하므로 설계가 복잡해질 수 있음
- 단방향 매핑은 상대적으로 단순하지만, 필요한 경우 양방향 매핑을 통해 데이터 접근을 유연하게 할 수 있음
연관관계 주인
- 매핑 시 어떤 엔티티가 외래키를 관리할지(연관관계의 주인)를 명확히 설정해야 함
- 주인은 @JoinColumn을 사용하여 외래키를 소유하게 됨
지연 로딩과의 결합
- 연관관계 매핑에서 연관 객체를 불필요하게 미리 로딩하면 성능에 영향을 줄 수 있으므로, 지연 로딩 설정이 중요
지연 로딩 (Lazy Loading)
개념
- 연관된 엔티티의 데이터를 실제로 사용할 때까지 로딩을 지연하는 전략
- 예를 들어, 사용자의 주문 목록을 조회할 때 사용자가 실제로 주문 정보를 요청할 때만 데이터베이스에 쿼리를 실행
장점
- 초기 로딩 속도가 빠르며, 불필요한 데이터 로딩을 방지하여 메모리 사용을 최적화
단점
- 연관 데이터를 사용하는 시점에 추가 쿼리가 발생할 수 있으므로, 이를 적절히 관리하지 않으면 성능 저하나 N+1 문제를 유발할 수 있음
Eager Loading (즉시 로딩)
개념
- 연관된 데이터나 객체를 부모 엔티티와 함께 미리 로딩하는 전략
- 예를 들어, 부모 엔티티를 조회할 때 모든 연관된 자식 엔티티를 함께 조회하여 한 번에 데이터를 불러옴
장점
- 추가적인 데이터 접근 시 추가 쿼리 없이 이미 로딩된 데이터를 바로 사용할 수 있어, 연관 데이터 접근이 빈번할 때 유리
단점
- 초기 로딩 시 모든 데이터를 한 번에 불러오기 때문에 불필요한 데이터까지 로드되어 메모리 사용량이 증가할 수 있으며, 초기 응답 속도가 느려질 수 있음
주요 차이점
N+1 문제
개념
- 지연 로딩 설정된 연관 데이터를 조회할 때, 부모 객체를 조회한 후 각 부모 객체마다 연관된 자식 데이터를 개별 쿼리로 호출하여 발생하는 문제
- 예를 들어, 10명의 사용자를 조회한 후 각각의 주문 목록을 지연 로딩으로 호출하면 총 1(부모 조회) + 10(각 사용자 주문 조회) = 11번의 쿼리가 실행
해결 방법
패치 조인(Fetch Join)
- JPQL에서 JOIN FETCH를 사용해 연관 데이터를 함께 조회하면, 단일 쿼리로 데이터를 불러올 수 있음
배치 사이즈 설정
- @BatchSize 어노테이션이나 ORM 설정을 통해 연관 객체 조회 시 한 번에 여러 건의 데이터를 묶어 조회하도록 설정할 수 있음
엔티티 그래프(Entity Graph)
- 필요한 연관 데이터를 미리 정의하여 로딩 전략을 제어하는 방법도 있음
보충 설명
N+1 문제는 특히 대량의 데이터를 다룰 때 심각한 성능 저하를 유발할 수 있으므로, ORM 사용 시 쿼리 실행 횟수를 면밀히 모니터링하고 패치 전략을 적절하게 설정하는 것이 매우 중요
면접 대비 질문
기본 질문
Q1. ORM이란 무엇이며, 주요 장점은 무엇인가요?
모범 답안
ORM은 객체 지향 언어의 객체와 관계형 데이터베이스의 데이터를 매핑하는 기술로, SQL 대신 객체를 다룸으로써 개발 생산성을 높이고 유지보수성을 개선하는 장점이 있습니다.
Q2. JPA와 Hibernate의 관계에 대해 설명해 주세요.
모범 답안
JPA는 ORM 표준 인터페이스이며, Hibernate는 그 구현체 중 하나로, JPA의 기능 외에도 추가적인 최적화와 확장 기능을 제공합니다.
심화 질문
Q1. 연관관계 매핑에서 양방향과 단방향 매핑의 차이점은 무엇이며, 각각의 장단점은 무엇인가요?
모범 답안
단방향 매핑은 한쪽 엔티티에서만 다른 엔티티를 참조하는 방식으로 설계가 간단하며, 필요 시 양방향 매핑을 통해 양쪽에서 데이터 접근이 가능해지지만, 설계와 유지보수가 복잡해질 수 있습니다.
Q2. 지연 로딩(Lazy Loading)과 즉시 로딩(Eager Loading)의 차이점과 각각의 장단점을 설명해 주세요.
모범 답안:
지연 로딩은 실제 필요한 시점에 연관 데이터를 조회하여 초기 로딩 속도와 메모리 사용을 최적화하지만, 추가 쿼리로 인한 N+1 문제가 발생할 수 있습니다. 반면, 즉시 로딩은 객체 생성 시 모든 연관 데이터를 함께 로드하여 쿼리 수는 줄이나 초기 로딩 시간이 길어질 수 있습니다.
압박 질문
Q1. N+1 문제란 무엇이며, 이를 해결하기 위한 방법을 구체적으로 설명해 주세요.
모범 답안:
N+1 문제는 부모 엔티티를 조회한 후, 각 부모 엔티티의 연관 데이터를 지연 로딩으로 조회할 때 발생하는 문제로, 패치 조인, 배치 사이즈 설정, 엔티티 그래프 등을 사용하여 한 번의 쿼리 또는 묶음 조회로 해결할 수 있습니다.
Q2. 복잡한 도메인 모델을 ORM으로 매핑할 때 발생할 수 있는 성능 및 데이터 정합성 문제와 이를 해결하기 위한 전략은 무엇인가요?
모범 답안:
복잡한 도메인 모델에서는 다중 연관관계와 상속 구조로 인해 매핑이 복잡해지고 성능 저하나 데이터 정합성 문제가 발생할 수 있습니다. 이를 해결하기 위해 도메인 모델을 단순화하고, 연관관계 매핑 시 지연 로딩 및 캐시 전략을 적절히 설정하며, 필요에 따라 네이티브 SQL이나 JPQL을 활용해 쿼리를 최적화해야 합니다.
'CS > 데이터베이스' 카테고리의 다른 글
[DB] 분산 시스템과 Sharding (0) | 2025.02.10 |
---|---|
[DB] B-tree와 B+tree (0) | 2025.02.07 |
[DB] 트랜잭션 (0) | 2025.02.06 |
[DB] SQL 기본 (0) | 2025.02.05 |
[DB] 데이터 모델링 및 설계 (1) | 2025.02.04 |
- Total
- Today
- Yesterday
- 분할 정복
- CPU 스케줄링
- i/o모델
- 자바
- 스프링
- HTTP
- 우아한 테크코스
- 탐색 알고리즘
- restful api
- 프리코스
- TRIE
- Spring
- Java
- 우선순위 큐
- B+Tree
- CS
- 알고리즘
- db
- 그리디 알고리즘
- Spring Boot
- 백트래킹
- 동적 프로그래밍
- 해시 테이블
- 운영체제
- 데이터베이스
- devops
- 자료구조
- k8
- MSA
- 우테코
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |