최원종의 개발 블로그

스프링부트 V5 JPARepository + Service(JpaRepository 인터페스와 Optional 타입) 본문

Spring boot 입문

스프링부트 V5 JPARepository + Service(JpaRepository 인터페스와 Optional 타입)

chl6698 2026. 5. 19. 17:38

 

JpaRepository 개념

Spring Data JPA에서 제공하는 인터페이스로, 기본적인 CRUD 메서드들을 자동으로 제공

- 기존의 EntityManager를 직접 사용하는 방식에서 더 간편한 방식으로 발전
- findAll(), save(), findById(), deleteById() 등의 메서드를 자동 제공
- 복잡한 쿼리는 @Query 어노테이션으로 직접 작성 가능

BoardRepository → JpaRepository 인터페이스 활용

@Repository 명시 생략 가능 : JpaRepository 인터페이스 상속 시 스프링이 자동으로 빈(Bean) 등록 수행

기본 제공 CRUD 메서드 : save()(등록/수정), findById()(단건조회), findAll()(전체조회), deleteById()(삭제)

findByIdJoinUser() : 게시글 ID로 상세 조회 시 fetch join을 통해
작성자(User) 정보까지 쿼리 1번으로 묶어서 가져옴

findAllJoinUser() : 전체 게시글 목록을 최신순(b.id DESC)으로 조회하면서 
작성자 정보까지 한방 쿼리로 낚아챔

데이터 수정 (더티 체킹) : Repository에 별도 쿼리 없이 @Transactional 안에서
엔티티 객체 값만 바꾸면 자동으로 수정 쿼리 실행

 

 

BoardRepository → JpaRepository 코드

package com.tenco.blog.board;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;

/**
 * Board 엔티티에 대한 JPA Repository 인터페이스 (클래스 아님!)
 * 게시글과 관련된 데이터 베이스 접근을 담당하게 됨
 * 기본적인 CRUD 이미 제공 됨.
 */
// @Repository // IoC - 굳이 명시할 필요 없음 ( JpaRepository 인터페이스를 상속했기 때문에 두번 선언할 필요 없음)
public interface BoardRepository extends JpaRepository<Board, Integer> {

    // 1. 등록 및 수정 save(Board entity)
    // 2. 단건 조회 : findById(Integer id)
    // 3. 전체 조회 : findAll()
    // 4. 삭제 : deleteById(Integer id)
    // 5. 데이터 개수: count()
    // 6. 존재 여부 확인: existsById(Integer id)

    // 단건 조회
    // 1. 게시글 ID로 조회시 사용자 정보도 함께 가져오기
    @Query("""
            SELECT b FROM Board b JOIN FETCH b.user WHERE b.id = :id
           """)
    Optional<Board> findByIdJoinUser(@Param("id") Integer id);

    // 2. 전체 게시글 조회 (단 한번에 작성자 정보도 조회)
    @Query("""
    SELECT b FROM Board b JOIN FETCH b.user ORDER BY b.id DESC
    """)
    List<Board> findAllJoinUser();

    // 3. 데이터 수정은 더티 체킹으로 처리
}

 

UserRepository → JpaRepository 인터페이스 활용

@Repository 명시 생략 : JpaRepository를 상속하면 스프링 부트가 뜰 때 자동으로 IoC 빈(Bean)으로 등록됨

기본 제공 CRUD 기능 : save()(등록/수정), findById()(단건조회), 
findAll()(전체조회), deleteById()(삭제), count()(개수)

findByUsername() : 회원가입 시 아이디 중복 체크를 위해 JPQL을 사용해
특정 username을 가진 유저를 단건 조회

findByUsernameAndPassword() : 로그인 시 아이디와 비밀번호가 일치하는 회원이 존재하는지
검증하는 로그인 전용 쿼리

회원 정보 수정 (더티 체킹) : 리포지토리에 별도의 수정 쿼리 메서드 없이
영속성 컨텍스트의 감시(변경 감지) 기능을 통해 처리

 

 

UserRepository → JpaRepository  코드

package com.tenco.blog.user;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.Optional;

// @Repository  -> JpaRepository 상속하고 있기 때문에 두번 작성할 필요 없음
public interface UserRepository extends JpaRepository<User, Integer> {

    // 1. 사용자 등록 및 수정 : save(User user)
    // - 새로운 사용자를 Insert 하거나, 기존 사용자 정보를 UPDATE 합니다.
    // 2. 사용자 단건 조회: findById(Integer id)
    //   - PK(id) 를 통해 특정 사용자를 조회하며 Optional<User>를 반환
    // 3. 전체 사용자 목록 조회 : findAll()
    //   - DB에 저장된 모든 사용자 정보를 List<User> 형태로 가지고 온다.
    // 4. 사용자 삭제: deleteById(Integer id)
    //   - 특정 ID를 가진 사용자를 삭제합니다.
    // 5. 데이터 갯수 : count()
    //   - 전체 레코드 수 반환
    // 6. 존재 여부 확인 : existsById(Integer id)
    //   - 해당 ID를 가진 데이터가 있는지 확인하여 boolean을 반환


    // 사용자명으로 사용자 조회(중복 체크 확인용)
    @Query("""
        SELECT u FROM User u WHERE u.username = :username
    """)
    Optional<User> findByUsername(@Param("username") String username);

    // 사용자명과 비밀번호로 사용자 조회(로그인용)
    @Query("""
        SELECT u FROM User u WHERE u.username = :username AND u.password = :password 
    """)
    Optional<User> findByUsernameAndPassword(@Param("username") String username,
                                             @Param("password")  String password);

    // 사용자 정보 수정
    // [더티 체킹이란?]
    // 트랜잭션 내에서 조회된 객체상태를 변경하면
    // 트랜잭션이 끝나는 시점에 JPA가 변경된 내용을 자동으로 감시하여
    // DB에 UPDATE 쿼리를ㄹ 날려주는 기능을 말한다.

}

 


Optional 방식

전통적인 null 체크 방식 : 객체가 null인지 if문으로 직접 검사. 
코드가 길어지고 실수로 null 체크를 빼먹으면 런타임 에러(NPE) 발생 위험

Optional 익명 클래스 방식 : JpaRepository가 반환한 Optional 객체의 orElseThrow() 메서드 활용.
내부적으로 Supplier 인터페이스를 직접 구현

Optional 람다 표현식 방식 : 익명 클래스의 불필요한 코드를 생략하고 
핵심 로직인 () -> 예외생성 문법만 남겨 가독성을 극대화한 현대적 스타일

 

Optional예시 코드

// null 체크 방식
public Board getBoardOrThrow(Long boardId) {
    Board board = boardJPARepository.findByIdJoinUser(boardId);
    if (board == null) {
        throw new RuntimeException("게시글을 찾을 수 없습니다.");
    }
    return board;
}

// Optional 방식 (익명 클래스 사용)
public Board getBoardOrThrow(Long boardId) {
    return boardJPARepository.findByIdJoinUser(boardId)
            .orElseThrow(new Supplier<RuntimeException>() {
                @Override
                public RuntimeException get() {
                    // 게시글이 없을 경우 예외를 생성해 반환
                    return new RuntimeException("게시글을 찾을 수 없습니다.");
                }
            });
}

// Optional 방식
// - 람다 표현식 사용 코드 
public Board getBoardOrThrow(Long boardId) {
    return boardJPARepository.findByIdJoinUser(boardId)
                            .orElseThrow(() -> new RuntimeException("게시글을 찾을 수 없습니다."));
}