확장 기능
- 김영한님의 실전! 스프링 데이터 JPA 강의를 기반으로 사용자 정의 리포지토리, Auditing 단일 추적, 도메인 클래스 컨버터, 페이징과 정렬 등 스프링 데이터 JPA의 여러 확장 기능 설정 및 활용 방법을 정리함
사용자 정의 리포지토리 구현
-
Spring Data JPA 리포지토리는 인터페이스만으로 구현체가 자동 생성되지만, JPA 직접 사용(
EntityManager), Spring JDBC Template, MyBatis, Querydsl 등 다양한 기술과 혼합하여 쿼리를 병합 구현해야 할 때 사용됨
- 사용자 정의 인터페이스 선언
- 확장하고자 하는 쿼리 메서드들의 명세를 정의하는 추가 인터페이스를 선언함
1 2 3
public interface MemberRepositoryCustom { List<Member> findMemberCustom(); }
- 구현 클래스 작성 (
인터페이스 이름 + Impl)- Spring Data 규약에 따라 반드시 인터페이스 이름에
Impl접미사를 붙여 구현체를 작성해야 스프링 데이터 JPA가 이를 인식하고 기능을 연결해 줌
1 2 3 4 5 6 7 8 9 10 11
@RequiredArgsConstructor public class MemberRepositoryImpl implements MemberRepositoryCustom { private final EntityManager em; @Override public List<Member> findMemberCustom() { return em.createQuery("select m from Member m", Member.class) .getResultList(); } }
- Spring Data 규약에 따라 반드시 인터페이스 이름에
- JpaRepository에 사용자 정의 인터페이스 상속
- 기본 제공 인터페이스와 커스텀 인터페이스를 모두 상속하여 한 곳에서 통일된 리포지토리 접근을 가능하게 만듦
1 2
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom { }
Auditing
-
엔티티가 생성되거나 변경될 때 누가, 언제 수행했는지(등록 시간, 수정 시간, 등록자, 수정자)를 자동으로 추적하여 데이터베이스에 기록하는 기능임
- 순수 JPA 방식 사용
- JPA 표준 이벤트 어노테이션인
@PrePersist와@PreUpdate를 직접 활용해 시간을 세팅하고 상속받아 사용하는 방식임
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
@MappedSuperclass public class JpaBaseEntity { @Column(updatable = false) private LocalDateTime createdDate; private LocalDateTime updatedDate; @PrePersist public void prePersist() { LocalDateTime now = LocalDateTime.now(); createdDate = now; updatedDate = now; } @PreUpdate public void preUpdate() { updatedDate = LocalDateTime.now(); } }
- JPA 표준 이벤트 어노테이션인
- Spring Data JPA 방식 사용
- 스프링 부트 애플리케이션에 Auditing 기능을 활성화한 후 등록자 및 수정자 정보를 공급하는 빈을 구성하는 형태로 이루어짐

- 등록 클래스의 상단에
@EnableJpaAuditing을 등록하고,AuditorAware스프링 빈을 등록하여 현재의 클라이언트 정보를 제공함
1 2 3 4 5 6 7 8 9 10 11 12
@EnableJpaAuditing @SpringBootApplication public class DataJpaApplication { public static void main(String[] args) { SpringApplication.run(DataJpaApplication.class, args); } @Bean public AuditorAware<String> auditorProvider() { return () -> Optional.of(UUID.randomUUID().toString()); } }
-
등록일자, 수정일자 전용 기초 역할을 수행할
BaseTimeEntity를 생성 후 이를 다른 엔티티에서 상속받아 사용함
1 2 3 4 5 6 7 8 9 10 11
@EntityListeners(AuditingEntityListener.class) @MappedSuperclass public class BaseTimeEntity { @CreatedDate @Column(updatable = false) private LocalDateTime createdDate; @LastModifiedDate private LocalDateTime lastModifiedDate; }
Web 확장 - 도메인 클래스 컨버터
-
HTTP 파라미터로 명시적으로 전달받은 식별자(
id) 값을 리포지토리에 조회 기능을 연계해 자동으로 엔티티 객체 인스턴스로 대치해 주는 기능임
-
도메인 클래스 컨버터 적용
- 컨트롤러에서
id를 이용해 리포지토리를 직접 호출하지 않고, 매개변수로 명시된 엔티티 타입 선언을 통해 내부에서 처리되도록 위임함
1 2 3 4 5 6 7 8 9 10 11
@RestController @RequiredArgsConstructor public class MemberController { private final MemberRepository memberRepository; @GetMapping("/members/{id}") public String findMember(@PathVariable("id") Member member) { return member.getUsername(); } }
-
영속성 컨텍스트 범위 외의 조회일 가능성이 있으므로 트랜잭션과 무관하게 읽기 작업 등 단순 조회용도로만 사용해야 하며, 데이터를 변경해도 데이터베이스에 반영되지 않음에 주의해야 함
- 컨트롤러에서
Web 확장 - 페이징과 정렬
-
스프링 데이터가 제공하는 페이징 및 정렬 편의 유틸리티를 스프링 MVC에서
Pageable파라미터 단 하나로 간편하게 바인딩하여 처리할 수 있음 - 컨트롤러에 기본 페이징 적용 (
Pageable파라미터 활용)/members?page=0&size=3&sort=id,desc
1 2 3 4
@GetMapping("/members") public Page<Member> list(Pageable pageable) { return memberRepository.findAll(pageable); }
- 페이징 정보 기본값 설정
-
글로벌 설정은
application.yml의spring.data.web.pageable.default-page-size설정을 통해 프로젝트 전체에 기본값을 지정함1 2 3 4 5 6
spring: data: web: pageable: default-page-size: 20 max-page-size: 2000
-
개별 설정이 필요한 경우 컨트롤러의 매개변수 부분에
@PageableDefault(size = 12, sort = "username", direction = Sort.Direction.DESC)어노테이션을 직접 부여하여 처리함1 2 3 4
@GetMapping("/members") public Page<Member> list(@PageableDefault(size = 12, sort = "username", direction = Sort.Direction.DESC) Pageable pageable) { return memberRepository.findAll(pageable); }
-
- 내부 정보 노출을 막기 위한 DTO 변환 반환
- 엔티티를 곧바로 반환하면 데이터베이스 테이블 구조가 강결합되어 노출되고 유연성이 극히 떨어지므로,
map()메서드를 활용해 API 스펙과 일치하는 DTO 형태를 구성하여 반환해야 함
1 2 3 4
@GetMapping("/members") public Page<MemberDto> list(Pageable pageable) { return memberRepository.findAll(pageable).map(MemberDto::new); }
- 엔티티를 곧바로 반환하면 데이터베이스 테이블 구조가 강결합되어 노출되고 유연성이 극히 떨어지므로,
- 시작 페이지를 1부터 구성해야 할 때의 고려 사항
- Spring Data의 기반이 0 페이지로 이루어져 있기 때문에 1 페이지 시작 요건을 맞출 때 내부 처리에 각별한 주의가 필요함

- 속성 파일의
one-indexed-parameters=true설정은 웹 요청 단락에서 값만 변경 적용할 뿐 응답Page객체 내부는 여전히 인덱스가 일치하지 않는 상태로 통지되는 한계가 존재함 - 완전히 어긋남 없는 1 시작 페이지의 결괏값을 클라이언트에 반환해야 한다면
Pageable을 사용하지 않고PageRequest를 직접 생성하고 최종 응답 객체 역시 직접 만들어 반환하는 방식이 권장됨
연습 문제
-
Spring Data JPA 리포지토리의 사용자 정의 메서드를 구현하는 주된 이유는 무엇일까요?
a. 복잡한 쿼리나 JDBC, MyBatis 등 특정 기술을 사용해야 할 때
- 사용자 정의 리포지토리는 Spring Data JPA가 지원하지 않는 복잡한 쿼리나 순수 JPA, JDBC 템플릿, MyBatis 등 다른 기술을 사용할 때 필요함. Spring Data JPA의 한계를 극복하기 위한 방법임
-
Spring Data JPA Auditing에서 엔티티의 생성 시간과 수정 시간을 자동으로 기록하기 위해 주로 사용하는 어노테이션 쌍은 무엇일까요?
a. @CreatedDate, @LastModifiedDate
@CreatedDate는 엔티티가 생성될 때 시간을 자동 주입하고,@LastModifiedDate는 생성 및 수정될 때마다 시간을 갱신해 줌AuditorAware는 사용자 정보를 기록할 때 필요한 인터페이스임
-
Spring Data JPA Auditing 설정 시, 현재 사용자(Auditor) 정보를 가져오기 위해 구현해야 하는 Spring Data 인터페이스는 무엇일까요?
a. AuditorAware
AuditorAware인터페이스를 구현하고 스프링 빈으로 등록해야 Spring Data Auditing이@CreatedBy나@LastModifiedBy필드에 현재 사용자 정보를 채울 수 있음
-
Spring MVC 컨트롤러 메서드에서 웹 요청 파라미터(페이지 번호, 크기, 정렬)를 받아 페이징 및 정렬 기능을 쉽게 적용하도록 도와주는 Spring Data 인터페이스는 무엇일까요?
a. Pageable
- 컨트롤러 메서드 파라미터로
Pageable타입을 사용하면 Spring이 자동으로 웹 요청 파라미터(page,size,sort등)를 바인딩하여 페이징 및 정렬 정보를 제공해 줌
- 컨트롤러 메서드 파라미터로
-
Spring 웹 컨트롤러에서 데이터베이스에서 조회한 엔티티 목록 또는 페이지를 클라이언트에 반환할 때, 내부 구현 정보 노출을 막고 유연성을 확보하기 위해 권장되는 방식은 무엇일까요?
a. 엔티티를 DTO로 변환하여 반환
- 엔티티를 외부에 직접 노출하면 내부 구조가 드러나고 변경이 어려워짐. API 계약 유지를 위해 엔티티를 DTO (Data Transfer Object)로 변환하여 반환하는 것이 가장 권장되는 좋은 설계 방식임
요약 정리
- Spring Data JPA가 기본으로 제공하지 못하는 복잡한 쿼리나 JDBC 연동 요건을 달성하기 위해 사용자 정의 리포지토리를 선언하고 인터페이스(
Custom)와 구현체(Impl)를 병합 연결함 - 데이터의 생성 및 수정 이력(
createdDate,lastModifiedDate) 등을 자동으로 추적하려면 진입 클래스에@EnableJpaAuditing을 활성화한 후, 각 엔티티에AuditingEntityListener를 적용한BaseTimeEntity를 상속 활용하면 편리함 - 도메인 클래스 컨버터는 식별자 파라미터에서 엔티티 매개변수로 변환을 알아서 지원하지만, 영속성 컨텍스트 범위 외의 조회일 수 있어 반드시 단순 조회 용도로만 사용해야 함
- 컨트롤러 부분에서는
Pageable객체를 활용해 페이징 파라미터를 손쉽게 바인딩하고,Page객체의map()기능을 통해 반환용 DTO로 변환 과정을 거쳐 응답 구조를 구성하는 것이 안전함