스프링 데이터 JPA는 인터페이스만 정의하고, 구현체는 스프링이 자동생성한다. 하지만 이를 안쓰고 스프링 데이터 JPA가 제공하는 인터페이스를 직접 구현하면 구현해야 하는 기능이 너무 많다. 이를 해결해 주는것이 사용자 정의 인터페이스이다.
우선 인터페이스를 하나 만든다.
package study.datajpa.repository;
import study.datajpa.entity.Member;
import java.util.List;
public interface MemberRepositoryCustom {
List<Member> findCustom();
}
그리고 이 인터페이스를 구현하는 Impl 객체를 하나 만든다
package study.datajpa.repository;
import lombok.RequiredArgsConstructor;
import study.datajpa.entity.Member;
import javax.persistence.EntityManager;
import java.util.List;
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom{
private final EntityManager em;
@Override
public List<Member> findCustom() {
return em.createQuery("select m from Member m")
.getResultList();
}
}
이제 인터페이스 안에 있는 기능은 구현되어 있다. 이를 JPA 데이터 리포지토리에 extends 해주면 된다.
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom
List<Member> result = memberRepository.findMemberCustom();
이렇게 호출해보면 정상 호출 되는것을 볼 수 있다.
리포지토리 인터페이스 이름 + Impl
이를 지키면 스프링 데이터 JPA가 인식해서 스프링 빈으로 등록한다.
너무 많은 로직을 한 리포지토리에 몰아넣거나, 핵심 비즈니스 로직과 그렇지 않은것을 구분하지 못하면 중에 혼란이 발생할 수 있다. 따라서 이를 분리하는 방법을 고려해야 할 수 도 있다.
엔티티 변경, 추가, 등록자, 등록 시간같은걸 추적하는 기능이다.
기본적으로 엔티티에는 등록일, 수정일은 기본적으로 등록이 되는것이 권장된다.
@MappedSuperclass
@Getter
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();
}
}
이렇게 시간을 기록하는 Entity를 생성하고, 모든 엔티티가 이를 상속받도록 해주면 된다.
등록자의 경우
AuditorProvier라는 함수를 이용하면, Entity가 수정도리 때 마다 자동으로 등록되도록 만들 수 있다.
설정
@EnableJpaAuditing 스프링 부트 설정 클래스에 적용해야함
@EntityListeners(AuditingEntityListener.class) 엔티티에 적용
@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());
}
}
이런식으로 main함수를 수정해주면 된다.
컨트롤러로 PathVariable을 통해서 id가 넘어온다고 하자.
@RestController
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
@GetMapping("/members/{id}")
public String findMember(@PathVariable("id") Long id) {
Member member = memberRepository.findById(id).get();
return member.getUsername();
}
}
이를 수정하면 다음과 같이 id만 가지고 바로 member 클래스로 넘어갈 수 있다.
@RestController
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
@GetMapping("/members/{id}")
public String findMember(@PathVariable("id") Member member) {
return member.getUsername();
}
}
하지만 이렇게 사용되는 경우에는 무조건 조회용으로만 사용해야한다.
Pageable을 인자로 받을 수 있고, page를 반환할 수 있다.
@GetMapping("/members")
public Page<Member> list(Pageable pageable) {
Page<Member> page = memberRepository.findAll(pageable);
return page;
}
findAll을 통해 page를 받아온 다음 이를 반환해 줄 수 있는것이다.
url뒤에 request param으로
?page=0&size=x
같이 붙여주면 한페이지에 몇개의 자료가 들어가는지 알아서 채워서 반환해줄 수 있다.
물론 해당 예시는 Entity를 바로 반환하니깐 DTO를 만들어서 사용하는것이 권장된다.