스프링

 

중급자를 위해 준비한
[웹 개발, 백엔드] 강의입니다.

스프링 데이터 JPA는 기존의 한계를 넘어 마치 마법처럼 리포지토리에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 모두 제공합니다. 스프링 데이터 JPA 실무 노하우를 전해드립니다.

✍️
이런 걸
배워요!

스프링 데이터 JPA를 기초부터 실무 활용까지 한번에 배울 수 있습니다.

실무에서 실제 사용하는 기능 위주로 학습합니다.

단순한 기능 설명을 넘어 실무 활용 노하우를 배울 수 있습니다.

JPA와 스프링 데이터 JPA의 차이를 명확하게 이해할 수 있습니다.

 

강좌 : https://www.inflearn.com/course/스프링-데이터-JPA-실전#

 

 

 

수업자료 : 

https://github.com/braverokmc79/jpa-basic-lecture-file2

 

 

 

소스 : https://github.com/braverokmc79/data-jpa

 

 

 

 

 

 

 

 

 

 

 

 

 

[4] 쿼리 메소드 기능

 

 

 

 

 

 

10. 메소드 이름으로 쿼리 생성

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28009&tab=curriculum

 

 

쿼리 메소드 기능

 

1) 메소드 이름으로 쿼리 생성


2) NamedQuery


3) @Query - 리파지토리 메소드에 쿼리 정의


4) 파라미터 바인딩


5) 반환 타입


6) 페이징과 정렬


7) 벌크성 수정 쿼리


8) @EntityGraph

 

 

스프링 데이터 JPA가 제공하는 마법 같은 기능

 

 

 

쿼리 메소드 기능 3가지


메소드 이름으로 쿼리 생성


메소드 이름으로 JPA NamedQuery 호출


@Query 어노테이션을 사용해서 리파지토리 인터페이스에 쿼리 직접 정의

 

 

 

 

 

 

메소드 이름으로 쿼리 생성


메소드 이름을 분석해서 JPQL 쿼리 실행
이름과 나이를 기준으로 회원을 조회하려면?

 

1) 순수 JPA 리포지토리

 

public List <Member> findByUsernameAndAgeGreaterThan(String username, int age) {
        return em.createQuery("select m from Member m where m.username = :username
            and m.age >: age ")
            .setParameter("username", username)
            .setParameter("age", age)
            .getResultList();
        }

 

 

 

순수 JPA 테스트 코드

 

@Test
public void findByUsernameAndAgeGreaterThan() {
    Member m1 = new Member("AAA", 10);
    Member m2 = new Member("AAA", 20);
    memberJpaRepository.save(m1);
    memberJpaRepository.save(m2);
    List < Member > result =
        memberJpaRepository.findByUsernameAndAgeGreaterThan("AAA", 15);
    assertThat(result.get(0).getUsername()).isEqualTo("AAA");
    assertThat(result.get(0).getAge()).isEqualTo(20);
    assertThat(result.size()).isEqualTo(1);
}

 

 

 

2 ) 스프링 데이터 JPA

public interface MemberRepository extends JpaRepository < Member, Long > {
    List <Member> findByUsernameAndAgeGreaterThan(String username, int age);
}

 

 

스프링 데이터 JPA는 메소드 이름을 분석해서 JPQL을 생성하고 실행

 

 

1)    https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation

 

2)  https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation

 

3)  https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.limit-query-result

 

 

 

 

 

 

 

 

 

 

 

 

 

11. JPA NamedQuery    (실무에서 사용하지 않는다.)

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28010&tab=curriculum

 

JPA NamedQuery


JPA의 NamedQuery를 호출할 수 있음

 

 

* @NamedQuery 어노테이션으로 Named 쿼리 정의*

 

@Entity
@NamedQuery(
    name = "Member.findByUsername",
    query = "select m from Member m where m.username = :username")
public class Member {
    ...
}

 

 

JPA를 직접 사용해서 Named 쿼리 호출

 

public class MemberRepository {
    public List <Member> findByUsername(String username) {
        ...
        List <Member> resultList =
        em.createNamedQuery("Member.findByUsername", Member.class)
        .setParameter("username", username)
        .getResultList();
    }
}

 

 

 

스프링 데이터 JPA로 NamedQuery 사용

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

 

 

@Query 를 생략하고 메서드 이름만으로 Named 쿼리를 호출할 수 있다.

 

스프링 데이터 JPA로 Named 쿼리 호출

 

public interface MemberRepository
extends JpaRepository < Member, Long > { //** 여기 선언한 Member 도메인 클래스
    List <Member> findByUsername(@Param("username") String username);
}

 

 

스프링 데이터 JPA는 선언한 "도메인 클래스 + .(점) + 메서드 이름"으로 Named 쿼리를 찾아서 실행
만약 실행할 Named 쿼리가 없으면 메서드 이름으로 쿼리 생성 전략을 사용한다.


필요하면 전략을 변경할 수 있지만 권장하지 않는다.


참고: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-lookup-strategies

 

 

참고: 스프링 데이터 JPA를 사용하면 실무에서 Named Query를 직접 등록해서 사용하는 일은 드물다.
대신 @Query 를 사용해서 리파지토리 메소드에 쿼리를 직접 정의한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

12. @Query, 리포지토리 메소드에 쿼리 정의하기 (실무 주로 사용)

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28012&tab=curriculum

 

 

메서드에 JPQL 쿼리 작성

 

 

public interface MemberRepository extends JpaRepository < Member, Long > {
    @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);
}

 

 

 

@org.springframework.data.jpa.repository.Query 어노테이션을 사용
실행할 메서드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라 할 수 있음
JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있음(매우 큰 장점!)

 


> 참고: 실무에서는 메소드 이름으로 쿼리 생성 기능은 파라미터가 증가하면 메서드 이름이 매우 지저분해진다. 따라서 @Query 기능을 자주 사용하게 된다

 

테스트

    @Test
    public void testQuery(){
        Member m1=new Member("AAA", 10);
        Member m2 =new Member("BBB", 20);
        memberRepository.save(m1);
        memberRepository.save(m2);

        List<Member> result =memberRepository.findUser("AAA", 10);
        Assertions.assertThat(result.get(0)).isEqualTo(m1);
    }

 

 

 

 

 

 

 

 

 

 

13. @Query, 값, DTO 조회하기

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28013&tab=curriculum

 

단순히 값 하나를 조회

@Query("select m.username from Member m")
List<String> findUsernameList();

 

 

JPA 값 타입( @Embedded )도 이 방식으로 조회할 수 있다.

  @Query("select new study.datajpa.repository.MemberDto(m.id, m.username, t.name ) " +
            " from Member m join m.team t")
    List<MemberDto> findMemberDto();

 

주의! DTO로 직접 조회 하려면 JPA의 new 명령어를 사용해야 한다. 그리고 다음과 같이 생성자가 맞는 DTO가 필요하다. (JPA와 사용방식이 동일하다.)

 

 

package study.datajpa.repository;

import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class MemberDto {
    private Long id;
    private String username;
    private String teamName;
    public MemberDto(Long id, String username, String teamName) {
        this.id = id;
        this.username = username;
        this.teamName = teamName;
    }
}

 

 

테스트

 

    @Test
    public void findUsernameList(){
        Member m1=new Member("AAA",10);
        Member m2=new Member("BBB",20);
        memberRepository.save(m1);
        memberRepository.save(m2);

        List<String> usernameList=memberRepository.findUsernameList();
        for(String s :usernameList){
            System.out.println("s = " + s);
        }
    }


    @Test
    public void findMemberDtoTest(){
        Team team =new Team("teamA");
        teamRepository.save(team);


        Member m1=new Member("AAA",10);
        Member m2=new Member("BBB",20);
        m1.setTeam(team);
        m2.setTeam(team);
        memberRepository.save(m1);
        memberRepository.save(m2);

        List<MemberDto> memberDtos=memberRepository.findMemberDto();
        for(MemberDto dto : memberDtos){
            System.out.println("회원 출력 = " + dto.toString());
        }
    }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

14. 파라미터 바인딩

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28014&tab=curriculum

 

 

파라미터 바인딩
위치 기반
이름 기반

 

select m from Member m where m.username = ?0 //위치 기반
select m from Member m where m.username = :name //이름 기반

 

 

파라미터 바인딩

import org.springframework.data.repository.query.Param
public interface MemberRepository extends JpaRepository < Member, Long > {
    @Query("select m from Member m where m.username = :name")
    Member findMembers(@Param("name") String username);
}

 

> 참고: 코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용하자 (위치기반은 순서 실수가  바꾸면…)
 

컬렉션 파라미터 바인딩
Collection 타입으로 in절 지원

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

 

 

테스트

    @Test
    public void findByNames() {
        Member m1=new Member("AAA",10);
        Member m2=new Member("BBB",20);
        memberRepository.save(m1);
        memberRepository.save(m2);

        List<Member> result=memberRepository.findByNames(Arrays.asList("AAA", "BBB"));
        for(Member member :result){
            System.out.println("member = " + member);
        }
    }

 

 

 

 

 

 

 

 

 

 

15. 반환타입

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28015&tab=curriculum

 

스프링 데이터 JPA는 유연한 반환 타입 지원

 

    List<Member> findListByUsername(String name); //컬렉션

    Member findMemberByUsername(String name); //단건

    Optional<Member> findOptionalByUsername(String name); //단건 Optional

 

 

List<Member> 반환시 null 값이 반환처리 되지 않는다.  size 0 인 객체 반환 처리

 

 

스프링 데이터 JPA 공식 문서:  https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-return-types

 

 

조회 결과가 많거나 없으면?


컬렉션
결과 없음: 빈 컬렉션 반환


단건 조회
결과 없음: null 반환
결과가 2건 이상: javax.persistence.NonUniqueResultException 예외 발생

 

 

> 참고:  단건으로 지정한 메서드를 호출하면 스프링 데이터 JPA는 내부에서 JPQL의

Query.getSingleResult() 메서드를 호출한다. 이 메서드를 호출했을 때 조회 결과가 없으면

javax.persistence.NoResultException 예외가 발생하는데 개발자 입장에서 다루기가 상당히 불편하다. 

스프링 데이터 JPA는 단건을 조회할 때 이 예외가 발생하면 예외를 무시하고 대신에 null 을  반환한다.

 

 

 

 

 

 

 

 

★16. 순수 JPA 페이징과 정렬

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28016&tab=curriculum

 

 


JPA에서 페이징을 어떻게 할 것인가?


다음 조건으로 페이징과 정렬을 사용하는 예제 코드를 보자.


검색 조건: 나이가 10살
정렬 조건: 이름으로 내림차순
페이징 조건: 첫 번째 페이지, 페이지당 보여줄 데이터는 3건

 

JPA 페이징 리포지토리 코드

public List <Member> findByPage(int age, int offset, int limit) {
        return em.createQuery("select m from Member m where m.age = :age order by
            m.username desc ")
            .setParameter("age", age)
            .setFirstResult(offset)
            .setMaxResults(limit)
            .getResultList();
        }
        public long totalCount(int age) {
            return em.createQuery("select count(m) from Member m where m.age = :age",
                    Long.class)
                .setParameter("age", age)
                .getSingleResult();
        }

 

 

JPA 페이징 테스트 코드

 

@Test
public void paging() throws Exception {
    //given
    memberJpaRepository.save(new Member("member1", 10));
    memberJpaRepository.save(new Member("member2", 10));
    memberJpaRepository.save(new Member("member3", 10));
    memberJpaRepository.save(new Member("member4", 10));
    memberJpaRepository.save(new Member("member5", 10));
    int age = 10;
    int offset = 0;
    int limit = 3;
    //when
    List <Member> members = memberJpaRepository.findByPage(age, offset, limit);
    long totalCount = memberJpaRepository.totalCount(age);
    //페이지 계산 공식 적용...
    // totalPage = totalCount / size ...
    // 마지막 페이지 ...
    // 최초 페이지 ..
    //then
    assertThat(members.size()).isEqualTo(3);
    assertThat(totalCount).isEqualTo(5);
}

 

 

 

 

 

 

 

 

 

 

 

 

★★★17. 스프링 데이터 JPA 페이징과 정렬 ★★★

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28017&tab=curriculum

 

 

페이징과 정렬 파라미터
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> findPageByUsername(String name, Pageable pageable); //count 쿼리 사용

    Slice<Member> findSliceByUsername(String name, Pageable pageable); //count 쿼리 사용 안함


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

    List<Member> findByUsername(String name, Sort sort);

 

 

 

다음 조건으로 페이징과 정렬을 사용하는 예제 코드를 보자.


검색 조건: 나이가 10살


정렬 조건: 이름으로 내림차순


페이징 조건: 첫 번째 페이지, 페이지당 보여줄 데이터는 3건

 

 

 

Page 사용 예제 정의 코드

public interface MemberRepository extends Repository < Member, Long > {
    Page <Member> findByAge(int age, Pageable pageable);
}

 

 

 

Page 사용 예제 실행 코드

    //페이징 조건과 정렬 조건 설정
    @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(); //조회된 데이터
        Assertions.assertThat(content.size()).isEqualTo(3); //조회된 데이터 수
        Assertions.assertThat(page.getTotalPages()).isEqualTo(2); //전체 페이지 번호
        Assertions.assertThat(page.getTotalElements()).isEqualTo(5); //전체 데이터 수
        Assertions.assertThat(page.isFirst()).isTrue(); //첫번째 항목인가?
        Assertions.assertThat(page.hasNext()).isTrue(); //다음 페이지가 있는가?
        Assertions.assertThat(page.getNumber()).isEqualTo(0); //페이지 번호
    }

 

 

두 번째 파라미터로 받은 Pageable 은 인터페이스다. 따라서 실제 사용할 때는 해당 인터페이스를 구현한
org.springframework.data.domain.PageRequest 객체를 사용한다.


PageRequest 생성자의 첫 번째 파라미터에는 현재 페이지를, 두 번째 파라미터에는 조회할 데이터 수를 입력한다.

 

여기에 추가로 정렬 정보도 파라미터로 사용할 수 있다. 참고로 페이지는 0부터 시작한다.


> 주의: Page는 1부터 시작이 아니라 0부터 시작이다.

 

 

 

 

 

Page 인터페이스

 

public interface Page < T > extends Slice < T > {
    int getTotalPages(); //전체 페이지 수
    long getTotalElements(); //전체 데이터 수
    <U> Page <U> map(Function < ? super T, ? extends U > converter); //변환기
}

 

 

Slice 인터페이스

 

public interface Slice < T > extends Streamable < T > {
    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(); //이전 페이지 객체
    <U> Slice <U> map(Function < ? super T, ? extends U > converter); //변환기
}

 

 

 

★★★참고: count 쿼리를 다음과 같이 분리할 수 있음

 

@Query(value = “select m from Member m”,
    countQuery = “select count(m.username) from Member m”)
Page <Member> findMemberAllCountBy(Pageable pageable);

 

 

 

Top, First 사용 참고

 

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.limit-query-result

 

List findTop3By();

 

 

 

페이지를 유지하면서 엔티티를 DTO로 변환하기 

 

Page <Member> page = memberRepository.findByAge(10, pageRequest);
Page <MemberDto> dtoPage = page.map(m -> new MemberDto());

 

실습


Page


Slice (count X) 추가로 limit + 1을 조회한다. 그래서 다음 페이지 여부 확인(최근 모바일 리스트 생각해보면 됨)


List (count X)

 

 

카운트 쿼리 분리(이건 복잡한 sql에서 사용, 데이터는 left join, 카운트는 left join 안해도 됨)

 

 

실무에서 매우 중요!!!


> 참고: 전체 count 쿼리는 매우 무겁다.

 

 

 

 

 

 

 

 

 

 

 

 

18. 벌크성 수정 쿼리

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28018&tab=curriculum

 

 

1) JPA를 사용한 벌크성 수정 쿼리

    public int bulkAgePlus(int age){
        int resultCount=em.createQuery("update Member m set m.age = m.age +1 " +
                " where m.age >= :age")
                .setParameter("age" , age)
                .executeUpdate();
        return resultCount;
    }

 

JPA를 사용한 벌크성 수정 쿼리 테스트

@Test
public void bulkUpdate() throws Exception {
    //given
    memberJpaRepository.save(new Member("member1", 10));
    memberJpaRepository.save(new Member("member2", 19));
    memberJpaRepository.save(new Member("member3", 20));
    memberJpaRepository.save(new Member("member4", 21));
    memberJpaRepository.save(new Member("member5", 40));
    //when
    int resultCount = memberJpaRepository.bulkAgePlus(20);
    //then
    assertThat(resultCount).isEqualTo(3);
}

 

 

 

 

2)스프링 데이터 JPA를 사용한 벌크성 수정 쿼리

 

   @Modifying(clearAutomatically = true)
    @Query("update Member m set m.age =m.age +1 where m.age >=:age ")
    int bulkAgePlus(@Param("age") int age);

 

스프링 데이터 JPA를 사용한 벌크성 수정 쿼리 테스트

   @Test
    public void bulkUpdate() throws Exception{
        //given
        memberRepository.save(new Member("member1", 10));
        memberRepository.save(new Member("member2", 19));
        memberRepository.save(new Member("member3", 20));
        memberRepository.save(new Member("member4", 21));
        memberRepository.save(new Member("member5", 40));

        //when
        int resultCount=memberRepository.bulkAgePlus(20);

        //then
        Assertions.assertThat(resultCount).isEqualTo(3);
    }

 

 

 

벌크성 수정, 삭제 쿼리는 @Modifying 어노테이션을 사용 사용하지 않으면 다음 예외 발생

 

org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operation

 

벌크성 쿼리를 실행하고 나서 영속성 컨텍스트 초기화: @Modifying(clearAutomatically = true)

 

 

 

참고: 벌크 연산은 영속성 컨텍스트를 무시하고 실행하기 때문에, 영속성 컨텍스트에 있는 엔티티의 상태와 DB에 엔티티 상태가 달라질 수 있다.

 

 

 

 

 

** 권장하는 방안

 

1. 영속성 컨텍스트에 엔티티가 없는 상태에서 벌크 연산을 먼저 실행한다.


2. 부득이하게 영속성 컨텍스트에 엔티티가 있으면 벌크 연산 직후 영속성 컨텍스트를 초기화 한다

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

19. @EntityGraph

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28019&tab=curriculum

 

 

#spring.jpa.properties.hibernate.default_batch_fetch_size=100

 

batch_fetch 를 사용하면  지연로딩에서  entityGraph 인 페치 조인을 사용안하면 in 으로 데이터를 한번에 가져온다.

앞 강의 확인 할것.

 

 

 

연관된 엔티티들을 SQL 한번에 조회하는 방법
member team은 지연로딩 관계이다. 따라서 다음과 같이 team의 데이터를 조회할 때 마다 쿼리가
실행된다. (N+1 문제 발생)

 

    @Test
    public void findMemberLazy() throws Exception {
        //given
        //member1 -> teamA
        //member2 -> teamB
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        teamRepository.save(teamA);
        teamRepository.save(teamB);
        memberRepository.save(new Member("member1", 10, teamA));
        memberRepository.save(new Member("member2", 20, teamB));
        em.flush();
        em.clear();
        //when
        List<Member> members = memberRepository.findAll();
        //then
        for (Member member : members) {

            System.out.println("시작============================================================");
            member.getTeam().getName();
            // 다음과 같이 지연 로딩 여부를 확인할 수 있다
            //Hibernate 기능으로 확인
            Hibernate.initialize(member.getTeam());

            //JPA 표준 방법으로 확인
            PersistenceUnitUtil util =  em.getEntityManagerFactory().getPersistenceUnitUtil();
            System.out.println( "isLoaded : " + util.isLoaded(member.getTeam()) );
            System.out.println("끝============================================================");
        }
    }

 

 

참고: 다음과 같이 지연 로딩 여부를 확인할 수 있다.

 

//Hibernate 기능으로 확인
Hibernate.isInitialized(member.getTeam())
//JPA 표준 방법으로 확인
PersistenceUnitUtil util =
    em.getEntityManagerFactory().getPersistenceUnitUtil();
util.isLoaded(member.getTeam());

 

 

연관된 엔티티를 한번에 조회하려면 페치 조인이 필요하다.

 

JPQL 페치 조인

@Query("select m from Member m left join fetch m.team")
List <Member> findMemberFetchJoin();

 

 

스프링 데이터 JPA는 JPA가 제공하는 엔티티 그래프 기능을 편리하게 사용하게 도와준다. 이 기능을
사용하면 JPQL 없이 페치 조인을 사용할 수 있다. (JPQL + 엔티티 그래프도 가능)

    //공통 메시더 오버라이드
    @Override
    @EntityGraph(attributePaths = {"team"})
    List<Member> findAll();

    //JPQL + 엔티티 그래프
    @EntityGraph(attributePaths = {"team"})
    @Query("select m from Member m")
    List<Member> findMemberEntityGraph();

    @EntityGraph(attributePaths = {"team"})
    List<Member> findEntityGraphByUsername(String username);
}

 

 

 

EntityGraph 정리   사실상 페치 조인(FETCH JOIN)의 간편 버전    LEFT OUTER JOIN 사용

 

 

 

 

NamedEntityGraph 사용 방법

@NamedEntityGraph(name = "Member.all", attributeNodes =
    @NamedAttributeNode("team"))
@Entity
public class Member {}
@EntityGraph("Member.all")
@Query("select m from Member m")
List <Member> findMemberEntityGraph();

 

 

 

 

 

 

 

 

 

 

 

 

20. JPA Hint & Lock  ( 참고 : 별로 사용하지 않는다. )

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=스프링-데이터-JPA-실전&unitId=28020&tab=curriculum

 

 

JPA 쿼리 힌트(SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트)

 

쿼리 힌트 사용

   @QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
    Member findReadOnlyByUsername(String username);

 

읽기만 가능하다.   중간에 더티체킹으로 인한  업데이트 처리가 안된다.

 

쿼리 힌트 사용 확인

    @QueryHints(value = { @QueryHint(name = "org.hibernate.readOnly", value = "true")}, forCounting = true)
    Page<Member> findByUsername(String name, Pageable pageable);

 

 

org.springframework.data.jpa.repository.QueryHints 어노테이션을 사용
forCounting : 반환 타입으로 Page 인터페이스를 적용하면 추가로 호출하는 페이징을 위한 count 쿼리도 쿼리 힌트 적용(기본값 true )
 

 

 

 

 

Lock  (금융권에서  주로 사용)

SELECT ~ FOR UPDATE 란

 

SELECT ~ FOR UPDATE 는 선택된 행들에 대해 배타적으로 LOCK을 거는 것이다.

즉 "데이터 수정하려고 SELECT 했어. 건드리지마 !" 와 같은 뜻입니다. 

 

[MySQL] SELECT ~ FOR UPDATE 란? - SELECT ~ FOR UPDATE 란

 

위와 같은 경우 동시제어가 필연적으로 필요하다. 

은행의 예금 및 통장의 잔액 변경 시 일관성을 유지하기위해, 영화 및 공연장의 특정 좌석을 예약하기 위해 사용한다.

 

SELECT FOR UPDATE를 사용하지 않으면 다른 세션에서 업데이트를 하고 있어도 같은 행을 조회가능하며, COMMIT을 하기 전까지는 결과가 반영되지 않는다

 

@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByUsername(String name);

 

org.springframework.data.jpa.repository.Lock 어노테이션을 사용
JPA가 제공하는 락은 JPA 책 16.1 트랜잭션과 락 절을 참고

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

떼꿩에 매를 놓다 , 이것저것 닥치는 대로 마구 욕심을 냄을 이르는 말. / 많은 목표를 걸어 놓고 갈팡질팡함을 이르는 말.

댓글 ( 4)

댓글 남기기

작성