[Spring & JPA & JPAQ] 쿼리 메서드, JPQL, NativeQuery

jin·2023년 1월 6일
0

Spring Boot

목록 보기
7/8
post-custom-banner

Spring Data JPA

  • JPA 자체 제공 쿼리메서드가 있다.

일반 JPA 메서드

  • Repository 인터페이스에 간단한 네이밍 룰을 이용하여 메서드를 작성하면 원하는 쿼리를 실행할 수 있다.
	public interface UserRepository extends JpaRepository<User, Long> {
   		 Optional<User> findByUserName(String userName);
}

@NamedQuery(정적 쿼리)

  • Entity에 @NamedQuery 어노테이션을 사용해 name과 query를 지정해준다.
  • 애플리케이션 로딩 시점에 쿼리를 검증할 수 있다.
  • Entity에 쿼리가 많이 쌓이는 단점과, Entity가 쿼리까지 담당하게 되어 단일 책임 원칙도 벗어나서 실무에서는 잘 사용하지 않는다고 한다.
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@NamedQuery(
        name = "User.findByUserName",
        query = "select u from User u where u.userName = :userName"
)
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String userName;
}

@Query

  • @NamedQuery를 사용하지 않고 Repository메서드에서 쿼리를 바로 정의할 수 있다.
  • 애플리케이션 로딩 시점에 쿼리를 검증할 수 있다.
  • SQL과 유사한 JPQL (Java Persistence Query Language) 라는 객체지향 쿼리 언어를 통해 복잡한 쿼리 처리를 지원한다.
  • 조인이나 복잡한 조건을 처리해야 하는 경우 @Query를 사용한다.
@Query("select u from User u where u.username = :username")
List<User> findUser(@Param("username") String username);

공식문서를 보면 JPA에 지원되는 키워드와 해당 키워드를 포함하는 메서드가 무엇으로 변환되는지 알 수 있으니 참고하자!

(공식 문서 : https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-keywords)


JPQL(Java Persistence Query Language)

  • JPA를 사용하면 엔티티 객체 중심으로 개발한다.
  • 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색을 한다.
    - DB를 몰라야 한다. 자바 코드에서 어떤 테이블이 있는지 생각을 하면서 개발을 해야한다.
  • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능하다. 필요한 데이터만 불러오려면 결국 검색 조건이 포함된 SQL이 필요하다.
  • 그래서 JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공한다.
  • JPQL은 엔티티 객체를 대상으로 쿼리를 질의하고
  • SQL은 데이터베이스 테이블을 대상으로 쿼리를 질의한다.

JPQL

	@Transactional
	@Modifying
    @Query("UPDATE Like l SET l.deletedAt=null WHERE l.id= :like_id")
    void reSave(@Param("like_id") Long like_id);

	@Query("SELECT COUNT(l) FROM Like l WHERE l.post= :post AND l.deletedAt IS NULL")
    Integer countByPost(@Param("post") Post post);
  1. from절에 들어가는 것은 객체이다.
  2. 엔티티와 속성은 대소문자를 구분한다
    • Like 엔티티와 deletedAt 필드
  3. JPQL 키워드는 대소문자 구분 안함
    • SELECT, FROM, where
  4. 엔티티 이름을 사용한다. 테이블 이름이 아니다
    • 엔티티명 Like
  5. 별칭은 필수이다.
    • Like의 별칭은 l

@Query 어노테이션 사용하기

@Modifying

insert, update, delete 뿐만 아니라 DDL구문을 사용할 때도 표기를 해줘야 된다. 왜냐하면 영속성 컨텍스트에 오래된 데이터를 비워주고 새로운 데이터를 읽어오기 위해서다. @Query에 벌크 연산 쿼리를 작성하고 @Modifying 어노테이션을 붙여주지 않으면 InvalidDataAccessApiUsage exception 발생하게 된다.

Repository

	@Modifying
    @Query("update Member m set m.age = m.age + 1 where m.age >= :age")
    int bulkAgePlus(@Param("age") int age);

Test코드

 @Test
    public void bulkUpdate() {
        // given
        memberRepository.save(new Member("member1", 10));
        memberRepository.save(new Member("member2", 19));
        memberRepository.save(new Member("member3", 20));
        memberRepository.save(new Member("member4", 21));
        memberRepository.save(new Member("member5", 40));

        // when
        int resultCount = memberRepository.bulkAgePlus(20);
 //        em.flush();
 //        em.clear();

        List<Member> result = memberRepository.findByUsername("member5");
        Member member = result.get(0);
        System.out.println("member = " + member);

       


벌크 연산 JPQL은 기존 JPA처럼 영속성 컨텍스트를 거쳐 쓰기지연 SQL로 동작하는 것이 아닌 바로 DB에 직접 쿼리를 날리게 된다. 그래서 영속성 컨텍스트에 저장되어 있는 데이터를 무시하고 실행하기 때문에 영속성 컨텍스트에 있는 엔티티의 상태와 DB에 엔티티 상태가 달라질 수 있다.. 즉, 영속성 컨테스트에 정의되어 있는 데이터가 우선순의를 갖고 덮어쓰지 않는다. 그래서 위에 로직 실행결과 기대한 값 41이 아닌 40이 나오게 된다.

해결책1
em.flush();로 영속성 컨텍스트에 있는 데이터를 DB로 쿼리를 전송하고, em.clear();로 영속성 컨텍스트에 있는 데이터를 제거주면 된다.
해결책2
@Modifying(clearAutomatically = true)를 적용하면 되는데 이름 그대로 @Query로 정의된 JPQL을 실행 후 자동으로 영속성 컨텍스트를 비워주는 옵션이다. 영속성 컨텍스트가 비워지니 DB에서 가져온 모든 데이터들이 영속성 컨텍스트에 저장되어 최신 상태를 유지할 수 있게 된다.
해결책3
영속성 컨텍스트에 엔티티가 없는 상태에서 벌크 연산을 먼저 실행한다.

@Transactional

update, delete를 할 때 표기해줘야 정상 실행이 된다.

@Param

파라미터를 쿼리에 주입하는 방법
쿼리문에 받는 파라미터 이름을 다르게 매핑하고 싶으면 지정해줄 수 있다.
@RequestParam을 사용할 떄와 동일한 방식이라고 생각하면 된다.

profile
jin
post-custom-banner

0개의 댓글