사용자 정의 리포지토리
리포지토리에 원하는 기능만 구현하여 사용하고 싶을 때, 직접 구현하면 인터페이스에 구현해야 하는 기능이 너무 많다.(인터페이스이기 때문에 모든 메서드 구현 필요), 이를 사용자 정의 리포지토리로 해결한다.
대부분 이 방법은 복잡한 쿼리를 Querydsl로 풀 때 사용된다.
- 사용자 정의 인터페이스 이름과 구현 클래스 이름이 비슷하므로 더 직관적
- 여러 인터페이스를 분리해서 구현하는 것도 가능하기 때문에 새롭게 변경된 이 방식을 권장
기존 리포지토리에 이 인터페이스를 상속받아 사용
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom
//사용자 정의 인터페이스
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();
}
}
Naming 관례
사용자 정의 인터페이스: (이름)
사용자 정의 인터페이스 구현: (이름)+Impl
Auditing
유지, 보수를 위해서 서비스가 등록일, 수정일, 등록자, 수정자를 갖고 있는 것이 좋음
설정
@EnableHpaAuditing -> 스프링 부트 설정 클래스에 적용해야함
@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());
}
}
등록자, 수정자를 처리해주는 AuditorAware 스프링 빈 등록
실무에서는 세션 정보나, 스프링 시큐리티 로그인 정보에서 ID를 받음@Bean public AuditorAware<String> auditorProvider(){ return () -> Optional.of(UUID.randomUUID().toString()); }
사용
@EntityListeners(AuditingEntityListener.class) -> 엔티티에 적용
@MappedSuperclass -> 다른 엔티티에 값만 상속시키기 위한 부모 엔티티 어노테이션
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDateTime;
@LastModifiedDate
private LocalDateTime lastModifiedDateTime;
}
사용 어노테이션
- @CreatedDate
- @LastModifiedDate
- @CreatedBy
- @LastModifiedBy실무에서는 등록시간, 수정시간이 필요하지만 등록자, 수정자는 필요하지 않을 수 있다. 때문에 시간타입 엔티티를 부모로 두고 이를 상속받는 등록/수정자 엔티티를 분리 구현하는 것이 좋다.
전체 적용
@EntityListeners(AuditingEntityListener.class)를 생략하고 엔티티 전체에 적용하려면 orm.xml에 다음과 같이 등록한다.
<?xml version=“1.0” encoding="UTF-8”?>
http://xmlns.jcp.org/xml/ns/persistence/orm”
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=“http://xmlns.jcp.org/xml/ns/persistence/
orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_2.xsd”
version=“2.2">
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener class="org.springframework.data.jpa.domain.support.AuditingEntityListener”/>
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>
도메인 클래스 컨버터
HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아 자동 바인딩
@RestController
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
@GetMapping("/member/{id}")
public String getMember(@PathVariable("id") Long id) {
Member member = memberRepository.findById(id).get();
return member.getUsername();
}
@GetMapping("/member2/{id}") //도메인 클래스 컨버터
public String getMemberConvertor(@PathVariable("id") Member member) {
return member.getUsername();
}
@PostConstruct
public void init() {
memberRepository.save(new Member("memberA"));
}
}
주의: 도메인 클래스 컨버터로 엔티티를 파라미터로 받으면, 이 엔티티는 트랜잭션이 없는 범위에 존재하기 때문에 수정을 하면 안된다. 단순 조회용으로만 사용한다.
@PostConstructor
Bean은 new키워드를 이용해 생성하지 않기 때문에 생성자를 호출할 수 없다.
Bean이 생성될 때 실행할 매서드를 @PostConstructor로 구현할 수 있다.
페이징과 정렬
@GetMapping("/members")
public Page<Member> list(Pageable pageable) {
Page<Member> page = memberRepository.findAll(pageable);
return page;
}
- 파라미터로 Pageable을 받을 수 있다. (모든 리포지토리 메서드 마지막 파라미터로 전달 가능)
- Pageable은 인터페이스, 실제는 org.springframework.data.domain.PageRequest 객체 생성
- 따라서 파라미터로 Pageable을 넘기면 스프링 데이터 JPA가 PageRequest를 생성해 주입해준다.
요청 파라미터
- 예) /members?page=0&size=4&sort=id,desc&sort=username.desc
- page: 현재 페이지, 0부터 시작
- size: 한 페이지에 노출할 데이터 건수
- sort: 정렬 조건 정의, (기본 asc)
설정
- 글로벌 설정
properties 또는 yml파일에 추가
spring.data.web.pageable.default-page-size=20 /# 기본 페이지 사이즈/
spring.data.web.pageable.max-page-size=2000 /# 최대 페이지 사이즈/
- 개별 설정
@PageableDefault 어노테이션 사용
@GetMapping("/members")
public Page<Member> list(@PageableDefault(size = 4, sort = "username") Pageable pageable) {
Page<Member> pageList = memberRepository.findAll(pageable);
return pageList;
}
접두사
- 페이징 정보가 둘 이상이면 접두사로 구분
- @Qualifier에 접두사명 추가 "{접두사명}_XXX"
- 예제: /members?member_page=1&order_page=2
public String list{
@Qualifier("member") Pageable memberPageable,
@Qualifier("order") Pageable orderPageable,
...
}
Page 내용을 DTO로 변환하기
- 엔티티를 API로 노출하면 다양한 문제가 발생
- 꼭 DTO로 변환해서 반환하자!
- Page는 map()을 지원해서 내부 데이터를 다른 것으로 변경 가능
@GetMapping("/members")
public Page<MemberDto> list(Pageable pageable){
Page<Member> page = memberRepository.findAll(pageable);
Page<MemberDto> pageDto = page.map(m -> new MemberDto(m.getId(), m.getUsername(), null));
return pageDto;
}
Page를 1부터 시작하기
비추천(유지 보수 어려움)
Pageable, Page를 파라미터, 반환값으로 사용하지 않고 직접 클래스를 만들어 처리
직접 PageRequest(Pageable 구현체)를 생성해서 리포지토리에 넘긴다.
응답값도 Page 대신 직접 만들어 제공
@GetMapping("/members")
public MyPage<MyMemberDto> list(@PageableDefault pageable pageable){
PageRequest request = PageRequest.of(1,2);
Page<MemberDto> map = memberRepository.findAll(request)
.map(MemberDto::new);
MyPage<MyMemberDto> myMap = ...
}
"본 포스트는 작성자가 공부한 내용을 바탕으로 작성한 글입니다.
잘못된 내용이 있을 시 언제든 댓글로 피드백 부탁드리겠습니다.
항상 정확한 내용을 포스팅하도록 노력하겠습니다."
'Spring' 카테고리의 다른 글
equals(), hashcode() (0) | 2023.02.06 |
---|---|
Spring data JPA: save() 메소드 (0) | 2023.02.06 |
Spring data JPA: 쿼리 메소드 (3) (0) | 2023.02.06 |
Spring data JPA: 쿼리 메소드 (2) (0) | 2023.02.06 |
Spring data JPA: 쿼리 메소드 (1) (0) | 2023.02.06 |