JpaRepository

박영준·2023년 2월 9일
0

JPA

목록 보기
4/8

1. 정의

  • 메소드를 호출하여 데이터를 검색 할 수 있도록
    미리 검색 메소드를 정의해두는 인터페이스

  • Entity 에 있는 데이터를 조회/저장/변경/삭제(CRUD) 하기 위해,
    Spring JPA 에서 Repository 를 정의하므로 해당 Entity 에 있는 데이터를 사용할 수 있다.
    → 즉, CRUD 처리를 위한 공통 인터페이스를 제공

    Repository
    내부적으로 EntityManager 가 직접 대상 Entity의 데이터를 관리하므로,
    굳이 Repository 인터페이스를 정의하지 않고도
    직접 EntityManger 를 사용해 Persistance Layer 를 구현 할 수 있다.

    그러나,
    Spring JPA 에서 Repository 의 내부 구현체를 자동 생성하므로,
    별도의 구현체를 따로 생성하지 않아도 된다.

  • org.springframework.data.jpa.repository 패키지의 JpaRepository 인터페이스를 상속하여 만들어진다.

  • exteds JpaRepository <엔티티 클래스이름 , ID 필드 타입> 을 지정한다.

2. 주의점

  1. 기본형의 경우, Wrapper 클래스를 지정한다.

  2. 클래스의 선언 앞에 @Repository 을 붙혀놓아야지 jpa 임을 나타낸다.

3. 사용법

스프링 공식 문서를 참고하면, 더 많은 쿼리와 키워드를 찾을 수 있다.
참고: Spring Data JPA - Reference Documentation - 5.3. Query methods

1) 흐름

(1) 의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

(2) Entity (도메인 객체) 생성

@Getter @NoArgsConstructor @Entity
public class Comment extends BaseTimeEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String comment;

    @Column(nullable = false)
    private Long postId;

    public Comment(CommentDto commentDto){
        this.comment = commentDto.getComment();
        this.postId = commentDto.getPost_id();
    }

    public void update(CommentDto commentDto){
        this.comment = commentDto.getComment();
    }
}

(3) Repository 생성

public interface CommentRepository extends JpaRepository<Comment, Long> {
    List<Comment> findAllByPostId(Long post_id);
}

(4) Repository 에서 정의한 메소드 사용

@GetMapping("/api/comment/{id}")
public List<Comment> listComment(@PathVariable Long id){
    return commentRepository.findAllByPostId(id);
}

Repository 에서 정의한 메소드 : findAllByPostId()

2) 메소드 규칙

(1) 메소드

save()

  • 새로운 엔티티는 저장하고, 이미 있는 엔티티는 수정
  • insert, update
  • 식별자 값이 없으면 em.persist(), 있으면 em.merge() 호출

delete()

  • 레코드 삭제(엔티티 하나를 삭제)
  • 내부에서 em.remove() 호출

findOne()

  • primary key로 레코드 한 건 찾기(엔티티 하나를 조회)
  • 내부에서 em.find() 호출

findAll()

  • 전체 레코드 불러오기(모든 엔티티를 조회)
  • 정렬(sort), 페이징(pageable) 조건을 파라미터로 제공
    (# 3. 사용법 中 ## 7) pageable 참고)

count()

  • 레코드 개수

getOne()

  • 엔티티를 프록시로 조회
  • 내부에서 em.getReference() 호출

특히, save( ), delete( ) 는 DB 에 직접적인 관여를 하기 떄문에 중요하다.

(2) 메소드 이름 키워드

메소드 이름을 붙이는 것만으로도, 메소드가 자동 생성된다.

findBy / countBy

findBy + 엔티티의 속성 이름 : 쿼리를 요청하는 메서드 임을 알림
countBy + 엔티티의 속성 이름 : 쿼리 결과 레코드 수를 요청하는 메서드 임을 알림

findById
findByName
findByAddress
findByMail
  • 첫 글자는 대문자로

Like / NotLike

findByNameLike(String name) : name에서 인수의 텍스트를 포함하는 엔티티를 검색 (퍼지 검색)
findByNameNotLike(String name) : name에서 인수의 텍스트를 포함하지 않는 엔티티를 검색 (퍼지 검색)
  • "퍼지 검색"에 관한 것

    퍼지검색

    • 입력된 검색 키워드가 정확하지 않아도, 사용자의 요구를 예상하고 적절한 단어를 찾는 검색 방식
    • 표기의 흔들림, 유의어/동의어를 보완
    • 맞춤법 교정은 사용자의 입력 실수를 지적하고 보다 정확도 높은 검색을 제공

StartingWith / EndingWith

findByNameStartingWith("A") : ame의 값이 "A"로 시작하는 항목을 검색
  • 텍스트 값에서 인수에 지정된 텍스트로 시작/끝나는 것을 검색

IsNull / IsNotNull

findByNameIsNull() : name의 값이 null인 것만 검색
findByNameIsNotNull()
  • 값이 null 이거나, 혹은 null이 아닌 것을 검색
  • 인수는 필요 X

True / False

findByCheckTrue() : check라는 항목이 true인 것만을 검색
  • 값으로 true 인 것, 혹은 false 인 것을 검색
  • 인수는 필요 X

Before / After

findByCreateBefore(new Date()) : create라는 항목의 값이 현재보다 이전의 것만을 찾는다. (reate가 Date인 경우)
  • 시간 값으로 사용
  • 인수에 지정한 값보다 이전의 것, 혹은 이후 것은 검색

LessThan / GreaterThan / GreaterThanEqual

findByAgeLessThan(int age) : 그 항목의 값이 인수보다 작은 것을 검색
findByAgeGreaterThan(int age) : 그 항목의 값이 인수보다 큰 것을 검색
findByAgeGraterThanEqual(int age) : 그 항목의 값이 인수보다 크거나 같은 것을 검색

findByAgeLessThan(20) : age의 값이 20보다 작은 것을 찾는다.
  • 숫자 값으로 사용
  • 그 항목의 값이 인수보다 작거나 큰 것을 검색

And

findByEmailAndUserId(String email, String userId)
  • 여러 필드를 and로 검색

Or

findByEmailOrUserId(String email, String userId)
  • 여러 필드를 or로 검색

Between

findByCreatedAtBetween(Date fromDate, Date toDate)

findByAgeBetween(10, 20) : age의 값이 10이상 20이하인 것을 검색
  • 수치, 시간의 항목 등.. 에도 사용 가능
  • 두 값을 인수로 가지고, 그 두 값 사이의 것을 검색
  • 필드의 두 값 사이에 있는 항목 검색

In

findByJob(String … jobs)
  • 여러 값 중에 하나인 항목을 검색

OrderBy

findByEmailOrderByNameAsc(String email)
  • 검색 결과를 정렬하여 전달

3) @Query

(1) 정의

jpa 에 정의된 키워드를 조합하면, 특정 조건에 해당하는 데이터를 원하는 형태대로 가져올 수 있다.

그러나,

  1. DB 에 종속적인 문법을 사용해야 할 때
  2. Entity 간의 명시적으로 드러나지 않는 관계간의 조인
  3. 데이터 조회 속도 향상 등...

이런 경우에는 직접 쿼리를 작성할 수 있는 방법을 제공한다.

(2) 사용법

public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.emailAddress = ?1")
  User findByEmailAddress(String emailAddress);
}

@Query 中 nativeQuery 속성을 true로 설정하지 않으면, Default값으로 JPQL 문법이 동작한다.

JPQL 문법

  • JPA에서 사용 되는 언어
  • 쿼리 구문과 유사하나, Table 이 아닌 Entity 를 기준으로 데이터를 조회
  • nativeQuery 속성을 통해, 직접 쿼리를 작성할 수도 있다.

4) @NamedQuery

  1. 쿼리 정의
@Entity
@NamedQuery(
    name = "Member.findByUsername",
    query = "select m from Member m where m.username = :username")
public class Member {
    ...
}
  1. 쿼리 호출
    1) 방법 : JPA를 직접 사용해서 Named 쿼리 호출

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

    2) 방법 : 스프링 데이터 JPA로 Named 쿼리 호출

    // Member : 도메인 클래스
    // findByUsername : 메소드 이름
    public interface MemberRepository extends JpaRepository<Member, Long> {
        List<Member> findByUsername(@Param("username") String username);
    }
  • 선언한 "도메인 클래스 + . + 메소드 이름"
    Member.findByUsername Named Query
    (만약, 실행한 Named Query가 없다면, 메소드 이름으로 쿼리 생성)

5) 공통 인터페이스

public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
    // ...
}
 
// 제네릭에 회원 엔티티와 식별자 타입을 지정 
public interface MemberRepository extends JpaRepository<Member, Long> {
    Member findByUsername(String username);
    // select m from Member m where username = :username
}

(공통 인터페이스의 구성)

6) 반환 타입

(1) 결과가 1건인 경우

Member findByEmail(String email);
  • 조회 결과가 없다면, null 을 반환
  • 만약 결과가 2건 이상 조회되면, NonUniqueReulstException 예외 발생
    → .getSingleResult() 메소드를 호출하기 때문

(2) 결과가 2건 이상인 경우

List<Member> findByName(String name);
  • 조회 결과가 없다면, 빈 컬렉션을 반환

7) pageable

스프링 데이터 JPA는 쿼리 메소드에 페이징과 정렬 기능 제공

  • org.springframework.data.domain.Sort : 정렬 기능
  • org.springframework.data.domain.Pageable : 페이징 기능 (내부에 Sort 포함)

파라미터에 Pageable 사용하면,
반환 타입으로 List 나 org.springframework.data.domain.Pageable 사용 가능

// count 쿼리 사용
	// Page 반환 타입을 사용하면, 스프링 데이터 JPA 는 페이징 기능을 제공하기 위해 검색된 전체 데이터 건수를 조회하는 count 쿼리를 추가로 호출
Page<Member> findByName(String name, Pageable Pageable);
 
// count 쿼리 사용 X
List<Mamber> findByName(String name, Pageable Pageable);
 
List<Mamber> findByName(String name, Sort sort);

@RestController
@RequestMapping("/member")
public class MemberController {
>
    @Autowired
    MemberService memberService;
>
    @RequestMapping("")
    // Query 메소드의 입력변수로 Pageable 변수를 추가하면, Page 타입을 반환형으로 사용 가능
    	// Pageable 객체를 통해 페이징과 정렬을 위한 파라미터를 전달
    Page<Member> getMembers(Pageable pageable){
        return memberService.getList(pageable)
    }
}

④ pageable 에서는 이러한 파라미터를 자동 수집

  • page : 몇 번째 페이지 인지를 전달
  • size : 한 페이지에 몇 개의 항목을 보여줄 것인지 전달
  • sort
    • 정렬 정보를 전달. (정렬정보 : 필드이름, 정렬방향의 포맷으로 전달)
    • 여러 필드로 순차적으로 정렬도 가능하다.
    • 예시 : sort=createdAt,desc&sort=userId,asc

⑤ Controller를 통해 HTTP요청으로 페이징과 정렬된 데이터를 전달받는 URI 샘플

GET /users?page=1&size=10&sort=createdAt,desc&sort=userId,asc

정렬과 페이지 정보를 접속 URI 에서부터 Repository 까지 바로 전달이 가능

⑥ Page interface에서 제공하는 메소드

public interface Page<T> extends Iterable<T> {
 
    int getNumber();            // 현재 페이지
    int getSize();              // 페이지 크기
    int getTotalPages();        // 전체 페이지 수
    int getNumberOfElements();  // 현재 페이지에 나올 데이터 수
    long getTotalElements();    // 전체 데이터 수
    boolean hasPreviousPage();  // 이전 페이지 여부
    boolean isFirstPage();      // 현재 페이지가 첫 페이지 인지 여부
    boolean hasNextPage();      // 다음 페이지 여부
    boolean isLastPage();       // 현재 페이지가 마지막 페이지 인지 여부
    Pageable nextPageable();    // 다음 페이지 객체, 없으면 null
    Pageable previousPageable();// 이전 페이지 객체, 없으면 null
    List<T> getContent();       // 조회된 데이터
    boolean hasContent();       // 조회된 데이터 존재 여부
    Sort getSort();             // 정렬 정보
}

⑦ 페이징과 정렬을 사용하는 예제

  1. 조건 설계
    • 검색 조건 : 이름이 김으로 시작하는 회원
    • 정렬 조건 : 이름으로 내림차순
    • 페이징 조건 : 첫 번째 페이지, 페이지당 보여줄 데이터는 10건
  2. Page 정의
public interface MemberRepository extends Repository<Member, Long> {
    Page<Member> findByNameStartingWith(String name, Pageable Pageable);
}
  1. Page 실행
// 페이징 조건과 정렬 조건 설정
PageRequest pageRequest = 
    new PageRequest(0, 10, new Sort(Direction.DESC, "name"));
 
Page<Member> result = 
    memberRepository.findByNameStartingWith("김", pageRequest);
 
List<Member> members = result.getContent();     // 조회된 데이터
int totalPages = result.getTotalPages();        // 전체 페이지 수
boolean hasNextPage = result.hasNextPage();     // 다음 페이지 존재 여부

참고: JPA Repository를 사용하는 이유
참고: JpaRepository Query 작성
참고: [Spring] Spring Data JPA 기본 사용법 ( JpaRepository )

참고: JPA 사용법 (JpaRepository)
참고: [Spring + JPA] Spring Data JPA 란? (1)
참고: 공통 인터페이스 기능

profile
개발자로 거듭나기!

0개의 댓글