이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


사용자 정의 리포지토리

사용자 정의 인터페이스

public interface MemberRepositoryCustom {
    List<Member> findMemberCustom();
}

 

사용자 정의 인터페이스 구현 클래스

@RequiredArgsConstructor
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom {
    private final EntityManager em;
    @Override
    public List<Member> findMemberCustom() {
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }
}
  • 규칙: 사용자 정의 인터페이스 이름(리포지토리 인터페이스 이름도 가능) + Impl
  • 스프링 데이터 JPA가 인식해서 스프링 빈으로 등록

 

사용자 정의 인터페이스 상속

public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
}

 

사용자 정의 메서드 호출 코드

List<Member> result = memberRepository.findMemberCustom();

 

실무에서는 주로 QueryDSL이나 SpringJdbcTemplate을 함께 사용할 때 사용자 정의 리포지토리 기능 자주 사용

 

항상 사용자 정의 리포지토리가 필요한 것은 아니다.
그냥 임의의 리포지토리를 만들어도 된다. 예를들어 MemberQueryRepository를 인터페이스가 아닌 클래스로 만들고 스프링 빈으로 등록해서 그냥 직접 사용해도 된다.
물론 이 경우 스프링 데이터 JPA와는 아무런 관계 없이 별도로 동작한다.

 

 

Auditing

엔티티를 생성 및 변경할 때 변경한 사람과 시간을 추적하고 싶을 때 쓰는 기능

(등록일, 수정일, 등록자, 수정자 등)

@EnableJpaAuditing //Auditing
@SpringBootApplication
public class DataJpaApplication {

	public static void main(String[] args) {
		SpringApplication.run(DataJpaApplication.class, args);
	}

	//Auditing
	@Bean
	public AuditorAware<String> auditorProvider() {
		return () -> Optional.of(UUID.randomUUID().toString()); //UUID
		//실무에선 세션에서 가져오거나, 스프링 시큐리티에서 가져와야 한다.
	}

}
  • @EnableJpaAuditing
  • auditorProvider() 메서드 등록(등록자와 수정자를 처리해줌)
@EnableJpaAuditing

이 어노테이션은 JPA 엔티티에 대해 자동으로 @CreatedDate, @LastModifiedDate, @CreatedBy, @LastModifiedBy 같은 어노테이션을 사용해 엔티티의 생성 및 수정 시간을 자동으로 관리할 수 있게 한다.

이를 사용하려면, JpaAuditing 기능을 활성화하기 위해 스프링 부트 애플리케이션 클래스나 설정 클래스에 @EnableJpaAuditing을 추가해야 한다.

 

 

등록일, 수정일만 설정

package study.datajpa.entity;
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity {
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;
    
    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
}

 

등록일, 수정일, 등록자, 수정자 설정

@Getter
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public class BaseEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String lastModifiedBy;
}

 

 

선택 적용

실무에서 대부분의 엔티티는 등록시간, 수정시간이 필요하지만, 등록자, 수정자는 없을 수도 있다.

그래서 다음과 같이 Base 타입을 분리하고, 원하는 타입을 선택해서 상속하면 된다.

public class BaseTimeEntity {
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
}

public class BaseEntity extends BaseTimeEntity { //상속
    @CreatedBy
    @Column(updatable = false)
    private String createdBy;
    
    @LastModifiedBy
    private String lastModifiedBy;
}

 

저장시점에 등록일, 등록자는 물론이고, 수정일, 수정자도 같은 데이터가 저장된다.
데이터가 중복 저장되는 것 같지만, 이렇게 해두면 변경 컬럼만 확인해도 마지막에 업데이트한 유저를 확인할 수 있으므로 유지보수 관점 에서 편리하다.
이렇게 하지 않으면 변경 컬럼이 null 일때 등록 컬럼을 또 찾아야 한다.

 

저장시점에 저장데이터만 입력하고 싶으면 @EnableJpaAuditing(modifyOnCreate = false) 옵션을 사용하면 된다.

기본적으로 @EnableJpaAuditing은 엔티티가 처음 생성될 때(@PrePersist 시점)에 @LastModifiedDate 필드도 함께 설정되도록 되어 있다.

그러나 modifyOnCreate = false를 설정하면, 엔티티가 처음 생성될 때는 @LastModifiedDate 필드를 설정하지 않는다.
즉, 생성 시에는 수정 시간이 기록되지 않고, 이후 엔티티가 업데이트될 때만 @LastModifiedDate 필드가 변경된다.

 

 

페이징과 정렬(Web 확장)

스프링 데이터가 제공하는 페이징과 정렬 기능을 스프링 MVC에서 편리하게 사용할 수 있다.

@GetMapping("/members")
public Page<Member> list(Pageable pageable) {
    Page<Member> page = memberRepository.findAll(pageable);
    return page;
}
  • 이 메서드는 Page<Member> 객체를 반환한다. Page는 Spring Data JPA에서 제공하는 인터페이스로, 페이징된 결과를 포함한다.
  • Page 객체는 현재 페이지의 데이터뿐만 아니라 전체 페이지 수, 현재 페이지 번호, 총 요소 수 등 페이징에 관련된 다양한 메타데이터도 포함하고 있다.
  • Pageable은 페이징 정보(예: 페이지 번호, 페이지 크기, 정렬)를 포함하는 객체이다.
  • 클라이언트가 요청 시 page, size, sort 등의 파라미터를 전달하여 원하는 페이지의 데이터를 요청할 수 있다.
  • 예를 들어, GET /members?page=0&size=10&sort=name,asc는 첫 번째 페이지(0부터 시작)에서 이름을 기준으로 오름차순으로 정렬된 10개의 Member 객체를 반환하게 한다.

 

요청 파라미터

예) /members?page=0&size=3&sort=id,desc&sort=username,desc

  • page: 현재 페이지, (0부터 시작한다.)
  • size: 한 페이지에 노출할 데이터 건수
  • sort: 정렬 조건을 정의한다.
    예) 정렬 속성,정렬 속성...(ASC | DESC), 정렬 방향을 변경하고 싶으면 sort 파라 미터 추가 ( asc 생략 가능)

 

기본 값

-글로벌 설정

application.properties

spring.data.web.pageable.default-page-size=20 /# 기본 페이지 사이즈/
spring.data.web.pageable.max-page-size=2000 /# 최대 페이지 사이즈/

 

-개별설정

@PageableDefault 어노테이션을 사용

@RequestMapping(value = "/members_page", method = RequestMethod.GET)
public String list(
    @PageableDefault(size = 12, sort = "username", direction = Sort.Direction.DESC)
    Pageable pageable) {
    //...
}

 

접두사

페이징 정보가 둘 이상이면 접두사로 구분

@Qualifier 에 접두사명 추가 "{접두사명}_xxx"

예제: /members?member_page=0&order_page=1

public String list(
    @Qualifier("member") Pageable memberPageable,
    @Qualifier("order") Pageable orderPageable, ...

 

 

DTO로 변환

엔티티를 API로 노출하면 다양한 문제가 발생한다. 그래서 엔티티를 꼭 DTO로 변환해서 반환해야 한다.

Page는 map() 을 지원해서 내부 데이터를 다른 것으로 변경할 수 있다.

@Data
public class MemberDto {
    private Long id;
    private String username;
    
    public MemberDto(Member m) {
        this.id = m.getId();
        this.username = m.getUsername();
	}
}

 

@GetMapping("/members")
//public Page<MemberDto> list(Pageable pageable) {
//     Page<Member> page = memberRepository.findAll(pageable);
//     Page<MemberDto> pageDto = page.map(MemberDto::new);
//     return pageDto;
//}

public Page<MemberDto> list(Pageable pageable) {
     return memberRepository.findAll(pageable).map(MemberDto::new);
 }

 

테스트

localhost:8080/members?page=0&size=10