멋쟁이사자처럼_부트캠프/Spring

[멋쟁이사자처럼 부트캠프 TIL 회고] 백엔드 부트캠프 13기: Java 47일차 Spring Data JPA

모건_Morgan 2025. 2. 19. 21:32

Spring Data JPA

  • Spring 프레임워크에서 JPA(Java Persistence API)를 더 쉽게 사용할 수 있도록 도와주는 모듈(프레임워크).
  • 기본적인 CRUD 기능을 자동으로 제공하며, 복잡한 쿼리도 간단하게 작성할 수 있도록 지원함.

Spring Data JPA (Java Persistence API)는 Spring 프레임워크의 일부로, 자바 개발자들이 관계형 데이터베이스의 데이터 접근을 더욱 용이하게 할 수 있도록 설계되었습니다. 이 모듈은 JPA를 사용하여 데이터 액세스 계층을 쉽게 구현하고 관리할 수 있게 해주며, 복잡한 쿼리를 간단하게 처리하고, 데이터베이스 작업을 자동화하여 개발자의 생산성을 향상시킵니다.

 

1. Spring Data JPA를 사용하는 이유

  1. 반복적인 CRUD 작업의 생산성 향상
    • 공통 인터페이스 제공
    • 구현체를 작성할 필요 없음
  2. 쿼리 메소드 기능
    • 메소드 이름만으로 쿼리 생성
    • JPQL을 몰라도 쿼리 작성 가능
  3. 페이징과 정렬 기능 내장
    • 데이터 조회와 카운팅을 동시에 처리

2. JpaRepository: Spring Data JPA의 공통 인터페이스

JpaRepository<T, ID>는 Spring Data JPA에서 제공하는 기본적인 CRUD 기능을 포함한 인터페이스로,
자동으로 구현체가 생성되어 데이터베이스 조작을 쉽게 할 수 있음.

 

특징

  • CrudRepository → PagingAndSortingRepository → JpaRepository 순으로 확장됨.
  • save(), findById(), findAll(), deleteById() 등의 기본 메서드 제공.
  • Pageable을 활용한 페이징 및 정렬 기능 지원.

예제

public interface UserRepository extends JpaRepository<User, Long> {
}

 

3. 쿼리 메서드 (Query Method)

Spring Data JPA는 메서드 네이밍 규칙을 기반으로 자동으로 JPQL 쿼리를 생성함.

 

기본 메서드

User findById(Long id);
List<User> findAll();
void deleteById(Long id);

 

조건 검색

List<User> findByName(String name); // WHERE name = ?
List<User> findByEmailContaining(String email); // WHERE email LIKE '%keyword%'
List<User> findByNameOrEmail(String name, String email); // WHERE name = ? OR email = ?
List<User> findByNameOrderByIdDesc(String name); // WHERE name = ? ORDER BY id DESC
페이징 및 정렬
Page<User> findByName(String name, Pageable pageable);

 

@Query 사용 (JPQL / Native Query 지원)

@Query("SELECT u FROM User u WHERE u.email = :email")
User findUserByEmail(@Param("email") String email);

@Query(value = "SELECT * FROM user WHERE email = ?1", nativeQuery = true)
User findByEmailNative(String email);

 

4. 페이징 처리 (Paging & Sorting)

Spring Data JPA에서는 페이징과 정렬을 쉽게 처리할 수 있도록 Pageable과 Sort를 지원함.
이를 활용하면 데이터를 한 번에 가져오는 것이 아니라, 필요한 만큼씩 가져올 수 있어 성능 최적화 가능.

 

1) Pageable을 활용한 페이징

페이징 처리는 Page 인터페이스와 Pageable을 활용하여 구현 가능.

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    Page<User> findByName(String name, Pageable pageable);
}

 

2) 컨트롤러에서 페이징 처리하는 방법

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
public class UserController {
    private final UserRepository userRepository;

    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @GetMapping
    public Page<User> getUsers(@RequestParam(defaultValue = "0") int page,
                               @RequestParam(defaultValue = "10") int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findAll(pageable);
    }
}

 

  • PageRequest.of(page, size): 페이지 번호(page), 페이지 크기(size) 설정.
  • Page<User>: 반환값이 페이지 단위(totalElements, totalPages 포함).

 

5. Fetch Type 관련 문제 및 해결 방법

JPA에서 연관 관계(OneToMany, ManyToOne, OneToOne, ManyToMany) 를 처리할 때,
데이터를 어떻게 가져올지 결정하는 Fetch Type(EAGER, LAZY) 이 중요함.

 

1) Fetch Type 종류

FetchType 설명
EAGER 연관된 엔티티를 즉시 로딩 (JOIN 사용)
LAZY 연관된 엔티티를 필요할 때 로딩 (프록시 사용)

 

 

2) 문제점

  • EAGER (즉시 로딩) 문제점
    • 불필요한 데이터까지 한 번에 가져와서 성능 저하 발생 가능.
    • 다대일(ManyToOne) 관계에서는 조인이 많아지면 쿼리 성능 저하.
  • LAZY (지연 로딩) 문제점
    • 연관된 엔티티가 필요한 시점에 가져오기 때문에 LazyInitializationException 발생 가능.

3) 해결 방법

 

1. 즉시 로딩 (EAGER) 대신 LAZY로 변경

@Entity
public class User {
    @ManyToOne(fetch = FetchType.LAZY)
    private Team team;
}

EAGER를 피하고 LAZY를 기본 사용하는 것이 성능적으로 유리함.

 

2. Fetch Join 사용 (@Query)

@Query("SELECT u FROM User u JOIN FETCH u.team WHERE u.id = :id")
User findUserWithTeam(@Param("id") Long id);

✅ JOIN FETCH를 사용하면 LAZY 로딩 시에도 한 번의 쿼리로 가져올 수 있음.

 

3. EntityGraph 사용

@EntityGraph(attributePaths = {"team"})
@Query("SELECT u FROM User u WHERE u.id = :id")
User findUserWithTeamUsingGraph(@Param("id") Long id);

✅ @EntityGraph를 사용하면 연관된 엔티티를 즉시 로딩(EAGER)처럼 가져오되, JPQL의 JOIN FETCH처럼 활용 가능.

 

 

 

✅ 요약

  • JpaRepository<T, ID>: 기본 CRUD 제공, 페이징 및 정렬 기능 포함.
  • 쿼리 메서드: 메서드 네이밍을 기반으로 자동 쿼리 생성.
  • @Query를 활용하여 JPQL 및 Native Query 사용 가능.
  • 복잡한 검색, 정렬, 페이징을 쉽게 구현할 수 있음.

 

  • 페이징 (Pageable)
    • Page<T>와 PageRequest.of(page, size)를 활용하여 데이터 조회.
  • Fetch Type 문제 해결
    • EAGER는 불필요한 쿼리를 많이 발생시키므로 기본적으로 LAZY 사용.
    • 필요할 때 JOIN FETCH, EntityGraph를 활용하여 최적화.

 

 

대소문자 구별하고싶을 때,

ALTER TABLE users MODIFY name VARCHAR(255) COLLATE utf8mb4_bin;
ALTER DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

MySQL에서 문자 인코딩과 정렬(collation)을 변경하는 명령어(대소문자 구분+이모지 지원)

데이터베이스의 문자셋을 바꿔도 기존 테이블은 영향을 받지 않음 테이블별로 따로 ALTER TABLE을 실행해야 함.