JPA는 엔티티를 통해서 데이터를 관리한다. 엔티티를 기준으로 테이블 자동 생성 또한 가능하다.
package com.example.chap05.domain;
import lombok.Data;
import javax.persistence.*;
import java.util.Date;
@Data
@Entity
public class Board {
@Id @GeneratedValue
private Long seq;
private String title;
private String writer;
private String content;
@Temporal(TemporalType.TIMESTAMP)
private Date createDate;
private Long cnt;
}
엔티티 작성 후 엔티티를 이용하여 CRUD 기능을 처리하는 Repository 인터페이스를 작성한다.
Repository는 기존의 DAO와 동일한 개념으로 비즈니스 클래스에서는 이 Repository를 이용하여 실질적인 데이터베이스 연동을 처리한다.
Repository는 스프링에서 제공하는 리포지토리 중 하나를 상속하여 작성한다.
모든 인터페이스들은 공통적으로 두 개의 제네릭 타입을 지정한다.
CrudRepository<T,ID>
스프링 데이터 JPA를 사용하는 경우 인터페이스에 대한 별도의 구현 클래스를 작성하지 않아도 스프링부트가 내부적으로 인터페이스에 대한 구현 객체를 자동으로 생성해준다.
또한 JPA 단독 사용 시 데이터베이스 연동을 위해 사용했던 EntityManagerFactory와 같은 객체들도 스프링 데이터 JPA가 내부적으로 처리해준다.
CrudRepository 인터페이스가 제공하는 메소드들
메소드 | 기능 |
---|---|
count() | 모든 엔티티의 개수 리턴 |
delete(ID) | 식별키를 통한 삭제 |
delete(Iterable<? Extends T>) | 주어진 모든 엔티티 삭제 |
exists(ID) | 해당 식별 키를 가진 엔티티가 존재하는지 확인 |
findAllById(ID) | 모든 엔티티 목록을 리턴한다. |
findAll(Iterable<ID>) | 해당 식별키를 가진 엔티티 목록을 리턴한다. |
findById(ID) | 해당 식별 키에 해당하는 단일 엔티티 리턴 |
saveAll<Iterable> | 여러 엔티티들을 한번에 등록하고 수정한다. |
save<S entity> | 하나의 엔티티를 등록하고 수정한다. |
일반적으로 JPA를 이용하여 목록 기능을 구현 시 JPQL을 이용한다. JPQL은 검색 대상이 테이블이 아닌 엔티티 인 것 주의한다.
스프링 JPA에서는 복잡한 JPQL을 메소드로 대신 처리할 수 있도록 쿼리메소드라는 기능을 제공한다.
쿼리 메소드란, 메소드의 이름으로 필요한 쿼리를 만들어주는 기능
특정한 네이밍 룰을 따라야지만 생성된다.
검색하려는 엔티티에서 특정 변수의 값만 조회하기 - find
find + 엔티티 이름 + By + 변수 이름
ex ) findBoardByTitle() : Board 엔티티에서 title 값만 조회한다.
만약 현재 사용하는 Repository 인터페이스에 타입을 선언해두었다면 선언된 타입 기준으로 자동으로 엔티티 이름이 적용된다.
BoardRepository의 제너릭 타입이 <Board, Long> 일 경우
ex ) findByTitle : Board 엔티티에서 title 값만 조회한다.
쿼리 메소드의 리턴 타입 : 모두 Collection<T> 타입이다.
@Test
public void testFindByTitle(){
List<Board> boardList = boardRepository.findByTitle("테스트 제목10");
System.out.println("----------------- 검색 결과 -------------------");
for (Board board : boardList) {
System.out.println(board.toString());
}
}
키워드 | 예시 |
---|---|
And | findByTitleAndContent |
Or | findByTitleOrContent |
Between | findByStartDateBetween |
LessThan | findByAgeLessThan |
LessThanEqual | findByAgeLessThanEqual |
After | findByStartDateAfter |
Before | findByStartDateBefore |
IsNull | findByTitleIsNull |
IsNotNull | findByTitleIsNotNull |
Like | findByTitleLike |
StartingWith | findByTitleStartingWith |
Containing | findByTitleContaining |
게시글 내용에 특정 단어가 속한 글 목록 검색하기 → LIKE 연산자, Containing 키워드 사용
public interface BoardRepository extends CrudRepository<Board, Long> {
List<Board> findByTitle(String searchKeyword);
List<Board> findByContentContaining(String searchKeyword);
}
@Test
public void testFindByContent(){
List<Board> boardList = boardRepository.findByContentContaining("14");
System.out.println("----------------- 검색 결과 -------------------");
for (Board board : boardList) {
System.out.println(board.toString());
}
}
여러 칼럼에 제약 조건 달기 → And 또는 Or 키워드 사용
List<Board> findByTitleContainingOrContentContaining(String title, String content);
두 개의 칼럼에 제약조건을 걸어줬기 때문에 매개변수도 두 개를 받아줘야한다.
근데 이름이 짱 긴데 어쩔 수 없슴
@Test
public void testFindByTitleAndContent(){
List<Board> boardList = boardRepository.findByTitleContainingOrContentContaining("14", "12");
System.out.println("----------------- 검색 결과 -------------------");
for (Board board : boardList) {
System.out.println(board.toString());
}
}
작성된 쿼리는 아래와 같다.
WHERE 절에 두개의 조건이 OR 연산으로 결합되었다
데이터를 정렬하여 조회 → OrderBy + 변수 + Asc / Des
List<Board> findByTitleContainingOrderBySeqDesc(String title);
모든 쿼리 메소드는 마지막 파라미터로 페이징 처리를 위한 Pageable 인터페이스와 Sort 인터페이스를 추가할 수 있다
List<Board> findByTitleContaining(String title, Pageable paging);
@Test
public void testPaging(){
Pageable paging = PageRequest.of(0,5);
List<Board> boardList = boardRepository.findByTitleContaining("제목", paging);
System.out.println("----------------- 검색 결과 -------------------");
for (Board board : boardList) {
System.out.println(board.toString());
}
}
PageRequest.of(0,5);
Sort 클래스 사용
Pageable paging = PageRequest.of(0,5, Sort.Direction.DESC, "seq");
Pageable 객체 생성 시 파라미터를 추가하여 준다.
첫번째는 정렬 방향에 대한 정보, 두 번째는 정렬 대상이 되는 변수 이름
검색 결과는 List<T> 타입으로 받아도 되지만 Page<T> 타입으로 받으면 검색 결과를 활용하기 더 쉽다. 페이징 처리 시 사용할 수 있는 다양한 정보를 추가로 제공한다.
메소드 | 설명 |
---|---|
getNumber() | 현재 페이지 정보 |
getSize() | 한 페이지의 크기 |
hasPreviousPage | 이전 페이지 존재 여부 |
hasNextPage | 다음 페이지 존재 여부 |
이거말고도 아무튼 많다
@Query
어노테이션JPQL을 사용하거나 특정 데이터베이스에 종속적인 네이티브 쿼리를 사용하는 경우를 위해 제공되는 어노테이션. 쿼리메소드와 동일하게 페이징 처리와 정렬 처리가 가능하다
JPQL에서는 사용자 입력 값을 바인딩할 수 있도록 위치기반 파라미터와 이름 기반 파라미터 두 가지를 지원한다.
'?[파라미터의 위치]' 형태로 사용한다. ('?1'의 경우 1번째 파라미터를 말한다.)
@Query("SELECT b FROM Board b WHERE b.title LIKE %?1% ORDER BY b.seq DESC")
List<Board> queryAnnotation1(String searchKeyword);
@Query
어노테이션을 이용하여 JPQL을 추가한다.?1
: 파라미터 위치 지정 @Query("SELECT b FROM Board b WHERE b.title LIKE %:searchKeyword% ORDER BY b.seq DESC")
List<Board> queryAnnotation1(@Param("searchKeyword") String searchKeyword);
@Param
: 매개변수로 받은 String searchKeyword 값을 바인딩한다.특정 변수 값만 조회할 경우 검색 결과로 엔티티 객체가 조회되는 것이 아니라 여러 변수값이 조회되는 것이기 때문에, 반환값을 List<Object[]> 형태로 받아야 한다.
@Query("SELECT b.seq, b.title FROM Board b WHERE b.title like %?1%")
List<Object[]> queryAnnotation2(@Param("searchKeyword") String searchKeyword);
@Query("SELECT b FROM Board b")
List<Board> queryAnnotation3(Pageable paging);
@Test
public void testQueryAnnotation3(){
Pageable paging = PageRequest.of(0,3, Sort.Direction.DESC, "seq");
List<Board> boardList = boardRepository.queryAnnotation3(paging);
System.out.println("----------------- 검색 결과 -------------------");
for(Board board : boardList) System.out.println(board.toString());
}