※ 예제 도메인 모델
ㅁ 예제 도메인 모델과 동작확인
- 엔티티 기본생성자 만들기를 lombok으로
> @NoArgsConstructor(access = AccessLevel.PROTECTED)
- ToString 만들기
> @ToString(of={"id","username","age"})
※ 공통 인터페이스 기능
ㅁ 순수 JPA 기반 리포지토리 만들기
- Optional return하기 위해 아래 ofNullable 사용
> return Optional.ofNullable(team);
- count 반환 ( getSingleResult 사용 )
> public long count(){
return em.createQuery("select count(t) from Team t", Long.class).getSingleResult();
}
ㅁ 공통 인터페이스 설정 & 공통 인터페이스 적용
- 원래는 @EnableJpaRepositories(basePackages = "study.datajpa.repository") 가 application에 설정되어있어야,
해당 패키지 아래를 JPA repository로 등록해준다.
> 부트를 쓸경우 없어도 됨 (자동으로 main application 아래 모든 패키지 scan하여 설정됨)
- 구현체가 없는데 interface가 JPArepository 기능을 할까?
> 스프링이 JPA repository 를 상속받은 인터페이스를 보고 구현 클래스를 만들어서 넣어줌!!
> 프록시 객체로 만들어 짐 (JDK dynamic 프록시 사용하는듯 - sun 어쩌구로 찍힘)
ㅁ 공통 인터페이스 분석
- PagingAndSortingRepository 는 org.springframework.data.repository에 있음
> spring-data-commons 에 있음 (공통적으로 사용)
- JpaRepository는 org.springframework.data.jpa 에 있음
> spring-data-jpa 에 있음
- getOne(ID) : 엔티티를 프록시로 조회 => EntityMangager.getReference() 호출
- findAll은 Sort와 Paging 조건을 제공할 수 있음
- JpaRepository는 왠만한 모든 기능을 전부 제공한다. (더 생각하기 힘들정도..?)
※ 쿼리 메소드 기능
ㅁ 메소드 이름으로 쿼리 생성
- 메소드 이름으로 쿼리 만들수 있는데, 두가지 조건 이상이면 이름이 너무 길어져서 다른방법으로 풀어냄
> find 후 by 가 없으면 전체 조회
- findHelloBy 처럼 식별하기 위한 내용(설명) 이 들어가도 됨
ㅁ JPA named Query
- 엔티티 위에 설정 후, repository 에서 em.createNamedQuery로 호출
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username")
- JPA respository 에서는
@Query(name = "Member.findByUsername")
List<Member> findByUsername(@Param("username") String username);
로 호출
@PARAM 잘 기억해야함
- 순서 : 설정된 엔티티의 네임드쿼리 찾음 -> 없으면 메소드이름으로 쿼리생성 진행
- 쿼리가 엔티티에 있는것이 조금 이상하고, repository에서 query 설정할 수 있기때문에, namedquery는 잘 안씀
- 네임드 쿼리의 가장 큰 장점은 애플리케이션 로딩 시점에 파싱해서 문법오류를 알려줌
ㅁ @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);
- @Query 에 쓰는 이름이 없는 namedQuery 로 생각하면 됨
> 그래서 namedQuery와 마찬가지로 오타나 오류가 있을 경우 미리 파싱해서 알려줌
ㅁ @Query, 값, DTO 조회하기
- 특정 칼럼만 가져오기
@Query("select m.username from Member m")
List<String> findUsernameList();
- DTO는 @DATA 써도 되는데 엔티티는 Setter를 두지 않는걸 권장하기 때문에 @DATA 쓰면 안됨
- new operation으로 DTO로 바로 조회 가능
@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();
ㅁ 파라미터 바인딩
- 위치 기반과 이름 기반이 있는데, 위치기반은 위치가 바뀔경우 에러가 발생할 수 있어서 이름기반으로 해야함
- in 절에 사용할 수도 있음 - Collection<타입> 으로 변수 설정하는게 좋음
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") Collection<String> names);
ㅁ 반환 타입
- 반환타입이 collection , List 일때 값이 없을 경우, null 이 아닌 empty collection 이 반환됨
> size로 체크하면 됨
- 단건 조회일때는 없으면 null 이 돰
- jpa는 조회해서 값이 없을 경우, nullexception이 터지는데, spring data jpa는 try catch가 감싸고 있어서, null 이 넘어오게됨
> 자바 8 이후에는 optional로 조회하게 되어, 개발자가 책임지게 됨
- 단건 조회에 2건이상 값이 뜨면, exception 이 발생하게 됨 (non unique result exception이 발생 -> 스프링 데이터 jpa가 스프링 프레임워크 exception으로 변환함
> jpa 예외에 의존하는게 아니라 스프링 프레임워크에 의존하기 위함
ㅁ 순수 JPA 페이징과 정렬
- 현재 페이지 구하는게 사실 제일 힘들지만( 페이지 관련 연산.. 첫페이지냐(offset), 마지막 페이지냐 등 ), 스프링 데이터는 제공해준다
- JPA 에서는 setFirstResult와 setMaxResult로 설정함
- totalCount 쿼리도 필요
ㅁ 스프링 데이터 JPA 페이징과 정렬
- pageRequest를 만들어서 정렬방법 및 갯수 설정 (PageRequest는 Pageable 인터페이스 상속받음)
> 이것을 Pageable 인터페이스 파라미터로 받아서 Page<Entity>를 return으로 받음
> page.getContent(); 각 값 내용들
> page.getTotalElements(); totalCount
> page.getNumber(): 페이지 번호
> pagegetTotalPages()) 페이지 전체 갯수
> page.isFirst() : 첫째 페이지
> page.hasNext() : 다음페이지가 있냐
- Page 인터페이스는 Slice 인터페이스 상속
> Slice가 Page의 위에 있음
> Slice는 다음 객체가 있을때, 더보기 를 만든다던지... 요청한 갯수보다 하나 더 가지고 옴
> totalcount 쿼리를 날리지않음
- totalCount 쿼리가 데이터 전체를 세기 때문에, 소요시간이 많이 걸릴 수 있다.
> 페이징은 시간 단축을 위한 건데, 오히려 더 부하가 걸림 (카운트를 세기 위해 left inner join을 또하는 등..)
> @Quert(countQuery = "" ) 를 작성하여 해결
ex)
@Query(value = "select m from Member m left join m.team t", countQuery = "select count(m) from Member m")
Page<Member> findByAge(int age, Pageable pageable);
- Paging과 pagable 등의 등장으로 개발자는 totalcount 나 페이지연산로직 관련없이 실제 핵심로직만 집중하면 됨
> sorting 등 복잡할 경우 @Query에 sort by 하면 됨
- Page<Entity> 를 api에서 그대로 반환하면 안되고 DTO로 변환해야함
> Page<MemberDto> toMap = page.map(m -> new MemberDto(m.getId(), m.getUsername(), null));
로 map을 통해 바로 변환 가능
ㅁ 벌크성 수정 쿼리
- command option n : inline 단축하기
- jpa 쿼리로 update 할때는 , .executeUpdate(); 로 실행 (반환 타입이 실행된 행 갯수)
- Spring Data JPA는 @Query로 동일하게 update 쿼리를 만들고 @Modifying 을 꼭 넣어야함
> 빼면 에러 발생 (Not Supprot DML ~~~)
- 벌크 연산은 JPA의 영속성 컨텍스트의 1차캐시 객체에 영향을 주지않음
> 그렇지 않으면 영속성 컨텍스트의 값과 db값이 다름
> 벌크 연산 후 영속성 컨텍스트를 날려줘야함 (em.flush(), em.clear() 필요)
> 그렇지 않으면 @Modifying에 옵션 추가 => @Modifying(clearAutomatically = true)
ㅁ @EntityGraph
- fetch join을 명시하지 않고, 내부적으로 fetch join을 사용하여 프록시 객체를 만들지 않고 가져오게 할 수 있음
- 사용 예 ) 1. overrride 하기 2.jpql에 fetch join 명시하지않고 사용
@Override
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();
- @NamedEntityGraph(name = "Member.all",attributeNodes = @NamedAttributeNode("team"))
를 엔티티 위에 넣어서 named query 처럼
@EntityGraph("Member.all")
List<Member> findEntityGraphByUsername(@Param("username") String username);
로 호출할 수 있음
- 실무에서는 간단한거는 @EntityGraph 로 해결하고, 어려운거는 jpql의 fetch join 작성함
> 어려운거는 보통 무조건 쿼리를 작성해야할 수준이기 때문에..
ㅁ JPA Hint & Lock
- 단순 조회만을 위한거는 read only만 해야 성능 최적화에 도움이 됨
> readonly 설정은 JPA는 제공해주지않고 , Hibernate에 구현되어 있음
@QueryHints(value = @QueryHint(name="org.hibernate.readOnly", value="true"))
Member findReadOnlyByUsername(String username);
- 그러나 실제 성능 최적화에는 효과가 없을 것... 쿼리 자체가 문제인게 많음
- @Lock을 통해 특정 리포지토리 쿼리 진행시 lock을 걸 수 있음
> @Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findLockByUsername(String username);
> select for update 구문 생성 확인
> SELECT ~ FOR UPDATE란
SELECT ~ FOR UPDATE 구문은 "데이터 수정하려고 SELECT 하는 중이야~ 다른 사람들은 데이터에 손 대지 마!" 라고 할 수 있습니다. 좀 더 딱딱한 표현으로는 동시성 제어를 위하여 특정 데이터(ROW)에 대해 베타적 LOCK을 거는 기능입니다.
'Spring' 카테고리의 다른 글
[Spring] 실전! Querydsl - 1 (0) | 2021.09.09 |
---|---|
[Spring] 실전! 스프링 데이터 JPA - 3 (0) | 2021.09.06 |
[Spring] 실전! 스프링 데이터 JPA - 1 (0) | 2021.09.02 |
[Spring] 스프링 기반 REST API 개발 - 3 (0) | 2021.08.06 |
[Spring] 스프링 기반 REST API 개발 - 2 (0) | 2021.08.03 |