티스토리 뷰
Auditing
Entity를 생성 변경할 때 변경한 사람과 시간을 추적하고 싶으면?
- 등록일
- 수정일
- 등록자
- 수정자
스프링 데이터 JPA 사용
설정
@EnableJpaAuditing
-> 스프링 부트 설정 클래스에 적용해야함@EntityListeners(AuditingEntityListener.class)
-> Entity에 적용사용 어노테이션
@CreatedDate
@LastModifiedDate
@CreateBy
@LastModifiedBy
@EntityListeners(AuditingEntityListener.class)
@MappedSuperClass
@Getter
public class BaseEntity{
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime lastModifiedDate;
}
등록자, 수정자를 처리해주는 AuditorAware
스프링 빈 등록
@Bean
public AuditorAware<String> auditorProvider(){
return () -> Optional.of(UUID.randomUUID().toString());
}
실무에서는 세션 정보나 스프링 시큐리티 로그인 정보에서 ID를 받음
참고 : 실무에서 대부분의 Entity는 등록시간, 수정시간이 필요하지만, 등록자, 수정자는 없을 수도 있다. 그래서 다음과 같이 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)
옵션을 사용하면 된다.
Pageable
설명하기에 앞서 실무에서 가장 중요한 내용 먼저 언급하고 써내려 갈려고 한다.
실무에서 가장 중요한 것은 페이지를 유지하면서 Entity를 DTO로 변환하여 Controller에 뿌리는 것이다
-> 이는 Entity가 절대로 노출되서는 안되기 때문이다.Page<Member> page = memberRepository.findByAge(10, pageRequest); Page<MemberDto> dtoPage = page.map(m -> new MemberDto());
카운트 쿼리를 분리해야 한다(실무에서는 left join, right join 등을 통해서 데이터를 가져오는데 총페이지 쿼리는 join을 할 필요가없다. 어차피 데이터 개수는 동일하다)
- 카운트 쿼리에서 성능 저하가 발생하기 때문이다.
@Query(value = "select m from Member m", countQuery = "select count(m) from Member m") Page<Member> findByMember(Pageable pageble);
스프링 데이터 JPA를 사용하면 페이징과 정렬을 말도안되게 간단하게 구현이 가능하다.
- org.springframework.data.domain.Sort : 정렬기능
- org.springframework.data.domain.Pageable : 페이징 기능(내부에
sort
포함)
반환 타입
- org.springframework.data.domain.Page : 추가 count 쿼리 결과를 포함하는
페이징
- org.springframework.data.domain.Slice : 추가 count 쿼리 없이 다음 페이지만 확인 가능 (내부적으로 limit + 1 조회)
- List : 추가 count 쿼리 없이 결과만 반환
예제
두번째 파라미터로 받은
Pageable
은 인터페이스 이다. 즉 실제로 사용할 때는 해당 인터페이스를 구현한org.springframework.data.domain.PageRequest
객체를 사용한다.PageRequest
생성자의 첫번째 파라미터에는현재페이지
를, 두번째 파라미터에는조회할 데이터 수
를 입력한다. 추가로 정렬 정보도 파라미터로 사용할 수 있다.참고로 페이지는
0
부터 시작한다.@Test public void page() throws Exception { //given memberRepository.save(new Member("member1", 10)); memberRepository.save(new Member("member2", 10)); memberRepository.save(new Member("member3", 10)); memberRepository.save(new Member("member4", 10)); memberRepository.save(new Member("member5", 10)); //when PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC,"username")); Page<Member> page = memberRepository.findByAge(10, pageRequest); //then List<Member> content = page.getContent(); //조회된 데이터 assertThat(content.size()).isEqualTo(3); //조회된 데이터 수 assertThat(page.getTotalElements()).isEqualTo(5); //전체 데이터 수 assertThat(page.getNumber()).isEqualTo(0); //페이지 번호 assertThat(page.getTotalPages()).isEqualTo(2); //전체 페이지 번호 assertThat(page.isFirst()).isTrue(); //첫번째 항목인가? assertThat(page.hasNext()).isTrue(); //다음 페이지가 있는가? }
추가로 모바일 처럼 스크롤을 아래로 내리거나
더보기
같은 버튼을 클릭해서 보여주는 페이징은Slice
를 사용한다.
별도의 개발필요없이 반환타입만Slice
로 변경해주면 된다.DB에 쿼리를 날릴때 limit + 1 숫자만큼 쿼리를 날린다.
Slice<Member> findByAge(int age, Pageable pageable); //Slice 로 반환된 객체로 알 수 있는 메소드 int getNumber(); //현재 페이지 int getSize(); //페이지 크기 int getNumberOfElements(); //현재 페이지에 나올 데이터 수 List<T> getContent(); //조회된 데이터 boolean hasContent(); //조회된 데이터 존재 여부 Sort getSort(); //정렬 정보 boolean isFirst(); //현재 페이지가 첫 페이지 인지 여부 boolean isLast(); //현재 페이지가 마지막 페이지 인지 여부 boolean hasNext(); //다음 페이지 여부 boolean hasPrevious(); //이전 페이지 여부 Pageable getPageable(); //페이지 요청 정보 Pageable nextPageable(); //다음 페이지 객체 Pageable previousPageable();//이전 페이지 객체
Projections
Entity 대신에 DTO를 편리하게 조회할 때 사용
전체 Entity가 아니라 만약 회원 이름만 딱 조회하고 싶으면?
- 먼저 인터페이스를 구현한다.
public interface UsernameOnly{
String getUsername();
}
- 조회할 Entity의 필드를 getter 형식으로 지정하면 해당 필드만 선택해서 조회(Projection)
- Repository를 구현
public interface MemberRepository ... {
List<UsernameOnly> findProjectionsByUsername(String username);
}
- 메서드 이름은 자유, 반환 타입으로 인지 한다.
- Test
@Test
public void projections() throws Exception {
//given
Team teamA = new Team("teamA");
em.persist(teamA);
Member m1 = new Member("m1", 0, teamA);
Member m2 = new Member("m2", 0, teamA);
em.persist(m1);
em.persist(m2);
em.flush();
em.clear();
//when
List<UsernameOnly> result = memberRepository.findProjectionsByUsername("m1");
//then
Assertions.assertThat(result.size()).isEqualTo(1);
}
- query 확인
select m.username from member m where m.username = 'm1';
- username 만 조회하는 것을 확인 할 수 있다.
인터페이스 기반 Closed Projections
프로퍼티 형식(getter)의 인터페이스를 제공하면, 구현체는 스프링 데이터 JPA가 제공
public interface UsernameOnly{
String getUsername();
}
클래스 기반 Projection
다음과 같이 인터페이스가 아닌 구체적인 DTO 형식도 가능
생성자의 파라미터 이름으로 매칭
public class UsernameOnlyDto {
private final String username;
public UsernameOnlyDto(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
}
Native Query + Projection 활용
@Query(value = "select m.member_id as id, m.username, t.name as teamName " +
"from member m left join team t",
countQuery = "select count(*) from member",
nativeQuery = true)
Page<MemberProjection> findByNativeProjection(Pageable pageable);
'JPA' 카테고리의 다른 글
[JPA] QueryDSL 기본 문법 알아보기 (0) | 2023.04.07 |
---|---|
[JPA] 영속성 컨텍스트 이해 및 정리 (0) | 2023.04.03 |
[Java] JPA 최적화(완벽 요약) (0) | 2023.03.30 |
[JAVA]JPA N+1 문제란?(해결방안) (0) | 2023.03.29 |
Spring Boot JPA 핵심 요약(이거만 보면 이해한다) (0) | 2023.03.15 |
- Total
- Today
- Yesterday
- 인덱스
- jenkins
- oauth2
- java
- R-Tree
- DispatcherServlet
- db
- mysql
- 쓰레드
- TCP
- 공간쿼리
- Spring Security
- spring boot
- jpa
- 논블로킹
- lock
- 영속성 컨텍스트
- github
- thread
- Index
- 스프링
- spring
- 비동기
- 데이터베이스
- 다운로드
- Excel
- spring mvc
- database
- 네트워크
- GIS
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |