[멋사 클라우드 5기] Day 29 & 30 - 게시판 구현을 통해 확인한 Spring Data JPA

2026. 3. 12. 16:10·Learning Log

findById vs getReferenceById 차이

메서드 사용하는 곳 대상
findById getPost(), getPostWithComments() Post 조회
getReferenceById registerPost(), updatePost()) User, Post

findById — 즉시 SELECT 실행

// PostService.java:29-33
@Transactional
public PostDto getPost(Long pid) {
    return postRepository.findById(pid)           // ← 이 시점에 SELECT 실행
                         .map(PostDto::from)
                         .orElseThrow(() -> new NoSuchElementException("해당 게시글 존재 x"));
}

findById(pid)가 호출되는 그 즉시 DB에 SELECT 쿼리를 날린다.

-- findById(pid) 호출 시 즉시 실행
SELECT p1_0.pid, p1_0.category_type, p1_0.content, p1_0.created_by,
       p1_0.created_date, p1_0.modified_by, p1_0.modified_date, p1_0.title,
       u1_0.uid, u1_0.created_by, u1_0.created_date, u1_0.email,
       u1_0.modified_by, u1_0.modified_date, u1_0.password, u1_0.role_type, u1_0.username
FROM post p1_0
LEFT JOIN user u1_0 ON u1_0.uid = p1_0.uid
WHERE p1_0.pid = ?
  • Optional<Post>를 반환한다.
  • DB에 해당 row가 없으면 Optional.empty()가 온다.
  • 결과: 실제 데이터가 채워진 진짜 Post 엔티티 객체를 반환한다.

getReferenceById — SELECT 없이 프록시 반환

// PostService.java:51
User user = userRepository.getReferenceById(postDto.getUserDto().getUid());
// ↑ 이 시점에서 SELECT 실행 안 함! 프록시 객체만 반환
// PostService.java:60
Post post = postRepository.getReferenceById(pid);
// ↑ 역시 SELECT 실행 안 함! 프록시 객체만 반환

 

getReferenceById()는 호출 시점에 DB에 쿼리를 보내지 않는다.
대신 Hibernate가 만든 프록시(Proxy) 객체를 반환한다.

프록시 객체란?

실제 User 객체:
┌──────────────────────┐
│ uid = "user1"        │  ← 모든 필드에 실제 값이 들어있음
│ username = "홍길동"   │
│ email = "a@b.com"    │
│ password = "1234"    │
└──────────────────────┘

프록시 User 객체:
┌──────────────────────┐
│ uid = "user1"        │  ← ID만 가지고 있음 (파라미터로 넘긴 값)
│ username = ???       │  ← 나머지 필드는 비어있음
│ email = ???          │
│ password = ???       │
└──────────────────────┘
   ↓ username에 접근하면?
   그제서야 SELECT 쿼리 실행!
  • 프록시는 실제 엔티티 클래스를 상속한 가짜 객체다.
  • ID 값만 가지고 있고, 나머지 필드는 비어있다.
  • 프록시의 ID가 아닌 다른 필드(예: user.getUsername())에 접근하면, 그 시점에 SELECT가 실행된다.
  • DB에 해당 row가 없으면 EntityNotFoundException이 발생한다 (Optional이 아님).

언제 어떤 것을 쓸까?

상황 적절한 메서드 이유
조회한 데이터를 화면에 보여줘야 할 때 findById 실제 데이터가 필요하므로 즉시 SELECT
FK 설정만 필요할 때
(INSERT/UPDATE 시 연관 엔티티)
getReferenceById ID만 있으면 되므로 SELECT 불필요
존재 여부를 확인해야 할 때 findById 프록시는 존재 여부를 즉시 알 수 없음

 

(1) registerPost

// User의 실제 데이터가 필요 없다.
// Post 테이블의 uid 컬럼에 FK만 넣으면 된다.
// → getReferenceById가 적절하다.
User user = userRepository.getReferenceById(postDto.getUserDto().getUid());
Post post = postDto.toEntity(user);  // Post의 user 필드에 프록시를 세팅
postRepository.save(post);           // INSERT 시 프록시의 uid만 사용

 

(2) getPost

// Post의 모든 필드를 화면에 보여줘야 한다.
// → findById가 적절하다.
return postRepository.findById(pid)
                     .map(PostDto::from)  // Post의 모든 필드 + User의 모든 필드에 접근

Lazy Loading vs Eager Loading

JPA의 기본 fetch 전략

어노테이션 기본값 이유
@ManyToOne EAGER "Many" 쪽에서 "One"을 조회 → 항상 1건이므로 부담 적음
@OneToMany LAZY "One" 쪽에서 "Many"를 조회 → 수백~수천 건일 수 있어 위험
@OneToOne EAGER 1건이므로 부담 적음
@ManyToMany LAZY 대량 데이터 가능

Post & PostComment Entity의 fetch 전략

// Post.java:46-48
@ManyToOne(cascade = CascadeType.PERSIST)   // ← fetch 속성 생략 = 기본값 EAGER
@JoinColumn(name = "uid")
private User user;

Post → User: EAGER (fetch 속성을 지정하지 않았으므로 @ManyToOne의 기본값인 EAGER 적용)

// Post.java:43-44
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL)   // ← 기본값 LAZY
private final Set<PostComment> postComments = new LinkedHashSet<>();

Post → PostComment: LAZY (@OneToMany의 기본값)

// PostComment.java:25-27
@ManyToOne(optional = false)   // ← 기본값 EAGER
@JoinColumn(name = "pid", referencedColumnName = "pid")
private Post post;

// PostComment.java:29-31
@ManyToOne(optional = false)   // ← 기본값 EAGER
@JoinColumn(name = "uid", referencedColumnName = "uid")
private User user;

PostComment → Post: EAGER
PostComment → User: EAGER

요약

Post 조회 시:
  ├── User (EAGER)         → Post SELECT할 때 LEFT JOIN으로 함께 가져옴
  └── PostComment (LAZY)   → postComments에 접근하기 전까지 SELECT 안 함
                               접근하면 그때 SELECT 실행

PostComment 조회 시 (이론상):
  ├── Post (EAGER)         → PostComment를 findById()로 직접 조회하면 LEFT JOIN (이론상!)
  └── User (EAGER)         → PostComment를 findById()로 직접 조회하면 LEFT JOIN

프로젝트에서의 실제 동작:
사실 PostComment를 findById()로 직접 조회하는 코드는 없다.
PostComment가 로딩되는 경우는 두 가지뿐이다:

  1. post.getPostComments() — Lazy 로딩 시
    → Post는 이미 영속성 컨텍스트에 존재하므로 Post JOIN 없음. User만 LEFT JOIN.
  2. deleteByIdAndPost_IdAndUser_Uid() — 댓글 삭제 시
    → Post, User JOIN이 보이지만 EAGER 로딩이 아니라 WHERE 조건 검색용 JOIN.

따라서 이 프로젝트에서 PostComment → Post EAGER LEFT JOIN은 실제로 발생하지 않는다.

Lazy 로딩이 실제 발생하는 시점

⚠️ 참고로 getPost 메서드는
상세페이지(GET /posts/{pid})에서
수정페이지(
GET /posts/{pid}/form)로 넘어갈 때 호출된다. (수정 폼에서는 댓글이 필요 없다)       

                                                                                

하지만 영속성 컨텍스트가 공유되지는 않는다.                                                                                                                 

HTTP 요청마다 영속성 컨텍스트가 새로 만들어지기 때문이다.

 

[1번째 요청] GET /posts/1  (상세페이지)

    → @Transactional 시작 → 영속성 컨텍스트 생성

    → getPostWithComments(1) → SELECT Post, SELECT PostComment

    → @Transactional 종료 → 영속성 컨텍스트 소멸 ← 여기서 전부 사라짐

 

[2번째 요청] GET /posts/1/form  (수정 폼)

    → @Transactional 시작 → 새 영속성 컨텍스트 생성 (1번 요청과 무관)

    → getPost(1) → SELECT Post JOIN User ← DB에 다시 쿼리

    → @Transactional 종료 → 영속성 컨텍스트 소멸

 

영속성 컨텍스트는 하나의 트랜잭션(= 하나의 HTTP 요청) 안에서만 살아있다.

브라우저에서 페이지를 이동하면 완전히 새로운 요청이므로, 이전에 조회했던

Post/User 캐시는 이미 없다.

 

getPost()

// PostService.java:30

postRepository.findById(pid)
// → SELECT post LEFT JOIN user 실행 (User는 EAGER이므로 JOIN)
// → PostComment는 LAZY이므로 아직 로딩 안 됨

.map(PostDto::from)
// → PostDto.from() 내부에서 post.getUser() 호출 (이미 JOIN으로 로드됨, 추가 쿼리 없음)
// → PostComment에는 접근하지 않으므로 PostComment SELECT 없음

getPostWithComments()

// PostService.java:37

postRepository.findById(pid)
// → SELECT post LEFT JOIN user 실행

// PostService.java:38
.map(PostWithCommentsDto::from)
// → PostWithCommentsDto.from() 내부:

// PostWithCommentsDto.java:42
post.getPostComments().stream()   // ← 이 순간! Lazy 로딩 발동!
// → SELECT post_comment LEFT JOIN user WHERE pid = ? 실행
// → PostComment들이 이 시점에 DB에서 로딩됨

핵심: post.getPostComments()를 호출하는 그 순간에 PostComment를 조회하는 SELECT가 실행된다. getPost()에서는 PostComment에 접근하지 않으므로 이 쿼리가 실행되지 않는다.


실행되는 SQL 추적

getPost() — 게시글 단건 조회

실제 코드

// PostService.java:28-33
@Transactional
public PostDto getPost(Long pid) {
    return postRepository.findById(pid)
                         .map(PostDto::from)
                         .orElseThrow(() -> new NoSuchElementException("해당 게시글 존재 x"));
}

실행되는 SQL

-- [1] findById(pid) → Post + User를 LEFT JOIN으로 한 번에 조회
SELECT p1_0.pid, p1_0.category_type, p1_0.content, p1_0.created_by, ...
FROM post p1_0
LEFT JOIN user u1_0 ON u1_0.uid = p1_0.uid
WHERE p1_0.pid = ?

총 1개의 쿼리가 실행된다.

  • findById()는 즉시 SELECT를 실행한다.
  • Post.user가 @ManyToOne (기본 EAGER)이므로, Hibernate가 LEFT JOIN으로 User를 함께 가져온다.
  • Post.postComments는 @OneToMany (기본 LAZY)이고, PostDto::from에서 postComments에 접근하지 않으므로 PostComment 쿼리는 실행되지 않는다.

getPostWithComments() — 게시글 + 댓글 조회

실제 코드

// PostService.java
@Transactional
public PostWithCommentsDto getPostWithComments(Long pid) {
    return postRepository.findById(pid)
                         .map(PostWithCommentsDto::from)
                         .orElseThrow(() -> new NoSuchElementException("해당 게시글 존재 x"));
}
// PostWithCommentsDto.java
public static PostWithCommentsDto from(Post post) {
    return new PostWithCommentsDto(
        post.getId(),
        post.getTitle(),
        post.getContent(),
        post.getCategoryType(),
        post.getPostComments().stream()          // ← Lazy 로딩 발동 지점
                              .map(PostCommentDto::from)
                              .collect(Collectors.toCollection(LinkedHashSet::new)),
        UserDto.from(post.getUser()),            // ← 이미 EAGER로 로드됨
        post.getCreatedDate(),
        post.getCreatedBy(),
        post.getModifiedDate(),
        post.getModifiedBy()
    );
}

실행되는 SQL

-- [1] findById(pid) → Post + User 조회 (EAGER JOIN)
SELECT p1_0.pid, p1_0.category_type, p1_0.content, p1_0.created_by, ...
FROM post p1_0
LEFT JOIN user u1_0 ON u1_0.uid = p1_0.uid
WHERE p1_0.pid = ?

-- [2] post.getPostComments() 접근 → Lazy 로딩으로 PostComment + User 조회
SELECT pc1_0.pid, pc1_0.id, pc1_0.content, pc1_0.created_by, ...
FROM post_comment pc1_0
LEFT JOIN user u1_0 ON u1_0.uid = pc1_0.uid
WHERE pc1_0.pid = ?

총 2개의 쿼리가 실행된다.

  • 1번 쿼리: findById()에 의해 Post + User(EAGER) 조회.
  • 2번 쿼리: PostWithCommentsDto.from() 안에서 post.getPostComments()를 호출하는 순간, Lazy 로딩이 발동하여 해당 Post의 PostComment들을 조회한다. PostComment → User도 @ManyToOne(EAGER)이므로 LEFT JOIN으로 함께 가져온다.

registerPost() — 게시글 등록

실제 코드

// PostService.java:42-55
@Transactional
public void registerPost(PostDto postDto) {
    User user = userRepository.getReferenceById(postDto.getUserDto().getUid());
    // ↑ SELECT 실행 안 함. uid만 가진 프록시 반환.

    Post post = postDto.toEntity(user);
    // ↑ Post 객체 생성. user 필드에 프록시가 들어감.

    postRepository.save(post);
    // ↑ INSERT 실행. 프록시의 uid만 FK 값으로 사용.
}

실행되는 SQL

-- [1] postRepository.save(post) → INSERT (getReferenceById는 쿼리 없음)
INSERT INTO post (category_type, content, created_by, created_date,
                  modified_by, modified_date, title, uid)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)

총 1개의 쿼리가 실행된다.

  • getReferenceById()는 프록시를 반환할 뿐, SELECT를 실행하지 않는다.
  • save()가 호출되면 Hibernate는 Post가 새 엔티티임을 감지하고(@GeneratedValue(strategy = IDENTITY) + id가 null) INSERT를 실행한다.
  • INSERT 시 uid 컬럼에는 프록시가 가진 ID 값("user1" 등)만 필요하므로, User의 다른 필드를 조회할 필요가 없다.
  • 이것이 getReferenceById의 장점이다. FK 설정을 위해 불필요한 SELECT를 하지 않는다.

updatePost() — 게시글 수정 (Dirty Checking)

실제 코드

// PostService.java:57-66
@Transactional
public void updatePost(Long pid, PostDto postDto) {
    Post post = postRepository.getReferenceById(pid);
    // ↑ 프록시 반환 (SELECT 아직 안 함)

    post.updateTitleAndContentAndCategoryType(
        postDto.getTitle(),
        postDto.getContent(),
        postDto.getCategoryType()
    );
    // ↑ 프록시의 필드에 접근 → 이 시점에 SELECT 실행
    // ↑ 그 후 title, content, categoryType 값을 변경
    // ↑ save() 호출 없음! @Transactional 종료 시 dirty checking으로 UPDATE 실행
}
// Post.java:63-67
public void updateTitleAndContentAndCategoryType(String title, String content, CategoryType categoryType) {
    this.title = title;        // ← setter로 값 변경
    this.content = content;
    this.categoryType = categoryType;
}

실행되는 SQL

-- [1] post.updateTitleAndContent...() 호출 시 → 프록시 초기화를 위한 SELECT
SELECT p1_0.pid, p1_0.category_type, p1_0.content, p1_0.created_by, ...
FROM post p1_0
LEFT JOIN user u1_0 ON u1_0.uid = p1_0.uid
WHERE p1_0.pid = ?

-- [2] @Transactional 종료 시 → dirty checking에 의한 UPDATE
UPDATE post
SET category_type = ?, content = ?, created_date = ?,
    modified_by = ?, modified_date = ?, title = ?, uid = ?
WHERE pid = ?

총 2개의 쿼리가 실행된다.

  1. getReferenceById(pid)는 프록시만 반환한다. (SELECT 없음)
  2. post.updateTitleAndContentAndCategoryType()이 호출되면, 프록시의 실제 필드에 접근하게 되므로 프록시가 초기화된다. 이때 SELECT가 실행된다.
  3. SELECT로 가져온 원본 데이터(스냅샷)를 영속성 컨텍스트가 보관한다.
  4. setter로 title, content, categoryType이 변경된다.
  5. @Transactional 메서드가 끝나면 트랜잭션이 커밋된다.
  6. 커밋 직전, Hibernate가 현재 엔티티 상태와 스냅샷(원본)을 비교한다 → Dirty Checking
  7. 변경된 필드가 발견되면 UPDATE SQL을 자동으로 실행한다.
  8. save()를 호출하지 않아도 UPDATE가 실행되는 이유가 바로 이것이다.

deletePost() — 게시글 삭제

실제 코드

// PostService.java:68-74
@Transactional
public void deletePost(long pid, String uid) {
    System.out.println("DELETE POST");
    postRepository.deleteByIdAndUser_Uid(pid, uid);
    // ↑ Spring Data JPA가 메서드 이름을 파싱하여 쿼리 생성
}

실행되는 SQL

-- [1] 삭제 대상 Post 조회 (pid + uid 조건)
SELECT p1_0.pid, p1_0.category_type, p1_0.content, p1_0.created_by, ...
FROM post p1_0
LEFT JOIN user u1_0 ON u1_0.uid = p1_0.uid
WHERE p1_0.pid = ? AND u1_0.uid = ?

-- [2] Post의 User를 로딩 (EAGER이므로)
SELECT u1_0.uid, u1_0.created_by, u1_0.created_date, u1_0.email, ...
FROM user u1_0
WHERE u1_0.uid = ?

-- [3] CascadeType.ALL에 의해 연관된 PostComment 로딩
SELECT pc1_0.pid, pc1_0.id, pc1_0.content, pc1_0.created_by, ...
FROM post_comment pc1_0
LEFT JOIN user u1_0 ON u1_0.uid = pc1_0.uid
WHERE pc1_0.pid = ?

-- [4] Post 삭제 (댓글이 있었다면 댓글 먼저 삭제 후 Post 삭제)
DELETE FROM post WHERE pid = ?

총 3~4개의 쿼리가 실행된다.

  • Spring Data JPA의 deleteByXxx() 메서드는 내부적으로 먼저 SELECT로 엔티티를 조회한 뒤, EntityManager.remove()로 삭제한다. (바로 DELETE를 날리지 않는다!)
  • Post의 postComments에 CascadeType.ALL이 걸려있으므로, Post를 삭제하기 전에 연관된 PostComment도 함께 삭제해야 한다.
  • 이를 위해 Hibernate는 PostComment도 미리 SELECT한다.
  • 댓글이 있으면 댓글 DELETE → Post DELETE 순서로 실행된다.

JOIN이 발생하는지 vs 추가 SELECT가 발생하는지

조회 방식 User 로딩 방식 이유
findById(pid) LEFT JOIN (한 방 쿼리) EntityManager.find() 사용 →
Hibernate가 EAGER 관계를 JOIN으로 최적화
findAll() 별도 SELECT (추가 쿼리) JPQL 기반 →
Hibernate가 EAGER이더라도 별도 SELECT로 처리
findByTitleContains() 별도 SELECT (추가 쿼리) Spring Data JPA 쿼리 메서드 → JPQL 기반
findByUser_UidContains() LEFT JOIN(조건) + 별도 SELECT(로딩) WHERE 절을 위해 JOIN은 하지만, User 데이터 로딩은 별도 SELECT

실제 로그로 확인

findById(1) — JOIN 발생:

-- 한 번의 쿼리로 Post + User를 함께 가져온다
SELECT p1_0.pid, ..., u1_0.uid, u1_0.username, ...
FROM post p1_0
LEFT JOIN user u1_0 ON u1_0.uid = p1_0.uid    -- ← JOIN!
WHERE p1_0.pid = ?

findAll() (목록 조회) — 별도 SELECT 발생:

-- 1번째 쿼리: Post만 가져온다 (User JOIN 없음!)
SELECT p1_0.pid, ..., p1_0.uid
FROM post p1_0
LIMIT ?, ?

-- 2번째 쿼리: 각 Post의 User를 개별적으로 가져온다
SELECT u1_0.uid, ... FROM user u1_0 WHERE u1_0.uid = ?
SELECT u1_0.uid, ... FROM user u1_0 WHERE u1_0.uid = ?
SELECT u1_0.uid, ... FROM user u1_0 WHERE u1_0.uid = ?
-- ... Post 개수만큼 반복 (같은 uid면 영속성 컨텍스트 캐시로 생략)

참고로 각 Post의 User를 개별적으로 가져오는 쿼리는 findAll() 뒤에 이어지는 map(PostDto::from) 때문에 발생하는 것이 아니다.
Post가 User를 참조로 갖고 있고 이는 ManyToOne 관계이기 때문에 EAGER가 적용되어 findAll() 후 User를 가져온다.

왜 이런 차이가 생기는가?

findById()
  → EntityManager.find() 호출
  → Hibernate가 직접 SQL을 생성
  → EAGER 관계를 LEFT JOIN으로 최적화함

findAll(), findByXxx()
  → JPQL 실행 ("SELECT p FROM Post p" 등)
  → JPQL은 엔티티 기준이라 JOIN을 자동 추가하지 않음
  → Post만 SELECT한 뒤, EAGER 관계의 User를 별도 SELECT로 로딩
  → 이것이 N+1 문제의 원인!

해결 방법

방법 설명 적용 예시
Fetch Join (JPQL) JPQL에 JOIN FETCH 추가 @Query("SELECT p FROM Post p JOIN FETCH p.user")
@EntityGraph 메서드에 어노테이션 추가 @EntityGraph(attributePaths = {"user"})
@BatchSize N+1의 N을 줄임 (IN절로 묶음) @BatchSize(size = 100) on User

N+1 문제에 대해 고민해보자

N+1 문제란?

1번의 쿼리로 N개의 데이터를 가져온 뒤,
각 데이터의 연관 엔티티를 가져오기 위해 N번의 추가 쿼리가 실행되는 것.
총 1 + N번의 쿼리가 실행된다.

N+1이 발생하는 시나리오

발생 위치: getPosts(), getPostsWithPage(), getPostsWithSearch()

// PostService.java:76-82
@Transactional
public List<PostDto> getPosts() {
    return postRepository.findAll().stream()   // ← Post Select 1번 + User Select N번
                         .map(PostDto::from)
                         .toList();
}

실제 SQL 로그

Post 쿼리: Post 10개 가져옴
User 쿼리: 10개 중 3개 게시글은 동일한 작성자. 총 8번의 User 쿼리 발생.

-- [1번째 쿼리] Post 목록 조회
SELECT p1_0.pid, p1_0.category_type, p1_0.content, ..., p1_0.uid
FROM post p1_0
LIMIT ?, ?

-- [2번째] 1번 Post의 User 조회
SELECT u1_0.uid, ... FROM user u1_0 WHERE u1_0.uid = ?    -- uid = 'user1'

-- [3번째] 2번 Post의 User 조회
SELECT u1_0.uid, ... FROM user u1_0 WHERE u1_0.uid = ?    -- uid = 'user2'

-- [4번째] 3번 Post의 User 조회
SELECT u1_0.uid, ... FROM user u1_0 WHERE u1_0.uid = ?    -- uid = 'user3'

-- ... uid가 같은 경우 영속성 컨텍스트 캐시로 생략되지만,
-- 서로 다른 uid가 있으면 그만큼 추가 쿼리 발생!

-- [마지막] count 쿼리 (페이징 시)
SELECT count(p1_0.pid) FROM post p1_0

해결 방법 ① — Fetch Join (JPQL)

// PostRepository.java에 추가
@Query("SELECT p FROM Post p JOIN FETCH p.user")
List<Post> findAllWithUser();

@Query("SELECT p FROM Post p JOIN FETCH p.user")
Page<Post> findAllWithUser(Pageable pageable);

실행되는 SQL:

-- 1번의 쿼리로 Post + User를 함께 가져온다
SELECT p1_0.pid, ..., u1_0.uid, u1_0.username, ...
FROM post p1_0
INNER JOIN user u1_0 ON u1_0.uid = p1_0.uid

해결 방법 ② — @EntityGraph

// PostRepository.java에 추가
@EntityGraph(attributePaths = {"user"})
@Override
List<Post> findAll();

@EntityGraph(attributePaths = {"user"})
Page<Post> findByTitleContains(String searchValue, Pageable pageable);

실행되는 SQL:

-- findAll()이지만 LEFT JOIN이 추가된다
SELECT p1_0.pid, ..., u1_0.uid, u1_0.username, ...
FROM post p1_0
LEFT JOIN user u1_0 ON u1_0.uid = p1_0.uid

해결 방법 ③ — @BatchSize

// User.java에 추가
@BatchSize(size = 100)    // ← 클래스 레벨에 추가
@Entity
public class User extends AuditingFields {
    // ...
}

실행되는 SQL:

-- Post 조회
SELECT p1_0.pid, ..., p1_0.uid FROM post p1_0

-- User를 한 번에 IN절로 묶어서 조회 (N번 → 1번으로 줄어듦)
SELECT u1_0.uid, ... FROM user u1_0 WHERE u1_0.uid IN (?, ?, ?, ?, ?, ?, ?, ?)

방법 비교

방법 장점 단점
Fetch Join 가장 확실, 1번의 쿼리 페이징과 함께 쓰면 메모리에서 페이징(경고 발생)
@EntityGraph 기존 메서드에 어노테이션만 추가 복잡한 조건에서는 한계
@BatchSize 기존 코드 변경 최소 완전한 1번은 아님(배치 수에 따라 나뉨)

 

'Learning Log' 카테고리의 다른 글

[멋사 클라우드 5기] Day 33 & 34 - Linux (1)  (1) 2026.03.25
[멋사 클라우드 5기] Day 31 & 32 - OSI 7 Layers  (1) 2026.03.16
[멋사 클라우드 5기] Day 28 - JWT 그런데 Redis를 곁들인  (0) 2026.03.10
[멋사 클라우드 5기] Day 26, 27 - Spring Security & JWT  (0) 2026.03.09
[멋사 클라우드 5기] Day 25 - Spring Data JPA (3)  (0) 2026.03.05
'Learning Log' 카테고리의 다른 글
  • [멋사 클라우드 5기] Day 33 & 34 - Linux (1)
  • [멋사 클라우드 5기] Day 31 & 32 - OSI 7 Layers
  • [멋사 클라우드 5기] Day 28 - JWT 그런데 Redis를 곁들인
  • [멋사 클라우드 5기] Day 26, 27 - Spring Security & JWT
allluck777
allluck777
allluck777
    • 분류 전체보기 (41)
      • AWS (0)
      • Network (0)
      • Linux (0)
      • Docker (0)
      • Project (4)
        • CloudNote (4)
      • Learning Log (34)
      • Lecture (3)
        • 스프링 입문 - 코드로 배우는 스프링 부트, 웹 .. (3)
  • 전체
    오늘
    어제
  • hELLO· Designed By정상우.v4.10.6
allluck777
[멋사 클라우드 5기] Day 29 & 30 - 게시판 구현을 통해 확인한 Spring Data JPA
상단으로

티스토리툴바