본문 바로가기
  • 실행력이 모든걸 결정한다
Spring 사전 준비/JPA Hibernate

[JPA] Spring Data JPA(1)

by 김코더 김주역 2022. 8. 19.
반응형

1. Spring Data JPA 소개

- 데이터를 다루는 동작으로는 JPA에서도 마찬가지로 CRUD(Create Read Update Delete)가 있다. JpaRepository 인터페이스는 기본적인 CRUD 메소드들(save, findOne, getOne, findAll, delete, count 등)을 제공해주기 때문에, 레포지토리마다 일일이 CRUD 메소드를 작성할 필요 없이 JpaRepository를 상속한 인터페이스만 만들면 된다.

- 특정 동작에 대한 메소드는 Spring Data JPA가 해석할 수 있는 규칙으로 메소드 이름을 지어 추가하면 된다. 놀랍게도, 메소드 이름으로 적절한 JPQL 쿼리가 자동으로 생성되어 실행된다. 메소드 이름 규칙은 [3. 메소드 작명 양식]에서 설명한다.

- JpaRepository 인터페이스의 구현체는 Spring Data JPA가 자동으로 생성해서 bean으로 등록해준다. 참고로, 구현체에는 @Transactional 어노테이션이 붙어있다.

 

Spring Data JPA 적용 전

public class MemberRepository {
    public void save(Member member) {...}
    public Member findOne(Long id) {...}
    public List<Member> findAll() {...}
    public Member findByUsername(String username) {...}
}

public class TeamRepository {
    public void save(Team team) {...}
    public Member findOne(Long id) {...}
    public List<Member> findAll() {...}
}

 

Spring Data JPA 적용 후

/* @Configuration 클래스에 @EnableJpaRepositories을 추가하면 Repository마다 @Repository를 생략할 수 있다.
Spring Boot에서는 @EnableJpaRepositories이 자동설정되어 있기 때문에 이 마저도 생략 가능하다. */

@Repository
public interface MebmerRepository extends JpaRepository<Member, Long> { // 제네릭에 엔티티 클래스와 식별자 타입을 지정
	Member findByUsername(String username);
}

@Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
	
}

 

- 조회 결과가 한 건 이상이면 컬렉션을 반환하고, 한 건이면 반환 타입을 반환한다. 만약 조회 결과가 없다면 컬렉션은 빈 컬렉션을 반환하고 단건은 null을 반환한다. 단건으로 지정한 메소드는 내부적으로 Query.getSingleResult() 메소드를 호출하고, 결과가 2건 이상 조회되면 javax.persistence.NonUniqueResultException 예외가 발생한다.

 

 

 

2. Spring Data JPA 설정

1) 라이브러리

- Spring 사용 : https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa

- Spring Boot 사용 : https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa

 

 

2) @Repository 탐색 범위 설정

(1) XML 방식

- xml 설정 파일에 jpa 스키마를 추가하고 <jpa:repositories>의 base-package 속성에 지정하면 된다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd 
        http://www.springframework.org/schema/data/jpa 
        http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
         
    <jpa:repositories base-package="com.example.demo.repository" />
	...
    
</beans>

 

(2) Annotation 방식

- @Configuration 클래스에 @EnableJpaRepositories 어노테이션을 추가해서 basePackages 속성에 지정하면 된다.

@Configuration
@EnableJpaRepositories(basePackages="com.example.demo.repository")
public class AppConfig {...}

 

 

 

3. 메소드 작명 양식

특정 동작에 대한 메소드는 어떤 규칙으로 작명하면 될까?

예를 들어, "findByUsername(String username)"는 username이라는 필드의 값이 인자로 들어온 username값과 일치한  객체들을 모두 조회한다는 의미다. 

생성한 JPQL을 보면 기본적으로 위치 기반 파라미터 방식을 사용한다는 것을 알 수 있다.

 

더 많은 양식들은 아래의 표에서 찾아보면 된다. spring.io에 있는 내용이다.

+ 추가) Limit : https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.limit-query-result

User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);

+ 추가) Distinct : https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation

// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

 

 

4. 정렬과 페이징

1) 정렬

- Sort 객체를 파라미터로 넣어 조회 결과를 원하는 기준으로 정렬할 수 있다.

public interface MemberRepository extends JpaRepository<Mebmer, Long> {
    List<Member> findByUsername(String username, Sort sort);
{

※ Sort 인터페이스 : org.springframework.data.domain.Sort

- Sort에는 정렬 순서와 기준(엔티티의 속성)을 지정할 수 있다.

- 여러 정렬 기준을 두고 싶다면 and()를 이용하면 된다.

Sort.by("username").descending().and(Sort.by("id"))

 

 

2) 페이징

- 페이징은 Pageable 인터페이스를 파라미터로 넣어 조회 결과를 List나 Page 인터페이스로 조회할 수 있게 하는 기능이다. Page를 이용하면 Page 인터페이스가 제공하는 유용한 메소드들을 이용할 수 있다.

public interface MemberRepository extends JpaRepository<Mebmer, Long> {
    Page<Member> findByUsername(String username, Pageable pageable);
    // List<Member> findByUsername(String username, Pageable pageable);
{

※ Pageable 인터페이스 : org.springframework.data.domain.Pageable

※ PageRequest 클래스(Pageable의 구현체) : org.springframework.data.domain.PageRequest

- Pageable에는 페이지 위치, 한 페이지 당 포함할 결과 수를 지정할 수 있고, 페이징 안에서 Sort 객체를 사용하여 정렬할 수도 있다.

 

Page가 제공하는 메소드

출처 :&nbsp;https://catchdream.tistory.com/181

 

3) 사용 예제

// 0페이지, 10건, "Member.username" 기준 내림차순 조회
PageRequest request = PageRequest.of(0, 10, Sort.by("username").ascending());

Page<Member> resultSet = memberRepository.findByUsername("name1", request);

int totalPages = resultSet.getTotalPages();
boolean hasNextPage = resultSet.hasNextPage();
List<Member> members = resultSet.getContent();

 

 

 

5. @Query와 Named 쿼리의 사용

- Spring Data JPA에서도 쿼리를 직접 작성할 수 있는 방법을 제공하며, 이름 기반 파라미터 방식과 위치 기반 파라미터 방식 모두를 지원한다. 이름 기반 파라미터 방식을 사용하는 경우에는 org.springframework.data.repository.query.Param 어노테이션을 사용한다.

 

1) @Query

- 레포지토리 메소드에 직접 쿼리를 정의할 때 사용되는 어노테이션으로, 애플리케이션 실행 시점에 문법 오류를 발견할 수 있다는 장점이 있다.

public interface MemberRepository extends JpaRepository<Member, Long> {
    @Query(value = "select m from Member m where m.username = :name")
    Member findByUsername(@Param("name") String username);
}

 

- Named 쿼리와 동일한 역할을 하지만, 차이가 있다면 이름이 없는 Named 쿼리라고 할 수 있겠다. Named 쿼리에 대한 내용은 https://kimcoder.tistory.com/493 포스팅의 [2-15) Named 쿼리]를 참고하자.

- @Query.nativeQuery=true로 설정하면 네이티브 SQL을 사용할 수 있다. 단, Spring Data JPA가 제공하는 파라미터 바인딩을 사용하면 JPQL은 위치 기반 파라미터를 1부터 시작하지만 네이티브 SQL은 0부터 시작한다는 점에 주의하자.

@Query(value="SELECT * FROM MEMBER WHERE USERNAME=?0", nativeQuery=true)
Member findByUsername(String username);

 

 

2) Named 쿼리의 사용

- 바로 위에 링크한 https://kimcoder.tistory.com/493의 [2-15) Named 쿼리]에서 봤듯이, Named 쿼리는 orm.xml 또는 @NamedQuery로 지정할 수 있었다.

- Spring Data JPA는 우선적으로 Named 쿼리의 이름을 찾아 쿼리를 실행한다. 단, NamedQuery의 이름은 "[도메인 클래스].[메소드 이름]" 형태로 정의되어 있어야 한다.

- 예를 들어, 아래 예제와 같이 엔티티 클래스가 "Member"로 지정된 JpaRepository의 findByUsername 메소드에서는 "Member.findByUsername" 이라는 이름을 가진 Named 쿼리를 우선적으로 찾아서 실행하고, 만약 찾지 못했다면 메소드 이름으로 쿼리를 생성하는 전략을 사용한다.

public interface MemberRepository extends JpaRepository<Mebmer, Long> {
    List<Member> findByUsername(@Param("username") String username);
{

- 애플리케이션 실행 시점에 문법 오류를 발견할 수 있다는 장점이 있다.

 

 

3) 벌크성 쿼리

- @Query 메소드 위에 org.springframework.data.jpa.repository.Modifying 어노테이션을 추가하면 된다.

@Modifying
@Query("UPDATE ...")

- JPA를 사용했을 때의 javax.persistence.Query.executeUpdate 메소드의 역할을 한다.

- 벌크성 쿼리를 실행한 뒤에 영속성 컨텍스트를 초기화하고 싶다면 @Modifying.clearAutomatically를 true로 지정하면 된다.

 

 

 

6. Hint

- 힌트란 최적화를 위해 쿼리 실행 방법을 지시하는 것이다.

- org.springframework.data.jpa.repository.QueryHints 어노테이션을 사용한다.

- SQL 힌트가 아닌 JPA 구현체에 제공되는 힌트다.

 

사용 예시

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

※ forCounting : 인터페이스를 반환타입으로 지정했을 때 페이징을 위한 count 쿼리에도 쿼리 힌트를 적용할 지 설정

 

 

 

7. Lock

- Lock은 데이터의 무결성을 보장하기 위한 방법이다.

- org.springframework.data.jpa.repository.Lock 어노테이션을 사용한다.

 

사용 예시

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

- 낙관적, 비관적 락과 LockModeType에 대한 설명은 아래 포스팅을 참고하자.

https://kimcoder.tistory.com/509

 

[JPA] 트랜잭션과 Lock

1. 격리 수준 - 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 설정하는 격리의 정도 - @Transactional 어노테이션으로 격리 수준을 지정할 수 있으며, 격리 수준의 종류는 아래 포스

kimcoder.tistory.com

 

 

 

● 참고자료 : 자바 ORM 표준 JPA 프로그래밍

 

반응형

'Spring 사전 준비 > JPA Hibernate' 카테고리의 다른 글

[JPA] 웹 애플리케이션에서의 영속성 관리  (0) 2022.08.22
[JPA] Spring Data JPA(2)  (0) 2022.08.20
[JPA] JPQL 최적화  (0) 2022.08.14
[JPA] 스토어드 프로시저  (0) 2022.08.14
[JPA] 네이티브 SQL  (0) 2022.08.11

댓글