2026. 4. 20. 20:35ㆍ대우개발원 수업 내용/spring boot, framework
JPA 영속성 전이 및 N+1 문제 해결을 포함한 연관관계 데이터 최적화와 Querydsl 기반 페이징 처리 구현
1. JPA @OneToMany 영속성 전이 및 고아 객체 제거 설정 (Board.java)
CascadeType.ALL: 부모(Board) 데이터 상태 변화 시 자식(BoardImage) 데이터에도 동일하게 전이하여 일괄 처리
orphanRemoval = true: 부모 엔티티와의 연관관계가 끊어진 고아 객체(자식 데이터)를 데이터베이스에서 자동 삭제
메서드 캡슐화: addImage, clearImages 메서드를 작성하여 안전하게 자식 데이터 추가 및 초기화 수행
2. 지연 로딩(Lazy) 오류 해결 및 즉시 로딩 최적화 (BoardRepository.java, BoardRepositoryTests.java)
문제 식별: 트랜잭션 종료 후 지연 로딩된 자식 데이터를 호출할 때 발생하는 LazyInitializationException 오류 확인
해결 방안: 조회 쿼리 메서드에 @EntityGraph(attributePaths = {"imageSet"})를 적용하여 연관 데이터를 한 번의 Join 쿼리로 즉시 로딩
3. 외래키 참조 무결성을 준수하는 연관 데이터 삭제 (ReplyRepository.java, BoardRepositoryTests.java)
무결성 유지: 자식(댓글)이 존재하는 상태에서 부모(게시글) 삭제 시 발생하는 외래키 제약 조건 위반 에러 원천 차단
삭제 순서: 사용자 정의 쿼리(deleteByBoard_Bno)로 연관된 댓글을 먼저 일괄 삭제한 후, 부모 게시글을 삭제하도록 트랜잭션(@Transactional) 구성
4. Querydsl 기반 동적 쿼리 및 조인 페이징 처리 (BoardSearch.java, BoardSearchImpl.java)
동적 쿼리 조인: QBoard와 QReply를 Left Join하여 댓글이 없는 게시글도 누락 없이 조회 결과에 포함
페이징 자동화: applyPagination()에 Pageable 정보를 넘겨 데이터베이스 수준의 페이징 쿼리를 자동으로 실행 및 결과 반환
5. 연관 데이터 로딩 시 N+1 문제와 @BatchSize 해결 (Board.java, BoardSearchImpl.java)
문제 식별: 게시물 목록을 순회하며 하위 이미지 조회 시, 각 게시물마다 쿼리가 N번 추가로 발생하는 심각한 성능 저하 확인
해결 방안: 연관관계 컬렉션 필드에 @BatchSize(size = 20)를 지정하여 여러 하위 데이터를 IN 절로 묶어 네트워크 호출 횟수 최적화


JPA
Borad.java코드가 변경이 되면 BoardImage.java 코드가 변경 될 수 있도록
cascade 옵션을 지정
Borad.java
코드 수정
게시물과 이미지 간의 일대다 관계를 설정하고, 부모의 상태 변화를 자식에게 전이하며 성능 최적화를 위해
지연 로딩을 적용하는 설정.
- @OneToMany: 하나의 게시글 엔티티에 대해 여러 개의 이미지 엔티티가 연결되는 1:N(일대다) 관계 선언.
- mappedBy = "board": 관계의 주인을 자식 엔티티(BoardImage)의 board 필드로 지정하여 데이터베이스
외래키 매핑 권한을 위임함. - cascade = {CascadeType.ALL}: 부모 엔티티의 저장, 수정, 삭제 등의 영속성 상태 변화를 연결된 자식 엔티티들에
모두 동일하게 적용함. - fetch = FetchType.LAZY: 데이터 조회 시점에 연관된 데이터를 즉시 불러오지 않고 실제 사용 시점에 쿼리를 실행하여 시스템 부하를 방지함.
- 주석 설명: 해당 설정이 BoardImage 엔티티 내의 어떤 필드에 의해 참조되고 있는지 명시하여 코드의 가독성을 높임.
@OneToMany(mappedBy = "board",
cascade = {CascadeType.ALL},
fetch = FetchType.LAZY) // BoardImage 테이블의 board
그리고 image관련 메소드 addImage, clearImages 2개 추가
게시글 엔티티 내에서 이미지 객체를 생성하여 리스트에 추가하거나,
기존 연관 관계를 안전하게 해제하여 목록을 비우는 메서드 구성.
- BoardImage.builder(): 빌더 패턴을 사용하여 UUID, 파일명, 현재 게시글 객체(this),
그리고 순번(ord) 정보가 담긴 이미지 객체를 생성함. - .board(this): 생성되는 이미지 객체에 현재 게시글을 부모로 지정하여 양방향 연관 관계를 설정함.
- .ord(imageSet.size()): 현재 보관 중인 이미지 개수를 순번으로 지정하여 리스트 내의 정렬 순서를 결정함.
- imageSet.add(boardImage): 최종 빌드된 이미지 객체를 게시글 내의 이미지 집합(imageSet)에 추가함.
- clearImages(): 기존에 등록된 모든 이미지와 게시글 간의 참조 관계를 끊고 리스트를 완전히 비움.
- boardImage.changeBoard(null): 각 이미지 객체가 참조하던 부모 게시글(외래키)을 비워
자식 엔티티들을 고아 객체로 만듦. - this.imageSet.clear(): 게시글 객체가 보유한 이미지 컬렉션을 초기화하여 메모리 및 DB 반영 준비를 마침.
public void addImage(String uuid, String fileName) {
BoardImage boardImage = BoardImage.builder()
.uuid(uuid)
.fileName(fileName)
.board(this)
.ord(imageSet.size())
.build();
imageSet.add(boardImage);
}
public void clearImages() {
imageSet.forEach(boardImage -> boardImage.changeBoard(null));
this.imageSet.clear();
}
BoardRepositoryTests.java
코드 추가
board 객체 생성하고 첨부파일을 만듬
첨부파일을 포함한 게시글 데이터를 생성하고 영속성 전이를 통해
자식 엔티티인 이미지 데이터까지 한 번에 데이터베이스에 저장하는 테스트 코드.
- Board.builder(): 빌더 패턴을 사용하여 제목, 내용, 작성자 정보가 담긴 게시글 객체를 생성함.
- for문 반복: 총 3번의 반복을 통해 하나의 게시글에 부속될 이미지 데이터를 생성하는 과정을 수행함.
- UUID.randomUUID(): 파일명의 중복을 방지하기 위해 고유한 식별자인 UUID를 생성하여
이미지의 식별값으로 할당함. - board.addImage(): 앞서 만든 UUID와 파일명을 인자로 전달하여 Board 객체 내부의
이미지 리스트(imageSet)에 자식 객체들을 추가함. - boardRepository.save(board): 리포지토리를 통해 게시글을 저장하며,
@OneToMany의 cascade 설정에 의해 연결된 이미지 3개도 자동으로 같이 DB에 저장
@Test
public void testInsertWithImages() {
Board board = Board.builder()
.title("Image Test")
.content("첨부파일 테스트")
.writer("tester")
.build();
for (int i = 0; i < 3; i++) {
board.addImage(UUID.randomUUID().toString(), "file"+i+".jpg");
}//end for
boardRepository.save(board);
}
실행하면
boardImage 테이블에 ord에 HashSet의 경우 정렬을 해주지 않기 때문에 0 2 1 순서가 될 수도 있고 다른 순서가 될 수도 있지만
우연히 0 1 2로 지금 정렬이 된 모습
그리고 아래 테스트 코드를 추가
특정 게시글 번호를 통해 DB를 조회하고, 연관된 이미지 데이터들이 지연 로딩(Lazy Loading) 방식으로
정상적으로 함께 불러와지는지 확인하는 테스트 코드.
- boardRepository.findById(1L): 데이터베이스에서 PK(기본키) 값이 1번인 게시글 데이터를 조회하여
Optional 객체에 담음. - result.orElseThrow(): 조회 결과가 존재하면 Board 객체를 반환하고, 만약 데이터가 없으면 예외(NoSuchElementException)를 발생시켜 안전하게 처리함.
- log.info(board): 게시글 본체 데이터를 로그로 출력하며, 이때까지는 이미지 테이블에 대한 쿼리가 실행되지 않음.
- board.getImageSet(): 게시글과 연결된 이미지 목록을 호출하는 시점으로,
@OneToMany의 지연 로딩(Lazy) 설정에 의해 이때 비로소 이미지 데이터를 가져오는 쿼리가 추가로 실행됨. - 테스트 목적: 게시글 조회 시 연관된 이미지들이 세션 안에서 문제없이 로딩되는지,
그리고 데이터가 누락 없이 출력되는지 검증함.
@Test
public void testReadWithImage() {
// 반드시 존재하는 bno로 확인
Optional<Board> result = boardRepository.findById(1L);
Board board = result.orElseThrow();
log.info(board);
log.info("-----------------------");
log.info(board.getImageSet());
}
테스트 코드를 실행하면
실패하는데
지연 로딩(FetchType.LAZY) 설정으로 인해 데이터베이스 연결 세션이 이미 종료된 시점에서
연관된 이미지 데이터를 호출하려 했기 때문에 발생하는 오류이다
이걸 해결하려면
BoardRepository.java
코드에 추가
EntityGraph를 활용하여 연관된 이미지 데이터를 조인 쿼리로 한 번에 조회하도록 설정하는 인터페이스 메서드 선언.
- @EntityGraph: JPA의 기본 지연 로딩 설정을 무시하고 특정 연관 관계를 즉시 로딩(Eager Loading)
방식으로 가져오도록 지시함. - attributePaths = {"imageSet"}: Board 엔티티 내부에 선언된 imageSet 필드를 조회 대상에 포함시켜
함께 로딩하도록 지정함. - @Query: JPQL을 직접 사용하여 특정 게시글 번호(bno)에 해당하는 데이터를 선택하는 사용자 정의 쿼리를 작성함.
- Join 쿼리 발생: 메서드 호출 시 데이터베이스에서 Left Outer Join이 수행되어 게시글 본체와
이미지 리스트를 단 한 번의 네트워크 요청으로 획득함. - LazyInitializationException 해결: 트랜잭션이 종료된 후에도 이미 필요한 이미지 데이터가
메모리에 올라와 있으므로 세션 종료로 인한 조회 오류를 원천 차단함
@EntityGraph(attributePaths = {"imageSet"})
@Query("select b from Board b where b.bno =:bno")
Optional<Board> findByIdWithImages(Long bno);
그리고
BoardRepositoryTests.java
수정
단일 테이블 조회 방식에서 연관 테이블과의 조인 조회 방식으로 변경하여 데이터 로딩 전략을 최적화함.
- 조회 방식의 변화: findById는 Board 테이블만 먼저 조회하는 반면 findByIdWithImages는 Board와
연관된 BoardImage 테이블을 Left Outer Join으로 묶어서 동시에 조회함. - 쿼리 실행 횟수: findById는 이미지 접근 시 추가 쿼리가 발생하는 N+1 문제의 위험이 있으나
findByIdWithImages는 단 한 번의 쿼리로 모든 데이터를 가져옴. - 데이터 로딩 시점: findById는 지연 로딩(Lazy) 방식으로 이미지를 실제 사용할 때 불러오지만
findByIdWithImages는 즉시 로딩(Eager)처럼 미리 데이터를 채워 넣음. - 예외 발생 여부: findById는 트랜잭션 밖에서 이미지에 접근할 경우 LazyInitializationException을 발생시키지만 findByIdWithImages는 세션 종료와 관계없이 안전하게 데이터 사용 가능함.
- 성능 영향: 단순 게시글 정보만 필요할 때는 findById가 유리하나 게시글과 첨부 이미지를 동시에
보여줘야 하는 화면에서는 findByIdWithImages가 효율적
@Test
public void testReadWithImage() {
// 반드시 존재하는 bno로 확인
// Optional<Board> result = boardRepository.findById(1L);
Optional<Board> result = boardRepository.findByIdWithImages(1L);
Board board = result.orElseThrow();
log.info(board);
log.info("-----------------------");
log.info(board.getImageSet());
}
실행하면
조회가 잘 되는 모습이다.
BoardRepositoryTests.java
코드 아래에 추가
기존의 모든 이미지 데이터를 삭제하고 새로운 이미지들을 추가하여 게시글의 첨부파일 목록을 완전히 갱신
기존의 모든 이미지 데이터를 삭제하고 새로운 이미지들을 추가하여 게시글의 첨부파일 목록을 완전히 갱신하는 테스트 코드.
- @Transactional: 메서드 실행 동안 데이터베이스 세션을 유지하며 모든 작업이 성공해야만 실제 DB에 반영되도록 보장
- @Commit: 테스트 메서드가 종료된 후 롤백(Rollback)하지 않고 변경된 사항을 데이터베이스에 영구적으로 반영 강제
- findByIdWithImages(1L): 게시글과 연관된 이미지들을 조인 쿼리로 한 번에 불러와서 이후 데이터 수정 작업 시 세션 종료 문제를 방지
- board.clearImages(): 게시글과 기존 이미지들 간의 연관 관계를 끊고 컬렉션을 비워 기존 데이터를 DB에서 삭제할 준비를 마침.
- addImage 반복 호출: UUID를 생성하여 새로운 파일명과 함께 3개의 이미지 객체를 생성하고 게시글 객체에 추가함.
- boardRepository.save(board): 수정된 게시글 객체를 저장하며 이때 orphanRemoval 또는 Cascade 설정에 의해
기존 이미지는 Delete 쿼리가, 새 이미지는 Insert 쿼리가 실행됨.
@Transactional //jakarta로 import
@Commit
@Test
public void testModifyImage() {
Optional<Board> result = boardRepository.findByIdWithImages(1L);
Board board = result.orElseThrow();
//기존의 첨부파일들은 삭제
board.clearImages();
//새로운 첨부파일들
for (int i = 0; i<2; i++){
board.addImage(UUID.randomUUID().toString(), "updatefile"+i+".jpg");
}
boardRepository.save(board);
}
실행하면
DB에서 확인
기존에 있는 이미지 파일은 삭제가 되어 있어야하는데
board_bno에 있는 정보만 <null>로 사라진것을 확인 할 수 있다
cascade속성을 ALL로 지정해놔서 상위 엔티티의 결과가 하위 엔티티에 영향은 줬지만
제대로 삭제가 되지 않았기 때문에
Borad.java에
orphanRemoval = true
를 추가해줘야한다.
근데 지금 null값으로 되어 있는게 오류 데이터라 인식을 못하니



@Test
public void testModifyImage() {
Optional<Board> result = boardRepository.findByIdWithImages(1L);
Board board = result.orElseThrow();
//기존의 첨부파일들은 삭제
board.clearImages();
//새로운 첨부파일들
for (int i = 0; i<2; i++){
board.addImage(UUID.randomUUID().toString(), "modefile"+i+".jpg");
}
boardRepository.save(board);
}
modefile이라는 이름으로 변경해서 확인
실행해서 DB를 확인하면
정상적으로 되는 모습
게시물을 삭제했을 때
reply(댓글)도 삭제할 수 있도록
ReplyRepository
에 코드 추가
게시글 번호를 기준으로 연관된 모든 댓글 데이터를 한 번에 삭제하기 위해 리포지토리에 정의한 사용자 정의 쿼리 메서드.
- void: 삭제 작업 후 별도의 결과값을 반환하지 않음.
- deleteBy: Spring Data JPA에서 제공하는 키워드로 특정 조건에 부합하는 데이터를 삭제하는 기능을 수행함.
- Board_Bno: 참조하고 있는 Board 엔티티 내부의 bno 필드 값을 조건으로 인식하여 조인 또는 외래키 조회를 수행함.
- Long bno: 삭제 대상이 되는 부모 게시글의 식별 번호를 매개변수로 전달받음.
- 대량 삭제 처리: 해당 게시글에 달린 여러 개의 댓글을 단일 조건으로 일괄 제거하여 데이터 무결성을 유지함.
void deleteByBoard_Bno(Long bno);
BoardRepositoryTests
에 추가
스프링 컨테이너에 등록된 ReplyRepository 빈을 필드 주입 방식으로 가져와 사용할 수 있게 의존성을 연결하는 코드
@Autowired
private ReplyRepository replyRepository;
특정 게시글 번호를 기준으로 연관된 모든 댓글을 먼저 삭제한 뒤 게시글 본체를
최종적으로 삭제하는 데이터 무결성 준수 테스트 코드.
- @Transactional: 댓글 삭제와 게시물 삭제 과정을 하나의 작업 단위로 묶어 중간에 오류 발생 시 모든 작업을 되돌리고 데이터 일관성을 유지함.
- @Commit: 테스트 환경의 기본값인 자동 롤백을 방지하고 실행 결과를 데이터베이스에 물리적으로 영구 반영함.
- replyRepository.deleteByBoard_Bno(bno): 게시물을 삭제하기 전 외래키 참조 무결성 오류를 방지하기 위해 해당 게시글에 달린 모든 댓글 데이터를 우선적으로 삭제함.
- boardRepository.deleteById(bno): 댓글이 모두 정리된 상태에서 비로소 주 데이터인 게시글 본체를 테이블에서 제거함.
- 삭제 순서의 중요성: 자식 데이터(댓글)를 먼저 지우지 않고 부모 데이터(게시글)를 삭제할 경우 발생하는 제약 조건 위반 예러를 방지하는 표준적인 삭제 절차
@Test
@Transactional
@Commit
public void testRemoveAll() {
Long bno = 1L;
replyRepository.deleteByBoard_Bno(bno);
boardRepository.deleteById(bno);
}
실행하기전에
이전에 있던 board와 reply를 삭제해 줬기때문에
BoardRepositoryTests에 있는
를 실행하고

를 확인해서 bno 2번에 reply를 추가하는
ReplyRepositoryTests
안에 있는
코드를 실행

reply가 추가 되었으니
다시
BoardRepositoryTests에 있는
(bno가 2번이니까 2번으로 수정)
를 실행
bno2번에 해당하는 reply와 board가 정상적으로 삭제된 모습
처리되고 나서 list도 변해야 한다
근데 list를 테스트 하려면 데이터가 많이 필요하기 때문에
테이블 3개를 DROP
BoardRepositoryTests
에 테스트 코드를 추가
반복문을 통해 100개의 게시글을 생성하며 특정 조건에 따라 첨부 이미지를 차등 등록하고
데이터베이스에 저장하는 테스트 코드.
- 외부 for문: 1부터 100까지 반복하며 각 회차마다 제목, 내용, 작성자가 포함된 게시글(Board) 객체를 생성함.
- 내부 for문: 게시글마다 최대 3개의 이미지를 추가하기 위한 반복 과정을 수행함.
- if (i % 5 == 0): 게시글 번호가 5의 배수인 경우 이미지 등록 과정을 생략하고 다음 게시글로 넘어감.
- board.addImage: 5의 배수가 아닌 게시글에 한해 고유 UUID와 파일명을 가진 이미지 객체를 게시글의
이미지 셋에 추가함. - boardRepository.save(board): 구성된 게시글 객체를 저장하며 영속성 전이(Cascade) 설정에 의해 포함된
이미지 데이터들도 함께 DB에 반영됨
@Test
public void testInsertAll() {
for (int i = 1; i<=100; i++) {
Board board = Board.builder()
.title("Title.."+ i)
.content("Content.." + i)
.writer("writer.." + i)
.build();
for (int j = 0; j < 3; j++) { //5의 배수
if (i % 5 == 0) {
continue;
}
board.addImage(UUID.randomUUID().toString(), i + "file" + j + ".jpg");
}
boardRepository.save(board);
}//end for
}
실행하면 자동으로 테이블 3개가 생성된다
그리고 위의 테스트 코드를 실행해서
board테이블에 더미데이터 100개가 들어온 것을 확인
BoardSearch
에 코드를 추가
다양한 검색 조건과 키워드를 바탕으로 게시글 목록을 조회하되,
각 게시글에 달린 댓글 개수까지 포함하여 페이징 처리된 결과를 반환하는 인터페이스 메서드.
- Page<BoardListReplyCountDTO>: 조회 결과 데이터뿐만 아니라 전체 페이지 수,
현재 페이지 번호 등의 페이징 정보를 포함한 DTO 객체 리스트를 반환함. - searchWithAll: 제목, 내용, 작성자 등 여러 항목을 복합적으로 검색할 수 있도록
설계된 사용자 정의 쿼리 메서드임을 나타냄. - String[] types: 제목(t), 내용(c), 작성자(w) 등 검색 대상 범위를 배열 형태로 전달받아 동적 쿼리를 생성하는 데 사용함.
- String keyword: 사용자가 입력한 실제 검색어 문구로, SQL의 LIKE 연산자와 결합하여 데이터를 필터링함.
- Pageable pageable: 페이지 번호, 한 페이지당 데이터 개수, 정렬 방향 등의 정보를 담고 있어
DB 수준의 페이징 쿼리 수행을 가능하게 함.
Page<BoardListReplyCountDTO> searchWithAll(String[] types,
String keyword,
Pageable pageable);
BoardSearchImpl
Querydsl을 사용하여 게시글과 댓글을 조인하고 페이지네이션을 적용하여 데이터를 조회하는 로직.
- QBoard & QReply: Querydsl의 핵심 객체인 Q도메인을 생성하여 타입 안정성이 보장된 쿼리 작성을 준비함.
- leftJoin: 게시글(Board)을 기준으로 댓글(Reply)을 조인하여 댓글이 없는 게시글도 조회 결과에 포함되도록 설정함.
- applyPagination: 전달받은 Pageable 정보를 기반으로 DB 쿼리에 페이징 처리를 자동으로 적용함.
- boardList.forEach: 조회된 각 게시글을 순회하며 번호와 연관된 이미지 데이터들을 출력하여 정상 조회 여부를 확인
- 지연 로딩 주의: 반복문 내부에서 getImageSet() 호출 시 추가적인 쿼리가 실행되어 성능 저하가 발생할 수 있음.
@Override
public Page<BoardListReplyCountDTO> searchWithAll(String[] types, String keyword,
Pageable pageable) {
QBoard board = QBoard.board;
QReply reply = QReply.reply;
//Board 엔터티를 기준으로 JPQLQuery 생성
JPQLQuery<Board> boardJPQLQuery = from(board);
//Board와 Reply를 left join
boardJPQLQuery.leftJoin(reply).on(reply.board.eq(board));
//페이지네이션 적용
getQuerydsl().applyPagination(pageable, boardJPQLQuery);
//쿼리 실행하여 Board 리스트 가져오기
List<Board> boardList = boardJPQLQuery.fetch();
//가져온 Board 리스트의 각 요소 출력
boardList.forEach(board1 -> {
System.out.println(board1.getBno()); //게시글 번호 출력
System.out.println(board1.getImageSet()); //이미지 정보 출력
System.out.println("-------------------"); // 구분선 출력
});
return null; // 현재는 반환값이 없으므로 null 반환
}
BoardRepositoryTests
에 아래 테스트 코드를 추가
작성한 searchWithAll 메서드의 동작을 검증하기 위해 페이징 정보만 인자로 전달하여 초기 데이터를 조회하는 테스트 코드.
- @Transactional: 테스트 실행 중 지연 로딩 데이터를 안전하게 조회하기 위해
데이터베이스 세션 유지 범위를 메서드 전체로 확장함. - PageRequest.of: 0번 페이지부터 10개씩 데이터를 가져오도록 설정하며 게시글 번호(bno) 기준
내림차순으로 정렬 조건을 생성함. - searchWithAll: 제목, 내용 등 별도의 검색 조건(types)과 키워드(keyword) 없이
전체 데이터를 대상으로 조인 쿼리를 실행함. - 연관 데이터 로딩: 내부적으로 이미지와 댓글 데이터를 조인하여 가져오는 로직이 정상적으로 수행되는지 확인하는 용도
- 로그 확인: 메서드 내부에 작성된 출력문을 통해 실제 데이터베이스에서
가져온 게시글 번호와 이미지 목록이 올바른지 검증함.
@Transactional
@Test
public void testSearchImageReplyCount() {
Pageable pageable = PageRequest.of(0, 10,
Sort.by("bno").descending());
boardRepository.searchWithAll(null, null, pageable);
}
실행하면
게시물당 boardImage에 대한 내용을 조회하는
N +1문제가 생김
N+1 문제를 해결하는 방법
1. Fetch Join 연관된 엔티티를 한 번의 쿼리로 함께 조회하여 지연 로딩 발생을 원천 차단하는 방식임.
- JPQL 활용: JOIN FETCH 구문을 사용하여 부모 엔티티 조회 시 자식 엔티티까지 SQL 조인으로 가져옴.
- 성능 이점: 여러 번의 Select 쿼리 대신 단일 쿼리를 실행하여 네트워크 오버헤드를 대폭 줄임.
2. Batch Size 지정된 개수만큼 연관된 엔티티를 IN 절을 사용하여 모아서 조회하는 최적화 방식임.
- @BatchSize: 엔티티 클래스나 컬렉션 필드에 설정하여 프록시 객체 로딩 시 한꺼번에 조회할 데이터의 양을 결정함.
- IN 절 활용: 개별적인 Select 쿼리 대신 WHERE id IN (?, ?, ...) 형태의 쿼리를 통해 DB 접근 횟수를 획기적으로 개선
3. Entity Graph 메서드 단위로 로딩 전략을 변경할 수 있도록 지원하는 선언적 데이터 조회 방식임.
- 어노테이션 설정: @NamedEntityGraph를 통해 함께 조회할 연관 관계를 미리 정의하고 힌트를 제공함.
- 동적 적용: 특정 시점에는 지연 로딩을 유지하고, 필요한 시점에만 즉시 로딩(Eager)처럼 동작하게 제어 가능함.
4. Querydsl 코드 기반의 타입 안정성을 보장하며 동적 쿼리와 페치 조인을 결합하여 사용하는 방식임.
- fetchJoin(): Querydsl의 메서드 체이닝 중 .fetchJoin()을 호출하여 SQL 조인 결과에 연관 데이터를 포함시킴.
- 타입 세이프: 컴파일 시점에 쿼리 오류를 발견할 수 있으며 복잡한 조인 조건을 직관적으로 작성 가능한 장점이 있음.
Board.java
@BatchSize(size = 20)
를 추가하고
테스트 코드를 다시 실행하면
각 게시물마다 조회하는게 아니고 20개를 한꺼번에 조회
'대우개발원 수업 내용 > spring boot, framework' 카테고리의 다른 글
| 자바 스프링 부트 12일차 b01Upload /첨부파일 비동기 업로드 구현 및 파일 정보를 포함한 게시글 등록 처리 (0) | 2026.04.22 |
|---|---|
| 자바 스프링 부트 11일차 b01Upload /첨부파일 이미지와 댓글 조회 및 처리 (0) | 2026.04.21 |
| 자바 스프링 부트 9일차b01Upload 파일 업로드 처리 (0) | 2026.04.16 |
| 자바 스프링 부트 8일차 b01Rest 댓글 페이징 및 CRUD 프론트엔드 연동 구현 (1) | 2026.04.15 |
| 자바 스프링 부트 7일차 b01Rest REST API 서버 구축부터 Axios를 활용한 프론트엔드 통신 준비 (0) | 2026.04.14 |













