Entity class 를 정의하고 CRUD Repository 구현 노가다를 하지 않게해준다.
JpaRepository 는 대부분의 공통 메소드를 지원하기 때문이다.
JpaRepository 지원 메소드
Class Diagram을 보면 JpaRepository 가 얼마나 많은 메소드를 지원하는지 알 수 있다.
(List)CrudRepository , PagingAndSortingRepository , QueryByExmampleExecutor 등 여러 인터페이스를 상속받는다.
Spring Data Jpa 3.4.0 기준
JpaRepository 가 제공하지 않는 메소드는?
다음처럼 username 을 가지고 Member 를 조회하는 기능이 필요하다고 하자.
인터페이스에 새로운 메소드를 정의했다.
public interface MemberRepository extends JpaRepository<Member, Long> {
// username 으로 Member 객체 조회하기
Optional<Member> findByUsername(String name);
}
구체 클래스를 작성하면?
public class MemberRepositoryImpl implements MemberRepository {
@Override
public List<Member> findByUsername(String username) {
return em.createQuery("SELECT m FROM Member m WHERE m.name = :username")
.setParameter("username", username)
.getResultList();
}
@Override
public void flush() {
}
@Override
public <S extends Member> S saveAndFlush(S entity) {
return null;
}
@Override
public <S extends Member> List<S> saveAllAndFlush(Iterable<S> entities) {
return List.of();
}
// ..
}
구체 클래스로 MemberRepository 인터페이스를 구현하기위해 모든 메소드를 Override 해야한다.
배보다 배꼽이 커진 것이다.
개발자는 findByUsername 추가 하고싶다.
Member 객체를 username 으로 조회하는 것은 도메인에 특화된기능이다.
JpaRepository 가 제공하지 않는 도메인에 특화된 메소드,
어떻게 추가해야할까?
간단한 메소드는 구현이 필요하지 않다!
Query Method: 메소드명으로 쿼리를 자동 생성해준다.
findByUsername 같은 메소드는 자동으로 JpaRepository 가 생성해준다.
그것도 메소드명을 읽어서 자동으로!
AS-IS
앞선 예제에서 메소드를 일일히 정의했었다.
public class MemberRepositoryImpl implements JpaRepository<Member, Long> {
public List<Member> findByUsername(String username) {
return em.createQuery("SELECT m FROM Member m WHERE m.name = :username")
.setParameter("username", username)
.getResultList();
}
// ..
}
TO-BE
Spring Data Jpa 가 메소드 이름을보고
쿼리 자동 생성해준다.
Repository 인터페이스에 메소드를 정의하기만 하면 된다.
ex) findByUsername
public interface MemberRepository extends JpaRepository<Member, Long> {
// 이게 끝이다!
List<Member> findByUsername(String name);
}
JPA Query Method 규칙에 따라 자동으로 쿼리를 생성한다.
Query method 문법은 다음 문서를 참조하면 된다.
Tip. 필드명 변경시 메소드명도 반드시 변경해야한다.
ApplicationContext 로딩 단계에서 field 명 변경에 대해 에러를 잡아준다.
-> 로딩 단계에서 에러를 빠르게 잡을 수 있다는 것이 장점이다.
다음은 Member class field 의 `name` 을 `username` 으로 바꾸고
@Table(name = "member")
@Entity @Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false, name = "member_id")
private Long id;
@Column(nullable = true, name = "member_name", length = 31)
private String name; // username -> name
// ..
}
public interface MemberRepository extends JpaRepository<Member, Long> {
// 메소드명은 여전히 `username` 을 참조한다.
List<Member> findByUsername(String username);
}
@Column(name) 값이 아닌 Entity class property 명 (위 예제에선 `name` ) 을 따른다.
Repository findByUsername 은 그대로 뒀을 때 발생하는 에러다.
Caused by:
org.springframework.data.mapping.PropertyReferenceException:
No property username found for type Member! Did you mean 'name'?
위 에러를 빨리 발견함으로써 Repository 인터페이스 메소드명도 변경 (findByUsername -> findByName) 해야 함을 빠르게 인지할 수있다.
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByName(String username);
}
빠른 에러가 더 좋은 에러다!
@Query: 수동 쿼리 정의
Query Method 는 메소드명에 의한 자동 쿼리 생성을 지원한다.
@Query 어노테이션은 메소드에 수동 쿼리 매핑을 지원한다.
사용법: jpql 쿼리 작성
public interface MemberRepository extends JpaRepository<Member, Long> {
// This is like an anonymous named-query
// NamedQuery supports static query parsing on application loading
@Query("SELECT m FROM Member m WHERE m.username = :username AND m.age = :age")
List<Member> findUser(String username, int age);
// @Query also supports retrieving VO (String)
@Query("SELECT m.username from Member m")
List<String> findUsernames();
}
@Query 장점1: 메소드명이 간단해진다.
Query Method 는 정해진 규칙에 따라 메소드를 정의해야한다.
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAge(String username, int age);
}
반면에 @Query 는 메소드명을 자유롭게 네이밍할 수 있다.
즉, 더 짧고 간결한 네이밍을 지원한다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("SELECT m FROM Member m WHERE m.username = :username AND m.age = :age")
List<Member> findUser(String username, int age);
}
@Query 장점2: 잘못된 쿼리를 빠르게 잡는다.
컬럼명을 "inavalid_column_name"로 잘못주면 어떻게 동작할까?
@Query("SELECT m FROM Member m WHERE m.inavalid_column_name = :username AND m.age = :age")
List<Member> findUser(@Param("username") String username, @Param("age") int age);
ApplicationContext 로딩 시점에 바로 Exception 을 발생시킨다.
Caused by:
org.hibernate.query.sqm.PathElementException:
Could not resolve attribute 'invalid_column_name' of 'com.example.entity.member.Member'
어째서 이런 것일까?
@Query 메소드로 정의한 쿼리는 애플리케이션 초기화 단계에서 Static Query 로 미리 Parsing 한다.
Static Query 를 Parsing 하는 과정에서 문법 오류가 발생하는지 하지 않는지를 미리 알 수 있다.
Conclusion
아주 간단한 쿼리는 Query Method
메소드명이 길어지거나 쿼리가 복잡해진다면 @Query 를 사용하라.
'JVM > Spring' 카테고리의 다른 글
[Spring] 환경 분리 방법 (2) | 2023.10.29 |
---|---|
[Spring] Servlet Container, Servlet (0) | 2023.03.19 |
[Spring] logback-spring.xml (0) | 2023.02.21 |
[Spring] ConfigurationProperties + ConfigurationPropertiesScan (0) | 2023.02.13 |
[SpringBoot] Naming methods in each layer (0) | 2023.02.09 |