공통 인터페이스

스프링 데이터 JPA를 사용하는 가장 단순한 방법은, 인터페이스를 상속받고, 제네릭에 엔티티클래스와, 엔티티 클래스가 사용하는 식별자 타입을 지정

public interface TestRepository extends JpaRepository<Test, String>{
    
}

이 JpaRepository를 단순하게 상속받은 것으로써 사용할 수 있게 되었다 JpaRepository의 구조는 Repository -> CrudRepository -> PagingAndSortRepository -> JpaRepository 순서대로 이렇게 상속받아서 내려오게 되는데, Repository, CrudRepository, PagingAndSortRepository는 스프링 데이터에서 제공해주는 기능이고 JpaRepository는 스프링 데이터 JPA에서 제공해주고 있는 기능이다

여기서 주요한 메소드로는 CrudRepository

  • save(엔티티) : 새로운 엔티티는 저장하고, 이미 있는 엔티티는 수정한다 / 엔티티에 식별자가 null이면 새로운 엔티티로 판단해서 persist를 호출, 있으면 merge를 호출하는 방법

  • findOne(엔티티아이디) : 엔티티 하나를 조회하고, 내부에서는 EntityManager.find()를 호출한다

  • exists(엔티티아이디) : 엔티티 하나를 조회하고 있다면 true, 없으면 false

  • count() :

  • delete(엔티티) : 엔티티 하나를 삭제한다. 내부에서는 EntityManager.remove()를 호출

PagingAndSortRepository

  • findAll(...) : 모든 엔티티를 조회하고, 정렬이나 페이징 조건을 파라미터로 제공할 수 있다

JpaRepository

  • getOne(엔티티아이디) : 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference()를 호출한다

이렇게 공통 인터페이스를 사용하면 일반적인 crud를 사용하는 것이 가능하다 하지만, 기본적으로 제공되는 것이 아니라 필요하다면 기능을 확장해서 만들 수 있으니까 얼마나 좋은 기능인것이냐! br

쿼리 메소드 기능

쿼리 메소드 기능은 스프링 데이터 JPA가 제공하는 아주 좋은 기능인데, 이것은 메소드의 이름을 통해서 쿼리를 생성하는 기능이다 스프링 데이터 JPA가 제공하는 쿼리 메소드의 기능은 크게 3가지 있음

  • 메소드 이름으로 쿼리 생성

  • 메소드 이름으로 JPA NamedQuery 호출

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

물론 마음대로 작성한다고 이러한 기능을 다 사용할 수 있는것은 아니고, 스프링 데이터 JPA에서 제공해주는 공식 문서를 확인해서 사용가능 많이 사용? 중요한? 메소드들을 적어봤다

  • And : findByIdAndName

  • Or : findByIdOrName

  • Is, Equal : findByNameIs, findByNameEqual

  • Between : findByCreatedBetween

  • LessThan : findByAgeLessThan (<)

  • LessThanEqual : findByAgeLessThanEqual (<=)

  • GreaterThan : findByAgeGreaterThan (>)

  • GreaterThanEqual : findByAgeGreaterThanEqual (>=)

  • After : findByCreatedAfter

  • Before : findByCreatedBefore

  • IsNull : findByNameIsNull

  • IsNotNull, NotNull : findByName(Is)NotNull

  • Like : findByNameLike

  • NotLike : findByNameNotLike

  • StartingWith : findByNameStartingWith

  • EndingWith : findByNameEndWith

  • Containing : findByNameContaining

  • OrderBy : findByAgeOrderByNameDesc

  • In : findByAgeIn(Collection)

  • NotIn : findByAgeNotIN(Collection)

  • TRUE : findByActiveTrue()

  • FALSE : findByActiveFalse()

  • IgnoreCase : findByNameIgnoreCase()

이 기능은 엔티티의 필드 이름을 따라서 구성되는 부분이기 때문에 알아서 필드명이 변경되면 변경되야한다는 점

JPA NamedQuery

요 방식은 쿼리에 이름을 부여해서 사용하는 방법이다 일단 직접 Named 네이티브 쿼리를 작성할 수 있음

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

이렇게 정의한 Named 쿼리를 직접 JPA에서 호출하기 위해서는 이렇게 사용

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

스프링 데이터 JPA를 사용하면 메소드 이름만으로 NamedQeury를 호출하는 것이 가능하다 이렇게

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

스프링 데이터 JPA는 선언한 "도메인클래스.메소드이름" 으로 Named쿼리를 찾아서 실행한다

@Query, 레포지토리 메소드에 쿼리 정의

직접 쿼리를 정의할 때는 @Query를 사용하는데, 실행할 메소드에 직접 정적 쿼리를 작성하기 때문에 익명 Named 쿼리라고 했다 이렇게 쿼리를 작성해두면 애플리케이션을 실행할 때 쿼리를 체크해주기 때문에 확인하기 쉽다 그리고 네이티브 쿼리를 사용하기 위해서는 이렇게 지정해줘야함

public interface MemberRepository extends JpaRepository<Member, Long>{
    @Query(value = "SELECT * FROM MEMBER WHERE USERNAME = ?0", nativeQuery=true)
    Member findByUsername(String username);
}

파라미터 바인딩

파라미터 바인딩은 이름을 기반으로 바인딩해주거나, 위치를 기반으로 바인딩을 진행해준다 SELECT * FROM MEMBER WHERE USERNAME = ?0 : 이렇게가 위치를 기반으로 진행 SELECT * FROM MEMBER WHERE USERNAME = :name : 이렇게가 이름을 기반으로 진행 위에서 native에서 사용했던 것이 위치기반의 파라미터 바인딩 기법이고 아래의 예시가 이름 기반의 파라미터 바인딩 기법

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

페이징과 정렬

페이징과 정렬하는 데있어서 2가지 특별한 파라미터가 존재한다

  • org.springframework.data.domain.Sort : 정렬 기능

  • org.springframework.data.domain.Pageable : 페이징 기능

사용 예제로는

//count 쿼리 사용
Page<Member> findByName(String name, Pageable pageable);

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

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

검색 조건 - 이름이 김으로 시작하고, 정렬 조건 - 이름으로 내림차순이고, 페이징 조건 - 첫 번째 페이지, 페이지당 보여중 데이터는 10개

public interface MemberRepository extends Repository<Member, Long>{
    Page<Member> findByStartingWith(String name, Pageable pageable);
}
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(); //다음 페이지 존재 여부

이게 밑에 주석3가지는 Page 인터페이스에서 제공해주고 있는 다양한 메소드 중에서도 몇 가지를 적어본 것이다 나중에 필요하면 사용하자

힌트

힌트는 @QueryHints 애노테이션을 사용해서 넣어줄 수 있다

Lock

쿼리시 Lock을 걸고 싶다면 @Lock을 사용해서 락을 걸어서 사용하는 것이 가능하다

사용자 정의 레포지토리 구현

메소드를 직접 구현해야 할 때가 있다 하지만 공통으로 제공해주고 있는 것도 사용하고 싶다면 공통으로 제공해주는 것도 다 구현해야하지만 귀찮으니까 방법이 있다 일단 원하는 레포지토리이름을 만든다

public interface MemberRepositoryCustom{
    public List<Member> findMemberCustom();
}

이렇게 하고, 구현을 실제로 해야하는데, 실제 구현 인터페이스에는 규칙이 있다 레포지토리인터페이스 + Impl로 지어야한다

public class MemberRepositoryCustomImpl implements MemberRepositoryCustom{
    @Override
    public List<Member> findMemberCustomm(){
        //원하는 정의를 구현
        ...
    }
}

그리고 실제로 상속받을때 요놈을 상속받으면 된다

public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom{

}

Last updated

Was this helpful?