
복잡한 비즈니스 로직을 구현하다 보면 하나의 테이블만으로는 충분하지 않은 상황이 자주 발생합니다. 특히 실무에서는 여러 개의 연관된 테이블 간 데이터를 동시에 조회해야 하는 경우가 빈번한데, 이를 효율적으로 처리하는 것은 개발자의 중요한 역량 중 하나입니다. 이 글에서는 JPA(Java Persistence API)를 활용하여 3개 이상의 테이블을 조인하는 방법을 다양한 방식으로 소개하고, 각각의 장단점 및 성능 고려 사항까지 상세히 설명합니다.
📚 목차
- 1. 단일 테이블만으로는 부족할 때
- 2. JPA의 조인 개념 빠르게 정리
- 3. 세 개 이상의 테이블 조인 – 대표 예제로 이해하기
- 4. 방법 ①: JPQL을 활용한 다중 테이블 조인
- 5. 방법 ②: DTO 직접 매핑을 통한 성능 최적화
- 6. 방법 ③: QueryDSL을 활용한 정적 타입 기반 조인
- 7. 성능 최적화를 위한 전략
- 8. 실무에서의 실수와 유의사항
- 9. 결론: 조인의 핵심은 설계에 있다
1. 단일 테이블만으로는 부족할 때
현실 세계의 복잡한 비즈니스 로직은 종종 여러 엔터티 간의 관계를 필요로 합니다. 예를 들어 쇼핑몰을 구축한다고 가정해 봅시다. 사용자는 여러 개의 주문을 할 수 있고, 각 주문은 다양한 상품과 배송 정보를 포함합니다. 이러한 구조에서는 단일 테이블만으로 필요한 데이터를 추출하기란 사실상 불가능합니다.
JPA는 객체 지향적인 방식으로 데이터베이스와 상호작용할 수 있도록 도와주며, SQL을 직접 작성하지 않아도 다양한 조인을 지원합니다. 하지만 세 개 이상의 테이블을 조인할 경우, 조인 순서, 연관 관계 설정, 페치 전략 등 여러 고려 사항이 뒤따릅니다. 또한 성능 저하를 유발하는 N+1 문제, 지연 로딩에 따른 데이터 누락 등 실무에서 자주 마주치는 이슈도 존재합니다.
따라서 본 글에서는 다음과 같은 관점으로 세 개 이상의 테이블 조인을 단계별로 다뤄보겠습니다.
- JPA의 조인 기본 개념과 연관관계 매핑 방식
- JPQL, DTO, QueryDSL을 활용한 다양한 조인 구현
- 조인 시 발생하는 성능 이슈와 그에 대한 대응 전략
이제부터 각각의 방법을 예제와 함께 깊이 있게 살펴보며, 어떤 상황에서 어떤 전략을 선택해야 하는지 통찰을 제공하겠습니다.

2. JPA의 조인 개념 빠르게 정리
JPA는 객체와 관계형 데이터베이스 간의 간극을 메우기 위해 다양한 연관관계 매핑을 제공합니다. 이를 통해 객체 지향적인 방식으로 테이블 간의 관계를 표현하고, 필요한 데이터를 효과적으로 조회할 수 있습니다. 하지만 조인의 목적과 방식에 따라 적절한 매핑 전략을 선택해야 하며, 이 전략에 따라 성능과 유지보수성이 크게 달라집니다.
2-1. 연관관계 매핑의 핵심 어노테이션
어노테이션 | 설명 |
---|---|
@OneToOne | 1:1 관계. 주로 사용자-프로필 등 하나의 엔티티가 다른 하나의 엔티티와 고정적으로 연결될 때 사용 |
@OneToMany | 1:N 관계. 예: 하나의 주문은 여러 개의 상품을 포함 |
@ManyToOne | N:1 관계. 자주 사용되며, 실제로 @OneToMany보다는 이 구조가 더 효율적임 |
@ManyToMany | N:M 관계. 중간 테이블이 필요하며, 실무에서는 거의 사용하지 않고 별도의 조인 엔티티로 분리하는 것이 일반적 |
2-2. 조인을 처리하는 3가지 방식
JPA에서 여러 테이블을 조인하는 방식은 크게 세 가지로 나눌 수 있습니다.
- JPQL (Java Persistence Query Language): 객체지향 쿼리 언어로, SQL과 유사하지만 Entity 기반으로 작성됩니다.
- Criteria API: 타입 세이프한 쿼리를 작성할 수 있는 방식으로, 유지보수와 리팩토링에 유리하지만 가독성이 떨어질 수 있습니다.
- Native Query: 복잡한 SQL을 직접 작성할 수 있는 방법으로, 성능 튜닝이 필요할 때 사용되지만 JPA의 장점을 희석시킬 수 있습니다.
2-3. 단일 조인과 다중 조인의 흐름 차이
두 개의 테이블을 조인하는 경우에는 비교적 간단하게 관계를 명시하고 데이터를 조회할 수 있지만, 세 개 이상의 테이블을 조인할 때는 조인 순서, 기준 필드, Fetch 전략 등이 복잡해지므로 보다 세심한 설계와 구현이 필요합니다.
SELECT o FROM Order o
JOIN o.member m
JOIN o.delivery d
WHERE m.name = :name
위 예제는 `Order`, `Member`, `Delivery` 세 개의 테이블을 조인하여 특정 사용자의 주문 정보를 조회하는 간단한 JPQL입니다. 다음 장에서는 실제 예제를 통해 3개 이상의 테이블을 어떻게 조인할 수 있는지 도메인 모델과 함께 상세히 설명하겠습니다.
3. 세 개 이상의 테이블 조인 – 대표 예제로 이해하기
JPA에서 3개 이상의 테이블을 조인할 때는 단순한 연관관계만으로는 부족하며, 비즈니스 로직에 따라 다양한 방향의 데이터 흐름을 고려해야 합니다. 이를 직관적으로 이해하기 위해 하나의 가상 시나리오를 설정하고 이를 기반으로 조인의 구성을 설명하겠습니다.
3-1. 예제 시나리오: 쇼핑몰 주문 시스템
이번 예제에서는 쇼핑몰 주문 시스템을 다룹니다. 주요 엔티티는 다음과 같습니다.
- Member: 사용자 정보를 담고 있는 테이블
- Order: 사용자의 주문 정보를 담고 있는 테이블
- Delivery: 주문의 배송 정보를 담고 있는 테이블
- OrderItem: 주문한 상품의 세부 항목을 담고 있는 테이블
- Item: 실제 판매되는 상품 정보를 담고 있는 테이블
3-2. 엔티티 간 관계 구조
각 엔티티는 아래와 같이 연관관계를 맺고 있습니다.
- Member 1 : N Order
- Order 1 : 1 Delivery
- Order 1 : N OrderItem
- OrderItem N : 1 Item
3-3. ERD로 보는 관계 구조
간단한 ERD 구조는 다음과 같습니다.
Member └── Order ├── Delivery └── OrderItem └── Item
이 구조를 기반으로 세 개 이상의 테이블을 조인하여 특정 사용자의 주문 내역과 그에 따른 상품 및 배송 정보를 조회하는 방식으로 예제를 확장해 보겠습니다.
3-4. 실제 조인 흐름 예시
아래는 특정 회원 이름을 기준으로, 해당 회원이 주문한 상품의 이름과 배송 상태를 함께 조회하는 JPQL 예시입니다.
SELECT o FROM Order o
JOIN o.member m
JOIN o.delivery d
JOIN o.orderItems oi
JOIN oi.item i
WHERE m.name = :name
위 쿼리는 다음과 같은 조건을 충족합니다.
- 회원 이름으로 주문 정보를 필터링
- 해당 주문의 배송 상태 조회
- 주문 내역에 포함된 상품명까지 조회
이처럼 조인의 깊이가 깊어질수록, 연관관계의 명확한 이해와 성능 고려가 필수적입니다. 다음 장에서는 JPQL을 활용하여 이러한 조인을 실제로 구현하는 방법과 주의할 점을 구체적으로 다뤄보겠습니다.
4. 방법 ①: JPQL을 활용한 다중 테이블 조인
JPA에서 가장 일반적으로 사용하는 조인 방식은 JPQL(Java Persistence Query Language)을 활용하는 것입니다. 이는 SQL과 유사하지만, 테이블이 아닌 엔티티를 대상으로 한다는 점에서 객체지향적인 장점을 제공합니다. 세 개 이상의 테이블을 조인할 때도 JPQL은 직관적인 쿼리 구성이 가능하며, 적절한 페치 전략과 함께 사용하면 성능 이슈도 충분히 제어할 수 있습니다.
4-1. 기본 구조와 JOIN 키워드
JPQL에서의 조인은 SQL의 조인과 유사하게 사용되며, 연관관계가 맺어진 엔티티 간에만 사용할 수 있습니다. `JOIN`, `LEFT JOIN`, `JOIN FETCH` 등의 키워드를 사용하여 필요한 데이터를 가져옵니다.
SELECT o FROM Order o
JOIN o.member m
JOIN o.delivery d
JOIN o.orderItems oi
JOIN oi.item i
WHERE m.name = :name
이 쿼리는 다음 네 가지 관계를 순차적으로 조인합니다:
Order
↔Member
Order
↔Delivery
Order
↔OrderItem
OrderItem
↔Item
결과적으로 하나의 JPQL 쿼리로 사용자 이름 기반의 모든 주문 및 상품, 배송 정보를 조회할 수 있습니다.
4-2. JOIN FETCH로 N+1 문제 방지
JPA는 지연 로딩(LAZY)이 기본 설정이기 때문에, 연관된 엔티티를 하나씩 가져오는 과정에서 N+1 문제가 발생할 수 있습니다. 이를 해결하기 위해 `JOIN FETCH`를 사용하면 한번의 쿼리로 모든 연관 데이터를 함께 가져올 수 있습니다.
SELECT o FROM Order o
JOIN FETCH o.member m
JOIN FETCH o.delivery d
JOIN FETCH o.orderItems oi
JOIN FETCH oi.item i
WHERE m.name = :name
이 방식은 연관된 모든 데이터를 조인하여 한 번에 조회하기 때문에 성능상 이점을 얻을 수 있습니다. 다만 주의해야 할 점은 다음과 같습니다:
- FETCH JOIN은 컬렉션 조인 시 중복 데이터가 발생할 수 있으므로
DISTINCT
키워드와 함께 사용하는 것이 일반적입니다. - 다수의 컬렉션을 FETCH JOIN하면 JPA의 내부 제약 때문에 예외가 발생할 수 있습니다.
4-3. 복잡한 조건 조인
JPA 2.1부터는 `ON` 절을 사용한 조인이 가능해져 더욱 유연한 조건 조인이 가능합니다. 예를 들어, 특정 조건을 만족하는 상품만 조인하고 싶을 때 다음과 같이 구성할 수 있습니다.
SELECT o FROM Order o
JOIN o.orderItems oi
JOIN oi.item i ON i.price > 10000
WHERE o.status = 'DELIVERED'
이 방식은 JPQL을 사용하는 가장 유연한 형태로, 복잡한 비즈니스 조건을 만족하는 데이터 조합을 효과적으로 처리할 수 있게 합니다.
다음 장에서는 DTO(Data Transfer Object)를 활용하여, 필요한 필드만을 가져오는 방식으로 성능을 극대화하는 전략을 소개하겠습니다.
5. 방법 ②: DTO 직접 매핑을 통한 성능 최적화
복잡한 조인을 수행한 후 모든 데이터를 엔티티 형태로 가져오는 것은 필요 이상의 자원 소비로 이어질 수 있습니다. 특히 화면에 일부 필드만 출력하거나, 불변 객체로 데이터를 전달해야 할 때는 DTO(Data Transfer Object)를 사용하는 것이 훨씬 효율적입니다.
DTO를 직접 매핑하면 다음과 같은 장점이 있습니다.
- 불필요한 연관 데이터를 로딩하지 않음
- 쿼리 결과의 크기를 최소화하여 네트워크 비용 절감
- API 응답 구조에 맞춘 데이터 가공 용이
5-1. DTO 클래스 설계 예
다음은 주문 정보를 반환하기 위한 DTO 예제입니다.
public class OrderSimpleDto {
private String memberName;
private LocalDateTime orderDate;
private String deliveryAddress;
private String itemName;
public OrderSimpleDto(String memberName, LocalDateTime orderDate,
String deliveryAddress, String itemName) {
this.memberName = memberName;
this.orderDate = orderDate;
this.deliveryAddress = deliveryAddress;
this.itemName = itemName;
}
}
5-2. DTO로 매핑하는 JPQL 예제
JPA는 생성자 방식으로 DTO에 직접 데이터를 주입할 수 있도록 `new` 키워드를 제공합니다. 이를 활용하면 필요한 데이터만 선별하여 가져올 수 있습니다.
SELECT new com.example.dto.OrderSimpleDto(
m.name,
o.orderDate,
d.address,
i.name
)
FROM Order o
JOIN o.member m
JOIN o.delivery d
JOIN o.orderItems oi
JOIN oi.item i
WHERE m.name = :name
위 예제는 Order
, Member
, Delivery
, OrderItem
, Item
엔티티를 조인한 후, DTO에 필요한 필드만 골라서 주입하는 방식입니다. Entity 전체를 불러오지 않고 필요한 필드만 조회하기 때문에 성능이 향상되며, 데이터 노출 범위도 제한할 수 있습니다.
5-3. DTO 매핑의 한계와 고려사항
DTO 매핑은 정해진 필드만 조회하기 때문에 다음과 같은 한계도 존재합니다.
- 동적으로 조회 필드를 변경하기 어려움
- 쿼리가 복잡해질수록 유지보수가 어려움
- 엔티티 변경 시 DTO와 쿼리 모두 수정 필요
그럼에도 불구하고 성능이 중요한 화면이나 API 응답에서는 DTO 매핑이 매우 유효한 전략입니다. 특히 모바일 환경에서 JSON 응답 크기를 줄이는 데 탁월한 효과를 발휘합니다.
다음 장에서는 정적 타입 쿼리를 지원하는 QueryDSL을 통해 다중 테이블 조인을 더욱 견고하게 처리하는 방법을 살펴보겠습니다.
6. 방법 ③: QueryDSL을 활용한 정적 타입 기반 조인
QueryDSL은 정적 타입을 기반으로 쿼리를 생성할 수 있도록 지원하는 프레임워크로, 복잡한 조인을 보다 안정적으로 구현할 수 있는 강력한 도구입니다. 컴파일 타임에 쿼리 오류를 발견할 수 있으며, IDE 자동완성 기능을 적극 활용할 수 있어 개발 생산성이 높아집니다.
6-1. QueryDSL의 장점
QueryDSL은 다음과 같은 이점을 제공합니다:
- 정적 타입 기반으로 문법 오류 사전 차단
- 가독성과 유지보수가 뛰어난 코드
- 동적 쿼리 구성의 유연성
- JPA와의 높은 호환성
특히 3개 이상의 테이블을 조인할 때, QueryDSL은 JPQL보다 더욱 안정적이고 명시적인 코드 구성이 가능합니다.
6-2. 기본 조인 예제
QueryDSL을 사용하여 `Order`, `Member`, `Delivery`, `OrderItem`, `Item`을 조인한 예시는 다음과 같습니다.
QOrder order = QOrder.order;
QMember member = QMember.member;
QDelivery delivery = QDelivery.delivery;
QOrderItem orderItem = QOrderItem.orderItem;
QItem item = QItem.item;
List<OrderSimpleDto> result = queryFactory
.select(new QOrderSimpleDto(
member.name,
order.orderDate,
delivery.address,
item.name
))
.from(order)
.join(order.member, member)
.join(order.delivery, delivery)
.join(order.orderItems, orderItem)
.join(orderItem.item, item)
.where(member.name.eq("홍길동"))
.fetch();
이 쿼리는 다음과 같은 조인을 수행합니다:
- Order ↔ Member
- Order ↔ Delivery
- Order ↔ OrderItem
- OrderItem ↔ Item
그리고 결과는 DTO(`OrderSimpleDto`)로 매핑됩니다. DTO에 사용하는 생성자는 @QueryProjection
어노테이션으로 처리할 수 있습니다.
public class OrderSimpleDto {
private String memberName;
private LocalDateTime orderDate;
private String deliveryAddress;
private String itemName;
@QueryProjection
public OrderSimpleDto(String memberName, LocalDateTime orderDate,
String deliveryAddress, String itemName) {
this.memberName = memberName;
this.orderDate = orderDate;
this.deliveryAddress = deliveryAddress;
this.itemName = itemName;
}
}
6-3. QueryDSL 사용 시 주의점
- 빌드 시 Q 클래스가 생성되지 않으면 querydsl-apt 설정 확인 필요
- JPAQueryFactory를 빈으로 등록해야 함
- 프로젝트 구조에 따라 QueryDSL 설정이 복잡할 수 있음
그럼에도 불구하고, QueryDSL은 복잡한 비즈니스 로직을 안정적으로 쿼리해야 하는 상황에서 매우 유용한 도구입니다. 특히 유지보수성 높은 코드를 선호하는 팀 환경에 적극 추천됩니다.
다음 장에서는 이러한 다양한 조인 방식들에 대해 성능 관점에서 어떤 전략을 취해야 하는지 심층적으로 살펴보겠습니다.
7. 성능 최적화를 위한 전략
JPA에서 다중 테이블 조인을 활용할 때, 단순히 쿼리가 잘 동작하는 것만으로는 충분하지 않습니다. 실서비스에서는 수천~수만 건의 데이터가 쿼리 대상이 되므로, 쿼리 성능에 민감할 수밖에 없습니다. 특히 3개 이상의 테이블을 조인할 경우, 잘못된 설계는 곧 시스템 전체의 병목으로 이어질 수 있습니다.
7-1. FetchType 전략 이해: LAZY vs EAGER
JPA 연관관계는 FetchType.LAZY
와 FetchType.EAGER
중 하나로 설정할 수 있으며, 기본 전략은 다음과 같습니다:
관계 | 기본 FetchType |
---|---|
@ManyToOne / @OneToOne | EAGER |
@OneToMany / @ManyToMany | LAZY |
실무에서는 대부분 LAZY
로 설정하고, 필요한 경우 JOIN FETCH
나 EntityGraph
를 활용하는 방식이 더 안전합니다. EAGER
는 예기치 않은 쿼리를 유발할 수 있기 때문에 주의가 필요합니다.
7-2. Batch Size 설정
컬렉션 조인을 지연 로딩(LAZY)하면서도 효율적으로 처리하려면 hibernate.default_batch_fetch_size
를 설정해주는 것이 좋습니다. 이 설정은 N+1 문제를 완화하는 데 유용합니다.
spring.jpa.properties.hibernate.default_batch_fetch_size=100
이 설정을 통해 최대 100개의 연관 엔티티를 한 번에 로딩할 수 있으므로, 성능 저하 없이도 지연 로딩 전략을 유지할 수 있습니다.
7-3. EntityGraph의 활용
@EntityGraph
는 JPQL에서 페치 조인을 선언적으로 대체할 수 있는 방법입니다. 복잡한 쿼리를 직접 작성하지 않아도 되며, 재사용성이 높아 코드 유지보수에 유리합니다.
@EntityGraph(attributePaths = {"member", "delivery", "orderItems.item"})
@Query("SELECT o FROM Order o WHERE o.status = :status")
List<Order> findByStatusWithGraph(@Param("status") OrderStatus status);
이 방식은 `JOIN FETCH`와 거의 동일한 효과를 내면서도, 코드의 분리를 유지할 수 있어 아키텍처적 이점이 있습니다.
7-4. 복잡한 쿼리에서는 Native SQL 고려
정말 복잡하거나 고성능이 요구되는 쿼리에서는 Native SQL을 사용하는 것도 전략이 될 수 있습니다. 단, 이 경우는 JPA의 장점을 일부 포기하는 선택이므로 신중히 판단해야 합니다.
예를 들어, 통계성 조회, 집계 함수, 복잡한 서브쿼리 등은 JPA보다 SQL이 유리할 수 있습니다.
다음 장에서는 실무에서 조인 전략을 잘못 적용해 생기는 주요 실수 사례를 정리하고, 이를 방지하기 위한 실천적 팁을 소개합니다.
8. 실무에서의 실수와 유의사항
JPA로 다중 테이블 조인을 구현할 때, 설계와 구현 모두에서 발생할 수 있는 실수가 존재합니다. 단순한 기능 구현에만 집중하면 데이터 중복, 성능 저하, 유지보수 어려움 등 다양한 문제에 직면하게 됩니다. 여기서는 실무에서 자주 발생하는 실수와 이를 예방하기 위한 핵심 포인트를 소개합니다.
8-1. Cartesian Product 발생
다수의 OneToMany
연관관계를 동시에 FETCH JOIN할 경우, 조인된 테이블의 모든 조합이 반환되어 중복 데이터가 발생합니다. 이를 Cartesian Product라 하며, 아래와 같은 상황에서 자주 발생합니다:
SELECT o FROM Order o
JOIN FETCH o.orderItems oi
JOIN FETCH oi.item i
JOIN FETCH o.member m
위 쿼리는 Order
와 OrderItem
이 1:N, OrderItem
과 Item
이 N:1 구조일 때, Order
의 수만큼 반복된 Item
데이터가 생겨날 수 있습니다.
해결 방법: 중복 방지를 위해 DISTINCT
를 사용하거나, DTO로 필요한 데이터만 조회하는 방식으로 전환하는 것이 안전합니다.
8-2. 무분별한 EAGER 로딩
FetchType.EAGER
는 연관 데이터를 항상 함께 로딩하는 방식이므로, 불필요한 쿼리가 다수 발생할 수 있습니다. 특히 뷰에서 사용하지 않는 연관 객체까지 자동으로 로딩되며 성능 저하를 유발할 수 있습니다.
해결 방법: 기본 전략은 LAZY
로 설정하고, 필요한 시점에 명시적으로 JOIN FETCH
또는 EntityGraph
로 제어합니다.
8-3. 쿼리 튜닝의 타이밍을 놓침
JPA는 초기에 학습곡선이 낮아 빠른 개발이 가능하지만, 일정 이상 복잡해지면 성능 문제가 나타납니다. 그럼에도 불구하고 JPA가 자동으로 성능을 최적화해줄 것이라는 착각은 실무에서 큰 문제로 이어질 수 있습니다.
해결 방법:
- JPA는 설계 초기에 성능 관점도 함께 고려해야 합니다.
- 조회 빈도 높은 쿼리는 반드시 DTO 매핑 또는 인덱스 튜닝 검토
- 쿼리 로그 확인과
Hibernate Statistics
사용을 통한 병목 파악
8-4. 연관 관계 설계 과잉
모든 테이블을 JPA 연관관계로 매핑하려는 시도는 오히려 관리 복잡성을 증가시킵니다. 특히 단방향이면 충분한 관계를 양방향으로 구성하거나, 외부 시스템과 연동되는 복잡한 테이블까지 무리하게 연동하려는 시도는 피해야 합니다.
해결 방법: 핵심 도메인에만 집중하여 연관관계를 설계하고, 단순 참조는 ID 기반 매핑으로 대체하는 것이 효율적입니다.
이제 마지막 장에서는 지금까지의 내용을 정리하고, JPA 조인을 바라보는 전략적 시각을 공유하겠습니다.
9. 결론: 조인의 핵심은 설계에 있다
JPA를 활용한 세 개 이상의 테이블 조인은 단순한 기술적 구현을 넘어, 전반적인 애플리케이션 구조와 성능 전략의 중심에 있습니다. 우리는 JPQL, DTO 매핑, QueryDSL과 같은 다양한 방식으로 다중 조인을 처리할 수 있지만, 어떤 기술을 선택하든 결국 중요한 것은 설계입니다.
불필요한 조인은 최소화하고, 필요한 데이터만 정확히 조회하며, 연관관계의 방향성과 로딩 전략을 체계적으로 구성하는 것. 이것이 바로 JPA 조인을 실무에 성공적으로 적용하는 핵심 원리입니다.
이번 글에서는 다음과 같은 메시지를 전달하고자 했습니다:
- 3개 이상의 테이블을 조인하는 상황은 실무에서 매우 흔하며, 다양한 전략이 필요합니다.
- JPQL, DTO, QueryDSL 각각의 장단점을 이해하고, 상황에 맞게 선택하는 것이 중요합니다.
- 성능 문제를 예방하기 위해 Fetch 전략, Batch 설정, EntityGraph 등을 활용해야 합니다.
- 실수로 인해 발생할 수 있는 중복 조회, N+1 문제, 불필요한 관계 매핑 등을 사전에 방지해야 합니다.
JPA는 단순한 ORM 툴을 넘어서, 도메인 주도 설계를 실현하는 강력한 기반입니다. 하지만 그 힘은 제대로 된 설계와 전략적인 사용에서 비롯됩니다. 이 글이 여러분의 JPA 설계와 구현에 실질적인 도움이 되기를 바랍니다.
조인은 끝이 아니라, 설계에 대한 깊은 이해의 시작입니다.