쿼리 메소드 기능

나길진·2024년 1월 5일

본 내용은 인프런의 김영한 님의 강의내용이 포함되어 있습니다.
실전! 스프링 데이터 JPA

스프링 데이터 JPA에서 제공해주는 기능중에 좀 복잡한 쿼리는 후에 QueryDSL로 풀리는 경우가 대부분인 것 같지만 JPA 기초부터 공부하면서 따라가기에 강의중에서 좀 기억해야하는 내용만 간단하게 정리하려고 합니다.

메소드 이름으로 쿼리 생성

먼저 네임드 쿼리는 스프링 데이터 JPA가 제공해주는 JPARepository를 상속받아 나의 엔티티에 맞는 Repository 인터페이스를 생성하고 규약에 맞춰서 메소드명만 작성해주면 해당 쿼리를 스프링 데이터 JPA가 작성해준다.

예시

//T : 엔티티 타입, id : 엔티티 아이디 타입
interface MemberRepository extends JpaRepository<Member, Long>{
	public Member findByUsername(String username);
}

이런식으로 간단하게 메소드 이름으로 Member를 조회할 수 있다. 하지만 단점은 뒤에 조건이 많아지면 메소드명이 끝없이 길어지기 때문에 대략 조건이 2개정도 있는 간단한 쿼리에 사용하기를 권장한다고 한다.

네임드 쿼리

조회할 때 조건이 여러개여서 위와 같은 방식으로 사용하기 어려울 때 사용하는 방법이다.
엔티티에 @NamedQuery 어노테이션을 사용해서 jqpl로 직접 쿼리를 작성하고 해당 쿼리에 이름을 부여하는 방식이다. 그 이후에 엔티티매니저의 createNameQuery로 호출을 해서 사용한다

예시

@Entity
@NamedQuery{
	name="Member.findByAge",
    query="select m from Member m where m.age = :age"
}
em.createNamedQuery("Member.findByAge", Member.class)
	.setParameter("age", age)
    .getResultList();

NamedQuery 어노테이션에서 name값은 아무렇게나 넣어도 작동은 하지만 관례는 "엔티티명.쿼리명" 으로 한다고 한다.
네임드 쿼리의 장점은 query값을 실수로 잘못 입력했을 때 런타임 오류를 발생시켜줘서 정상적인 쿼리인지 확인을 시켜준다는 점이다.

하지만 단점으로는 엔티티에 쿼리를 작성하는 점이 역할의 경계가 무너진(?)그림이라고 생각한다.
두 번째로는 구현체를 작성해야한다. entityManager에서 저장한 쿼리를 불러오고 파라미터를 매핑시킨 후 결과를 리턴하는 구현체를 작성해야하는데 이 작업도 귀찮기 때문에 해당기능은 잘 사용하지 않는다고 한다.

@Query

네임드 쿼리의 단점이라고 생각하는 것을 개선시켜준 기능이다. 엔티티에 쿼리를 작성하는것을 Repository에 메소드명을 작성 후 @Query 어노테이션을 붙여주고 파라미터에도 @Param 어노테이션을 사용하여 파라미터를 매핑시켜준다.

예시

interface MemberRepository extends JpaRepository<Member, Long>{
	
    @Query("select m from Member where age = :age")
    public List<Member> findByAge(@Param("age") int age);
    
}

네임드 쿼리랑 비교해보면 굉장히 간단해진걸 느낄 수 있다. 실무에서도 많이 사용하는 기능이라고 한다.
하지만 동적쿼리를 작성할 때에는 사용할 수 없는 기능같다. JPQL을 좀 더 간단하게 사용하기 위해 스프링 데이터 JPA가 제공해주는 기능 같다.

페이징 및 정렬

페이징 기능은 회사에서 나름 만들어 보면서 은근 신경쓸 부분이 많았던 기억이 있다. 하지만 스프링 데이터 JPA에서는 페이징과 정렬 기능을 제공해주는데 방식은 2가지가 있다.

  1. 일반적인 페이징
  2. 슬라이드 페이징(더보기)

이 2가지를 제공해주는데 슬라이드 페이징 방식은 데이터의 토탈 갯수를 조회하지 않기 때문에 성능상 이점이 있다.
사용방법은 내가 상속받은 Repository에 메소드를 작성할 때 반환타입을 Page<엔티티>로 작성을 하고 파라미터로 Pageable interface를 받아주면 된다.

예시

interface MemberRepository extends JpaRepository<Member, Long>{
	
    public Page<Member> findByAge(int age, Pageable pageable);
    
}
//페이지 번호(0번부터 시작), 가져올 갯수, 정렬 추가(이름 역순)
PageRequest page = PageRequest.of(0, 10, Sort.by(Sort.Directrion.DESC, "name"));

Page<Member> findMembers = memberRepository.findByAge(10, page);

Page는 일반적인 페이징 방식을 사용할 때 받는 클레스이고 슬라이드 페이징 방식은 Slice를 사용한다. Page는 Slice를 상속받은 객체로 getTotalElements(), getTotalPages(),
map() 메소드를 추가했기 때문에 토탈 갯수를 구하는 쿼리가 같이 실행된다.

벌크성 쿼리

벌크성 쿼리란 update시 여러 건을 한번에 수정하는 쿼리를 말한다. 예를 들면 멤버의 나이가 해가 바뀌어서 한살씩 증가시켜야할 때 "update Member m set m.age = m.age + 1" 이라는 JPQL을 작성하고 @Modifying 어노테이션을 추가해주면 끝이다.

예시

interface MemberRepository extends JpaRepository<Member, Long>{
	
    @Modifying
    @Query("update Member m set m.age = m.age + 1")
    public int bulkAgePlus();
    
}

해당 쿼리를 실행시키는 것은 문제가 되지 않지만 주의해야 할 점은 실행시킨 이후이다. 왜냐면 벌크성 쿼리는 영속성 컨텍스트의 영향을 받지 않기 때문이다. DB에는 값을 변경했지만 영속성 컨텍스트에 값들은 변경되지 않았기 때문에 그냥 사용하면 값의 싱크가 맞지않을 수 있다.
반드시 영속성 컨텍스트를 clear() 해준 후 다시 조회해서 사용해야 한다.

EntityGraph

해당기능은 연관관계 매핑시 다대일 매핑시 지연로딩으로 설정하면 N+1 문제가 생기는데 그 때 사용하는 fetch join을 간단하게 사용하는 방법이라고 알면 될 것 같다.

예시

@EntityGraph(attributePaths = {"조인할 테이블 명"})
public List<Member> findAll();

참고사이트

profile
백엔드 개발자

0개의 댓글