일반 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
@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);
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
영속성 컨텍스트에 엔티티가 없는 상태에서 벌크 연산을 먼저 실행한다.
update, delete를 할 때 표기해줘야 정상 실행이 된다.
파라미터를 쿼리에 주입하는 방법
쿼리문에 받는 파라미터 이름을 다르게 매핑하고 싶으면 지정해줄 수 있다.
@RequestParam
을 사용할 떄와 동일한 방식이라고 생각하면 된다.