[멋쟁이사자처럼 부트캠프 TIL 회고] 백엔드 부트캠프 13기: Java 47일차 Spring Data JPA
Spring Data JPA
- Spring 프레임워크에서 JPA(Java Persistence API)를 더 쉽게 사용할 수 있도록 도와주는 모듈(프레임워크).
- 기본적인 CRUD 기능을 자동으로 제공하며, 복잡한 쿼리도 간단하게 작성할 수 있도록 지원함.
Spring Data JPA (Java Persistence API)는 Spring 프레임워크의 일부로, 자바 개발자들이 관계형 데이터베이스의 데이터 접근을 더욱 용이하게 할 수 있도록 설계되었습니다. 이 모듈은 JPA를 사용하여 데이터 액세스 계층을 쉽게 구현하고 관리할 수 있게 해주며, 복잡한 쿼리를 간단하게 처리하고, 데이터베이스 작업을 자동화하여 개발자의 생산성을 향상시킵니다.
1. Spring Data JPA를 사용하는 이유
- 반복적인 CRUD 작업의 생산성 향상
- 공통 인터페이스 제공
- 구현체를 작성할 필요 없음
- 쿼리 메소드 기능
- 메소드 이름만으로 쿼리 생성
- JPQL을 몰라도 쿼리 작성 가능
- 페이징과 정렬 기능 내장
- 데이터 조회와 카운팅을 동시에 처리
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을 실행해야 함.