사용자 정의 레포지토리 Spring Data Jpa의 JpaRepository는 인터페이스만 정의하고 구현체는 스프링이 자동 생성해준다. JpaRepository에서는 제공해주는 기능이 참 많지만 그러나 경우에 따라서 별도의 메소드를 직접 정의해야할 때가 많다. 그럴 때 사용자 정의 리포지토리를 구현해서 더욱 유연하고 효율적인 개발을 실천한다. 장점 복잡한 쿼리 구현Spring Data JPA는 기본적인 쿼리 기능을 제공한다.복잡한 쿼리나 특정 조건에 맞는 쿼리를 구현하기에는 부족할 수 있다. 사용자 정의 리포지토리를 사용하면 MyBatis, JPQL, Native SQL 등을 직접 사용하여 원하는 쿼리를 자유롭게 작성할 수 있다. 비즈니스 로직 추가비즈니스 로직을 함께 처리해야 하는 경우 사용자 정의 리포지토리가 유용하다. 예시 : 특정 조건에 따라 데이터를 필터링, 여러 엔터티를 조인하여 결과를 가져오는 등의 로직을 구현 성능 향상Spring Data JPA는 다양한 기능을 제공하기 위해 추상화 레이어를 사용한다. 하지만 특정 상황에서는 이 추상화 레이어가 오히려 성능 저하를 초래할 수 있다. 사용자 정의 리포지토리를 사용하면 불필요한 추상화 레이어를 제거하고 쿼리를 최적화하여 성능을 향상시킬 수 있다. 유지 보수성 향상리포지토리에 대한 비즈니스 로직을 분리하여 관리하면 코드의 가독성과 유지 보수성을 향상시킬 수 있다. 여러 개발자가 프로젝트에 참여하는 경우 사용자 정의 리포지토리를 통해 각 개발자가 담당하는 영역을 명확하게 구분할 수 있다. 테스트 용이성 향상사용자 정의 리포지토리는 단위 테스트를 수행하기에도 유리하다. 사용자 정의 리포지토리를 사용하면 쿼리와 비즈니스 로직을 분리하여 각각 독립적으로 테스트할 수 있다. 기능 구현 방식 JPA 직접 사용 (EntityManager) 스프링 JDBC Template 사용 MyBatis 사용 Querydsl 사용 데이터베이스 커넥션 직접 사용 구현 방법 JpaRepository를 상속받는 인터페이스를 생성한다. public interface MemberRepository extends JpaRepository < Member , Long > {
//...
} 내가 원하는 기능을 구현하기 위한 인터페이스를 생성한다. public interface MemberRepositoryCustom {
List < Member > findMemberCustom ();
} 2번에서 생성한 인터페이스를 구현한 클래스를 생성하고, 메소드를 정의한다.주로 사용자 정의 인터페이스명와 Impl을 합친 단어를 클래스명으로 사용한다. @RequiredArgsConstructor
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom {
private final EntityManager em ;
@Override
public List < Member > findMemberCustom () {
//실제로 사용하고 싶은 기능을 구현
return em . createQuery ( "select m from Member m" ). getResultList ();
}
} 1번에서 작성한 인터페이스가 상속받는 인터페이스 목록에 2번에서 생성한 인터페이스를 추가한다. public interface MemberRepository extends JpaRepository < Member , Long >, MemberRepositoryCustom {
} 이제 원하는 곳에서 사용하면 된다. @Test
public void callCustom () {
List < Member > result = memberRepository . findMemberCustom ();
} JPA Auditing 엔터티의 생성, 수정 정보를 자동으로 추적하여 기록하는 기능 개발자가 직접 코드를 작성하지 않고도 엔터티의 변경 이력을 관리할 수 있도록 도와준다. 장점 개발 생산성 향상엔터티 변경 이력 관리 코드를 직접 작성할 필요 없이 자동으로 기록 데이터 추적 및 감사누가, 언제 엔터티를 변경했는지 쉽게 추적 가능 변경 이력 관리엔터티 변경 이력을 통해 과거 데이터 복원 및 비교 가능 사용 방법 공통된 로직을 처리하기 위한 공용 클래스를 생성한다. @EntityListeners ( AuditingEntityListener . class )
@MappedSuperclass
public class BaseEntity {
@CreatedDate
@Column ( updatable = false ) //"updatable = false"를 지정하면 수정 불가능한 컬럼인 것을 의미한다.
private LocalDateTime createdDate ; //작성일시
@LastModifiedDate
private LocalDateTime lastModifiedDate ; //수정일시
@CreatedBy
@Column ( updatable = false )
private String createdBy ; //작성자
@LastModifiedBy
private String lastModifiedBy ; //수정자
} 공통 속성이 필요한 엔티티 클래스가 공용 클래스를 상속받게 한다. @Entity
@Getter
@EntityListeners ( AuditingEntityListener . class )
@NoArgsConstructor ( access = AccessLevel . PROTECTED )
@ToString
public class User extends BaseEntity {
@Id
@GeneratedValue ( strategy = GenerationType . IDENTITY )
private Long id ;
private String username ;
public User ( String username ) {
this . username = username ;
}
} @SpringBootApplication 어노테이션이 붙은 시작 클래스에 찾아가서 @EnableJpaAuditing 어노테이션을 추가한다.아래처럼 설정하면 저장 시점에 등록일, 등록자, 수정일, 수정자에 같은 데이터가 저장된다. 만약 저장 시점에 등록일과 등록자만 저장하고 싶다면 @EnableJpaAuditing 어노테이션에 modifyOnCreate = false 옵션을 주면 된다. @EnableJpaAuditing
@SpringBootApplication
public class DataJpaApplication {
public static void main ( String [] args ) {
SpringApplication . run ( DataJpaApplication . class , args );
}
} 3번에서 사용한 클래스에 @Bean을 통해서 공통 속성에 들어갈 값을 지정한다. 아래 예시에서는 String을 썼지만 실제 스펙에서는 제네럴 타입이기 때문에 프로젝트 스펙에 맞게 수정하면 된다. UID.randomUUID().toString()도 예시를 위해 넣은 것이지 실제로는 세션에 있는 회원 아이디같은 값이 들어간다.@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());
return new AuditorAware < String >() {
@Override
public Optional < String > getCurrentAuditor () {
return Optional . of ( UUID . randomUUID (). toString ());
}
};
}
} 도메인 클래스 컨버터 (Web 확장) Spring Data JPA가 제공하는 HTTP 요청 파라미터 또는 경로 변수에서 엔터티 객체로 변환하는 기능 컨트롤러 코드에서 엔터티 ID 대신 엔터티 객체를 직접 사용할 수 있어 코드를 더욱 명확하고 간결하게 작성할 수 있다. 도메인 클래스 컨버터도 리포지토리를 사용해서 엔티티를 찾음 도메인 클래스 컨버터 사용 전 @GetMapping ( "/members/{id}" )
public String findMember ( @PathVariable ( "id" ) Long id ) {
Member member = memberRepository . findById ( id ). get ();
return member . getUsername ();
} 도메인 클래스 컨버터 사용 후 @GetMapping ( "/members/{id}" )
public String findMember ( @PathVariable ( "id" ) Member member ) {
return member . getUsername ();
} 장점 코드 간결화엔터티 ID 대신 엔터티 객체를 직접 사용하여 코드를 더욱 명확하고 간결하게 작성할 수 있다. 유지 보수성 향상코드를 더욱 쉽게 이해하고 유지 관리할 수 있다. 코드 재사용성 향상엔터티 객체를 직접 사용하여 코드 재사용성을 높일 수 있다. 주의사항 도메인 클래스 컨버터로 엔티티를 파라미터로 받으면, 이 엔티티는 단순 조회용으로만 사용해야 한다. 트랜잭션이 없는 범위에서 엔티티를 조회했으므로, 엔티티를 변경해도 DB에 반영되지 않는다. 엔터티 ID만 사용하는 경우 성능 저하를 초래할 수 있다. 엔터티 클래스에 대한 의존성을 증가시킬 수 있다. 페이징과 정렬 (Web 확장) Spring Data Jpa가 제공하는 페이징과 정렬 기능을 Spring MVC에서 편리하게 사용할 수 있다. 파라미터로 Pageable을 받아서 사용한다. Pageable 은 인터페이스이기 때문에, 실제로는 org.springframework.data.domain.PageRequest 객체가 생성된다.요청 파라미터 예시` /members?page=0&size=3&sort=id,desc&sort=username,desc` page size sort정렬 조건 예시 : 정렬 속성,정렬 속성…(ASC DESC)
정렬 방향을 변경하고 싶으면 sort 파라미터추가 (ASC 생략 가능) 환경설정 spring.data.web.pageable.default-page-size = 20 /# 기본 페이지 사이즈/
spring.data.web.pageable.max-page-size = 2000 /# 최대 페이지 사이즈/ @GetMapping ( "/members" )
public Page < MemberDto > list ( @PageableDefault ( size = 50 , sort = "username" , direction = Direction . DESC ) Pageable pageable ) {
return memberRepository . findAll ( pageable ). map ( m -> new MemberDto ( m . getId (), m . getUsername (), null ));
} 페이징 정보가 둘 이상인 경우 @Qualifier 어노테이션에 지정한 값이 접두사가 된다.만약 @Qualifier("member")일 때 페이징 정보를 요청하고 싶다면 /members?member_page=0&order_page=1처럼 요청하면 된다. @GetMapping ( "/members" )
public Page < MemberDto > list ( @Qualifier ( "member" ) Pageable memberPageable , @Qualifier ( "order" ) Pageable orderPageable ) {
//...
} 출처