Spring Data JPA
Spring Data JPA는 Spring 프레임워크의 일부로, JPA(Java Persistence API)를 편리하게 이용할 수 있습니다. 이를 통해 개발자들은 데이터 엑세스 코드를 작성하는데 적은 시간을 투자하고, 비즈니스 로직에 더욱 집중할 수 있습니다.
Repository 인터페이스: 편리함 증대
Spring Data JPA는 데이터베이스와의 상호작용을 더욱 간단하게 만들기 위해 Repository 인터페이스를 활용합니다.
기존의 JPA에서는 개발자가 EntityManager를 사용하여 데이터베이스 작업을 수행하였습니다. 그러나 Spring Data JPA는 이보다 더 추상화된 접근 방식을 제공합니다. 개발자는 단순히 Repository 인터페이스를 정의하고, 필요한 메서드를 선언함으로써 데이터베이스 작업을 수행할 수 있습니다. 이러한 인터페이스는 내부적으로 EntityManager를 활용하여 데이터베이스와 상호작용하게 됩니다. 또한 Repository 인터페이스는 기본적인 CRUD 작업을 자동으로 제공합니다. 개발자가 직접 쿼리를 작성하거나 복잡한 설정을 할 필요 없이, 간단한 메서드 선언만으로도 데이터베이스 작업을 처리할 수 있습니다. 이는 생산성을 높이고 반복적인 코드 작성을 줄여줄 뿐만 아니라, 코드의 가독성도 향상시켜줍니다.
공통 인터페이스 적용
- 인터페이스를 생성 후 JpaRepository<T, ID> 인터페이스를 상속받습니다.
- T: 타겟 엔티티 / ID: 엔티티의 PK
public interface MemberRepository extends JpaRepository<Member, Long> {
}
Spring Data JPA의 기본 메서드들
save(S entity) 엔티티를 저장하거나 수정 saveAll(Iterable<S> entities 여러 엔티티를 한 번에 저장 delete(T entity) 엔티티 삭제 deleteById(ID id) ID에 해당하는 엔티티 삭제 deleteAll() 모든 엔티티 삭제 deleteAll(Iterable<? extends T> entities) 여러 엔티티를 한 번에 삭제 findById(ID id) ID에 해당하는 엔티티 조회 findAll() 모든 엔티티 조회 findAllById(Iterable<ID> ids) ID 리스트에 해당하는 엔티티를 조회 count() 엔티티의 총 개수를 조회 existsById(ID id) ID에 해당하는 엔티티가 존재하는지 여부 확
JPA만으로 JPQL을 실행할 때는 TypeQuery를 생성하고 여러 과정을 작성해주어야 합니다. Spring Data JPA를 활용하면 번거롭게 이러한 과정을 거치지 않고 구현할 수 있습니다. Spring Data JPA가 제공해주는 방법은 다음과 같은 방법이 있습니다.
- Query Methods
- @Query
Query Methods로 간단한 쿼리 작성
- Query Methods를 사용하면 메서드 이름만으로 쿼리를 자동으로 생성할 수 있습니다. 스프링 데이터 JPA는 메서드 이름을 분석해서 JPQL을 생성하고 실행합니다.
- 작성은 Repository 인터페이스에 작성해주면 됩니다.
- `findBy` 접두사를 사용하여 엔티티의 필드를 조건으로 하는 쿼리를 쉽게 작성할 수 있습니다.
- JPQL의 매개변수는 인수의 순서대로 바인드 됩니다.
- Spring에서 제공하는 Docs
// 단 건
Member findByEmail(String email);
// 한 건 이상(컬렉션 사용)
List<Member> findByUsername(String username);
List<Member> findByUsernameAndAge(String username, int age);
List<Member> findByUsernameAndAgeLessThanOrderByAgeAsc(String username, int age);
// 정렬
List<Member> findByAgeOrderByUsernameDesc(int age);
List<Member> findByAgeAndUsernameOrderByAgeDescUsernameAsc(int age, String username);
// 비교
List<Member> findByAgeGreaterThan(int age);
List<Member> findByAgeLessThan(int age);
List<Member> findByAgeBetween(int start, int end);
// 키워드 ex."John Doe"
findByUsernameStartingWith("John"); //"John"으로 시작하는 엔티티를 찾습니다.
findByUsernameEndingWith("Doe"); // "Doe"로 끝나는 엔티티를 찾습니다.
findByUsernameContaining("ohn"); // "ohn"을 포함하는 엔티티를 찾습니다.
findByUsernameLike("%ohn%"); // "ohn"을 포함하는 어떤 위치에 있더라도 해당하는 엔티티를 찾습니다.
- 컬렉션 인터페이스를 사용했을 때 조회 결과가 없는 경우: 빈 컬렉션 반환
- 단 건 조회할 때 조회 결과가 없는 경우: null 반환
단 건 조회시, 내부적으로 Query.getSingleResult()를 호출합니다. Spring Data JPA는 단 건 조회 시 예외가 발생하면 무시하고 null을 반환합니다.
@Query: 메서드에 쿼리 직접 정의
복잡한 JOIN 또는 서브 쿼리를 포함한 쿼리를 작성하거나, 복잡한 집계 함수 또는 GROUP BY를 포함한 쿼리를 작성할 때 사용합니다. 또한 동적인 쿼리를 작성하거나 성능을 최적화하고 싶을때, 네이티브 SQL을 활용하고 싶을 때 @Query 어노테이션을 활용합니다.
// JPQL
@Query("SELECT u FROM User u WHERE u.age > :age")
List<Member> findUsersByAgeGreaterThan(@Param("age") int age);
// Native SQL
@Query(value = "SELECT * FROM users u WHERE u.age > :age", nativeQuery = true)
List<User> findUsersByAgeGreaterThanNative(@Param("age") int age);
// UPDATE문
@Query("UPDATE Member m SET m.age = :age")
Integer updateAgeAll(@Param("age") Integer age);
위처럼 @Query를 사용하 복잡한 쿼리를 직접 작성할 수 있고 메서드 이름을 가독성이 좋게 작성할 수 있습니다.
@Query 어노테이션을 사용하여 JPA 문법과 동일하게 DTO를 조회할 수도 있습니다. DTO를 사용하여 엔티티의 일부 필드만 조회하거나, 여러 엔티티의 조인 결과를 특정 형식으로 매핑하여 반환할 수 있습니다.
// JPQL
@Query("SELECT new com.example.dto.UserDTO(m.id, m.username)
FROM Member m
WHERE m.age > :age")
List<UserDTO> findUserDTOsByAgeGreaterThan(@Param("age") int age);
// Native SQL
@Query(value = "SELECT u.id, u.username
FROM users u
WHERE u.age > :age",
nativeQuery = true)
List<UserDTO> findUserDTOsByAgeGreaterThanNative(@Param("age") int age);
페이징과 정렬
Repository 인터페이스에 paging 메서드 생성:
public interface MemberRepository extends JpaRepository<Member, Long> {
Page<Member> findByAgeGreaterThan(int age, Pageable pageable);
}
Paging, sorting 정보 설정:
public Page<Member> getUsers(int page) {
List<Sort.Order> sorts = new ArrayList<>();
sorts.add(Sort.Order.desc("username"));
// 1 param: 0부터 시작하는 페이지 번호
// 2 param: 페이지당 데이터 개수
// 3 param: 정렬 조건
Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));
return MemberRepository.findByAgeGreaterThan(18, pageable);
}
> sort 설정 방법
// sort 설정 방법 1
List<Sort.Order> sorts = new ArrayList<>();
sorts.add(Sort.Order.desc("username"));
Pageable pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sorts));
// sort 설정 방법 2
Pageable pageable = PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "username"));
// sort 설정 방법 3
Pageable pageable = PageRequest.of(pageNumber, pageSize, new Sort(Direction.DESC, "username"));
Page 객체에서 페이지 정보 얻기
Page<Member> membersPage = memberRepository.findByAgeGreaterThan(age, pageable);
membersPage.getTotalPages(); // 총 페이지 수를 반환
membersPage.getTotalElements(); // 총 데이터 개수를 반환
membersPage.getNumber(); // 현재 페이지 번호를 0부터 시작하여 반환
membersPage.getSize(); // 페이지당 데이터 개수를 반환
membersPage.getNumberOfElements();// 현재 페이지의 데이터 개수를 반환
membersPage.hasNext(); // 다음 페이지가 있는지 여부를 반환
membersPage.hasPrevious(); // 이전 페이지가 있는지 여부를 반환
membersPage.isFirst(); // 현재 페이지가 첫 번째 페이지인지 여부를 반환
membersPage.isLast(); // 현재 페이지가 마지막 페이지인지 여부를 반환
membersPage.getContent(); // 현재 페이지의 데이터를 리스트 형태로 반환
membersPage.getPageable(); // 현재 페이지의 페이징 및 정렬 정보를 반환
벌크성 쿼리: @Modifying
벌크성 쿼리는 대량의 데이터를 한 번에 수행하기 위해 사용되는 쿼리입니다.
Spring Data JPA에서 벌크성 쿼리를 작성하기 위해선 `@Modifying` 어노테이션을 사용하여 수행할 메서드에 표시하고, `@Query` 어노테이션을 사용하여 실제 쿼리를 정의하면됩니다.
`@Modifying` 어노테이션은 Spring Data JPA가 쿼리 실행을 구분하기 위한 역할을 합니다. 해당 어노테이션을 붙여줌으로 Spring Data JPA가 해당 쿼리가 수정 또는 삭제 작업을 수행하는 쿼리임을 알게합니다. 만약 해당 어노테이션을 붙여주지 않으면 Spring Data JPA는 해당 메서드를 데이터 조회용으로 간주하여 수정 작업을 수행하지 않습니다.
즉, `@Modifying` 어노테이션은 데이터의 변경 작업임을 Spring Data JPA에게 명시하는 용도입니다.
수정 및 삭제 쿼리:
public interface MemberRepository extends JpaRepository<Member, Long> {
@Modifying
@Query("UPDATE Member m
SET m.active = true
WHERE m.lastLoggedIn < :lastLoggedIn")
int activateInactiveUsers(@Param("lastLoggedIn") LocalDateTime lastLoggedIn);
@Modifying
@Query("DELETE FROM User u
WHERE u.age < :age")
int deleteUsersWithAgeLessThan(@Param("age") int age);
}
벌크성 쿼리를 수행(수정, 삭제)하고 나서 데이터를 조회하려고 하면 영속성 컨텍스트에 과거 값이 남아있어서 문제가 생길 수 있기 때문에, 벌크성 쿼리 수행 후 바로 조회를 해야 한다면 다음과 같이 @Modifying의 옵션을 지정해주어야 합니다.
@Modifying(clearAutomatically = true)
@EntityGraph
- Spring Data JPA에서, 데이터를 조회할때 연관 관계를 함께 로딩하는 방법을 지정하는데 사용되는 어노테이션
- 페치 조인(FETCH JOIN)의 간편 버전
- left join만 지원합니다. 다른 방식이 필요하면 JPQL을 작성하고 fetch join을 직접 작성해주어야 합니다.
@Override
@EntityGraph(attributePaths = {"orders"})
List<Member> findAll;
@EntityGraph(attributePaths = {"orders"})
@Query("select m from Member")
List<Member> findAllByEntityGraph();
@EntityGraph 속성
- attributePaths: 로딩 설정을 변경하고자하는 프로퍼티를 배열로 지정합니다.
- type: @EntityGraph를 어떤 방식으로 적용할 것인지를 지정합니다. 기본값은 `EntityGraphType.FETCH`로 지정된 속성 경로에 대해 즉시 로딩(EAGER)을 적용합니다. 다른 옵션으로는 `LOAD`가 있습니다. 이 옵션을 사용하면 연관된 데이터를 지연 로딩하게 됩니다.
'Spring Data > JPA' 카테고리의 다른 글
JPA Entity의 기본 생성자가 필수인 이유 (0) | 2023.08.30 |
---|---|
JPA JPQL(객체 지향 쿼리 언어)를 완벽히 이해해보자 (0) | 2023.08.23 |
JPA '값 타입'의 선언과 활용, @AttributeOverride와 @ElementCollection, @CollectionTable 역할 (1) | 2023.08.23 |
JPA 영속성 전이(CASCADE)와 고아 객체 제거: 데이터 일관성과 관리 효율 높이는 전략(방법) (0) | 2023.08.23 |
JPA 프록시와 지연로딩: 성능 최적화를 위한 기술(방법) (0) | 2023.08.22 |