스프링 데이터 JPA

HUSII·2023년 7월 12일
0

JPA

목록 보기
5/7

기존 레포지토리 객체 대신
JpaRepository<T,ID> 인터페이스를 상속받는 인터페이스를 이용한다.
이 레포지토리는 대부분의 공통 메서드를 제공한다.

--
유저의 이름으로 조회하고 싶을때

List<Member> findByUsername(String username);

혹은

@Query(name = "Member.findByUsername")
List<Member> findByUsername(@Param("username") String username);

위 두개의 메서드 둘다 애플리케이션이 시작하는 시점에 오류를 체크해준다.

하지만 멤버 객체의 필드명이 바뀌면 위 메서드명도 바꿔줘야 한다.


@Query, 리포지토리 메소드에 쿼리 정의하기

이 기능을 자주 사용함

@Query("select m from Member m where m.username = :username and m.age = :age")
List<Member> findUser(@Param("username") String username, @Param("age") int age);

컬렉션 파라미터 바인딩

Collection타입으로 in절 지원

@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);

반환 타입

스프링 데이터 JPA는 유연한 반환 타입 지원
List<Member>, Member, Option<Member>

컬렉션일때는 조회 결과가 몇개든 상관없지만,
단건 조회일때 조회 결과가 많거나 없다면?
1. 결과 없음: null 반환

JPA에서는 NoResultException 예외가 발생하는데 스프링 데이터 JPA에서 try/catch문으로 잡아주고 null을 반환한다.
2. 결과가 2건 이상: NonUniqueResultException 예외 발생

--

페이징과 정렬

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 쿼리 없이 결과만 반환

예제

Page<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용
Slice<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 안함
List<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 안함
List<Member> findByUsername(String name, Sort sort);

Page 사용 예제 실행 코드

PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
Page<Member> page = memberRepository.findByUsernaem(10, pageRequest);

List<Member> content = page.getcount() // 조회된 데이터
content.size() // 조회된 데이터의 수
page.getTotalElements() // 전체 데이터 수
page.getNumber() // 페이지 번호
page.getTotalPages // 전체 페이지 번호
page.isFirst() // 첫번째 항목인가?
page.hasNext() // 다음 페이지가 있는가?

페이지는 0부터 시작

여기서 정렬 기준이 많아지면 Sort 객체로 다루기 어려울수 있다.
이때는 그냥 order by JPQL을 쓰자

count 쿼리를 @Query 어노테이션의 countQuery 인자로 분리할 수 있다. 이렇게 되면 기존의 복잡한 쿼리 그대로 카운팅 하지않고 따로 설정해둔 간단한 쿼리로 카운팅할 수 있다.

카운트는 left join은 안해도 그대로 나온다.

Page 클래스를 사용하면 자동으로 카운팅 쿼리를 추가로 보낸다.
이것때문에 성능이 떨어진다면 Slice 클래스를 사용하면 된다.

Slice 클래스를 사용하면 limit + 1 개를 조회한다. -> 다음 페이지 여부 확인


@EntityGraph

페치조인을 실행해주는 어노테이션
select m from Member m JPQL을 실행하는 메서드에 @EntityGraph(attributePaths = {"team"})을 붙히면
select m from Member m join fetch m.team JPQL 처럼 자동으로 페치조인을 진행한다.

left outer join을 사용한다.
(기존 페치조인은 inner join 사용함)


확장 기능

스프링 데이터 JPA 레포지토리는 인터페이스, 구현체는 스프링이 자동 생성
이 인터페이스를 직접 구현하려면 너무 귀찮다
그런데 메서드를 직접 구현하여 기존 인터페이스에 추가하고 싶다면?

새로운 인터페이스와 구현 객체를 생성해주고, 해당 인터페이스를 JpaRepository와 같이 상속해주면 된다.

대신 구현 객체의 이름을
(리포지토리 인터페이스 이름) + Impl or (사용자 정의 인터페이스 이름) + Impl
을 해줘야 한다


Auditing

엔티티를 생성,변경한 사람과 시간을 추적하고 싶다면?

  1. 순수 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();
  }
}

@Column(updatable = false) 업테이트 못하게 막아준다
@PrePersist 처음 생성할때 자동으로 실행되게 지정해주는 어노테이션
@PreUpdate 업데이트마다 실행되게 지정해주는 어노테이션
이 외에도 @PostPersist, @PostUpdate가 있다.

  1. 스프링 데이터 JPA 사용

설정
@EnableJpaAuditing 스프링 부트 설정 클래스에 적용해야함
@EntityListeners(AuditingEntityListener.class) 엔티티에 적용

사용 어노테이션
@CreatedDate, @LastModifiedDate, @CreatedBy, @LastModifiedBy

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


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

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

파라미터로 Pageable 객체를 받을 수 있다.
요청파라미터 -
/members?page=0&size=3&sort=id,desc&sort=username,desc

페이지는 0부터 시작한다.


새로운 엔티티를 구별하는 방법
save() 메서드
새로운 엔티티면 저장( persist )
새로운 엔티티가 아니면 병합( merge )

@GenerateValue 어노테이션을 사용하는 엔티티는 딱히 상관없다. 그런데 @GenerateValue 어노테이션을 사용하지 않는 엔티티는 새로운 엔티티인지 구별해주는 메서드가 따로 필요하다.

새로운 엔티티를 판단하는 기본 전략
식별자가 객체일 때 null 로 판단
식별자가 자바 기본 타입일 때 0 으로 판단
Persistable 인터페이스를 구현해서 판단 로직 변경 가능

@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item implements Persistable<String> {
  @Id
  private String id;
  @CreatedDate
  private LocalDateTime createdDate;
  public Item(String id) {
  	this.id = id;
  }
  @Override
  public String getId() {
  	return id;
  }
  @Override
  public boolean isNew() {
      return createdDate == null;
  }
}
profile
공부하다가 생긴 궁금한 것들을 정리하는 공간

0개의 댓글