반응형
해당 글에서는 JpaReposiory 인터페이스를 활용한 Query Method, @Query, NamedQuery 방법에 대해 활용하는 방법에 대해 알아봅니다.
💡 [참고] JPA 관련해서 구성 내용에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
분류 | 링크 |
Spring Boot Data JPA -1: ORM, JPA, Hibernate, QueryDSL 이론 | https://adjh54.tistory.com/421 |
Spring Boot Data JPA -2: 초기 환경 구성 + JpaRepository 활용 방법 | https://adjh54.tistory.com/422 |
Spring Boot Data JPA -3: 상세 JpaRepository 활용 방법(Query Method, @Query, NamedQuery) | https://adjh54.tistory.com/481 |
Spring Boot Data JPA 엔티티 어노테이션 -1 : 테이블 컬럼 단위 | https://adjh54.tistory.com/466 |
Spring Boot Data JPA 엔티티 어노테이션 -2 : 엔티티(테이블)간의 관계 | https://adjh54.tistory.com/477 |
Spring Boot Data JPA FetchType 이해하기 : 즉시/지연로딩 | https://adjh54.tistory.com/476 |
Spring Boot Data JPA + JPQL 활용 방법 | https://adjh54.tistory.com/479 |
Spring Boot Data JPA + Criteria API 활용 방법 | https://adjh54.tistory.com/483 |
Spring Boot Data JPA + QueryDSL 활용 방법-1 : 정의 및 구성요소 | https://adjh54.tistory.com/484 |
Spring Boot Data JPA + QueryDSL 활용 방법-2 : 초기 환경설정 및 활용예시 | https://adjh54.tistory.com/485 |
1) Spring Boot JPA(Java Persistence API)
💡 Spring Boot JPA(Java Persistence API)
- 데이터베이스를 쉽게 다루기 위한 ‘데이터 액세스 기술’로 ORM(Object-Relational Mapping) 기법을 사용하여 자바 애플리케이션에서 사용하는 객체와 관계형 데이터베이스 사이의 매핑을 관리하는 ORM 기술에 대한 API 표준 명세서(인터페이스) 의미합니다.
- 이 API를 사용하여 개발자가 직접적인 SQL을 작성하지 않고도 데이터베이스에서 데이터를 저장, 업데이트, 삭제, 조회하는 등의 작업을 수행할 수 있게 해 줍니다.
- JPA는 표준화된 API를 제공함으로써, 다양한 ORM 프레임워크(예: Hibernate, EclipseLink, OpenJPA 등)와의 호환성을 보장합니다. 이로 인해 개발자는 특정 ORM 프레임워크에 종속되지 않고 필요에 따라 다른 프레임워크로 쉽게 전환할 수 있습니다.
2) JpaRepository
💡 JpaRepository
- Spring Data JPA에서 제공하는 기능 중 하나로 개발자가 데이터베이스와의 CRUD(Create, Read, Update, Delete) 연산을 더욱 쉽게 처리할 수 있도록 도와주는 인터페이스입니다.
- JpaRepository 인터페이스를 상속받은 인터페이스를 만들면, Spring Data JPA가 자동으로 해당 인터페이스의 구현체를 만들어서 Bean으로 등록해 줍니다.
- 이를 통해 개발자는 SQL 쿼리를 직접 작성하지 않고도 메서드 이름만으로 데이터베이스 연산을 수행할 수 있게 됩니다.
1. ORM 내에서 JpaRepository 구조
💡 ORM 내에서 JpaRepository 구조
- 하단의 ORM 내의 JpaRepository의 구조를 확인해 보면 Repository, CRUDRepository, PagingAndSortingRepository 인터페이스로부터 상속을 받습니다.
- 그렇기에 JpaRepository 인터페이스를 사용하면 각각의 상속받은 기능을 모두 이용할 수 있습니다.
2. JpaRepository 활용방안
💡 JpaRepository 활용방안
- JpaReposiory 기반으로 활용할 수 있는 방안으로는 JpaRepository 기본 메서드, Query Method, @Query를 이용하는 방안으로 활용됩니다.
활용 방안 | 설명 | 예시 |
기본 제공 메서드 | Spring Data JPA에서 제공하는 기본적인 CRUD(Create, Read, Update, Delete) 연산을 처리하도록 도와주는 메서드 | save(), findById(), findAll(), count(), delete() |
Query Method | 메서드 이름을 통해 SQL 쿼리를 생성하고 실행하는 기능 | findByUserNm(String userNm) - UserNm 필드를 기준으로 데이터를 찾는 쿼리를 생성하고 실행 |
@Query | 개발자가 직접 JPQL(Java Persistence Query Language) 또는 SQL 쿼리를 작성할 수 있게 해주는 어노테이션 | @Query("SELECT u FROM User u WHERE u.email = ?1") - 개발자가 직접 작성한 JPQL 쿼리를 실행 |
NamedQuery | JPA에서 미리 정의해둔 쿼리를 이름을 통해 사용할 수 있는 기능. 쿼리를 재사용할 수 있고, 애플리케이션 로딩 시점에 쿼리의 유효성을 검사할 수 있음 | @NamedQuery(name="findUserById", query="SELECT u FROM User u WHERE u.id= :id") - 미리 정의된 NamedQuery를 사용하여 쿼리를 실행 |
1.1. 기본 제공 메서드 활용 예시
💡 기본 제공 메서드
- 'JpaRepository를 상속'받아 구성한 UserJpaRepository를 호출하면 기본 제공 메서드 활용이 가능합니다.
@Repository
public interface UserJpaRepository extends JpaRepository<UserEntity, Long> {
}
@RestController
@RequestMapping("/api/v1/user")
public class UserController {
private final UserJpaRepository userJpaRepository;
public UserController(UserJpaRepository userJpaRepository) {
this.userJpaRepository = userJpaRepository;
}
@PostMapping("/users")
public ResponseEntity<List<UserEntity>> selectUserList(@RequestParam String userNm) {
UserEntity userEntity = new UserEntity();
// userJpaRepository 인터페이스의 기본 메서드를 호출하여 사용합니다.
userJpaRepository.findAll();
userJpaRepository.findById((long) 1);
userJpaRepository.count();
userJpaRepository.save(userEntity);
userJpaRepository.delete(userEntity);
}
}
1.2. Query Method 활용 예시
💡 Query Method 활용 예시
- 이후에 나오는 Query Method를 통해서 ‘메서드 명’으로 쿼리문을 수행하는 방법으로 활용이 가능합니다.
@Repository
public interface UserJpaRepository extends JpaRepository<UserEntity, Long> {
// 메서드 명을 기준으로 사용자 아이디 기준으로 정렬하여 조회합니다.
List<UserEntity> findByOrderByUserId(long userId);
// 메서드 명을 기준으로 사용자 이름을 기준으로 조회합니다.
List<UserEntity> findByUserNm(String userNm);
}
1.3. @Query 활용 예시
💡 @Query 활용 예시
- 이후에 나오는 @Query 어노테이션을 통해 JPQL을 이용한 엔티티 접근 방식 또는 실제 SQL문을 작성하는 nativeQuery 방법을 통해 구성이 가능합니다.
@Repository
public interface UserJpaRepository extends JpaRepository<UserEntity, Long> {
// @Query 활용 + JPQL 활용
@Query(value = "SELECT t1 FROM UserEntity t1 WHERE t1.userId = : userId")
List<UserEntity> findByUserNm(String userNm);
// @Query 활용 + NativeQuery 활용(SQL 쿼리 활용)
@Query(value = "SELECT t1 FROM TB_USER t1 WHERE t1.userId = : userId", nativeQuery = true)
List<UserEntity> selectUserId(@Param("userId") long userId);
}
💡 [참고] JPQL에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
1.4. NamedQuery 활용 예시
💡 NamedQuery 활용 예시
- 이후에 나오는 엔티티 내에 정적으로 선언하는 NamedQuery 어노테이션으로 @Repository의 메서드명을 참조합니다. 해당 참조된 메서드에서는 query 부분을 실행한 반환값을 갖습니다.
@Entity
@Getter
@Table(name = "tb_user")
@NamedQuery(name = "UserEntity.selectUserList", query = "SELECT u FROM UserEntity u WHERE u.userNm = :userNm")
public class UserEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private long userId;
@Column(name = "user_nm")
private String userNm;
}
@Repository
public interface UserJpaRepository extends JpaRepository<UserEntity, Long> {
List<UserEntity> selectUserList(@Param("userNm") String userNm);
}
3) Query Method
💡 Query Method
- Spring Data JPA에서 제공하는 기능 중 하나로 ‘메서드 이름’을 통해 SQL 쿼리를 생성하고 실행하는 기능을 의미합니다.
- 개발자는 SQL 쿼리를 직접 작성하지 않고도 메서드 이름만으로 간단한 조회 쿼리를 수행할 수 있습니다.
- 복잡한 쿼리의 경우에는 @Query 어노테이션을 사용하여 JPQL(Java Persistence Query Language) 또는 SQL을 직접 작성할 수 있습니다
1. Query Method 종류
💡 Query Method 종류
- 아래의 표와 같이 Query Method를 통해서 이름을 구성하여 쿼리를 생성할 수 있습니다.
💡Query Method 일반적인 생성 규칙
1. 조회를 수행합니다.
- 조회를 수행하는 ‘findBy[엔티티 필드명]’형태를 통해서 엔티티 필드명의 조건을 포함하여 조회를 수행합니다.
- 예를 들어, 'UserEntity' 엔티티가 'UserNm'이라는 필드를 가지고 있다면, 해당 필드를 사용하여 데이터를 찾는 Query Method를 'findByUserNm'이라고 지을 수 있습니다.
2. 조건절을 추가합니다.
- 조회를 구성한 뒤 [엔티티 필드명] 다음에 조건절을 추가할 수 있습니다.
- 예를 들어, 특정 이름보다 큰 값을 가진 'UserNm' 필드를 찾으려면 'findByUserNmGreaterThan'이라는 이름을 사용할 수 있습니다.
3. 추가 조건절을 추가합니다.
- 여러 필드에 대한 조건을 동시에 지정하려면, 'And' 또는 'Or'를 사용하여 조건들을 연결할 수 있습니다.
- 예를 들어, 'UserNm'과 'UserAge' 필드를 동시에 고려하는 Query Method는 'findByUserNmAndUserAge'라고 지을 수 있습니다.
4. 정렬을 추가합니다.
- 특정 필드를 기준으로 결과를 정렬하려면, 'OrderBy' 키워드를 사용하고, 이어서 정렬하려는 필드의 이름과 'Asc' 또는 'Desc'를 추가합니다.
- 예를 들면, 'UserNm' 필드를 기준으로 오름차순으로 정렬하는 Query Method는 'findByOrderByUserNmAsc'라고 지을 수 있습니다.
분류 | 메서드 | 설명 | 사용예시 | 쿼리 예시 |
SELECT 절 : 조회 | findAll | 조건에 상관없이 모든 문서를 반환합니다. | findAll | select * from xx |
SELECT 절 : 조회 | findBy | 조건에 맞는 모든 문서를 반환합니다. | findByName | select * from xx where name = ?1 |
SELECT 절 : 조회 | findOne | 조건에 맞는 하나의 문서를 반환합니다. | findOneByName | select * from xx where name = ?1 |
SELECT 절 : 조회 | findById | 주어진 ID에 해당하는 하나의 문서를 반환합니다. | findById | select * from xx where id = ?1 |
SELECT 절 : 조회 | findDesctinct | 중복 없이 조건에 맞는 문서를 반환합니다. | findDistinctByName | select distinct(name) from xx where name = ?1 |
SELECT 절 : 조회 | findTop | 조건에 맞는 상위 한 개의 문서를 반환합니다. | findTopByOrderByIdDesc | select * from xx order by id desc limit 1 |
SELECT 절 : 조회 | countById | 조건에 맞는 문서의 수를 반환합니다. | countDocumentsByName | select count(*) where name = ?1 |
WHERE 절 : 조건절 | Is, Equals | 주어진 값과 동일한 데이터를 찾습니다. | findByFirstname, findByFirstnameIs, findByFirstnameEquals | … where x.firstname = ?1 |
WHERE 절 : 조건절 | Not | 주어진 값과 다른 데이터를 찾습니다. | findByLastnameNot | … where x.lastname <> ?1 |
WHERE 절 : 조건절 | And | 두 개 이상의 조건 모두 충족하는 데이터를 찾습니다. | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
WHERE 절 : 조건절 | Or | 두 개 이상의 조건 중 하나 이상 충족하는 데이터를 찾습니다. | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
WHERE 절 : 조건절 | In | 주어진 범위 안에 있는 데이터를 찾습니다. | findByAgeIn(Collection<Age> ages) | … where x.age in ?1 |
WHERE 절 : 조건절 | NotIn | 주어진 범위 외에 있는 데이터를 찾습니다. | findByAgeNotIn(Collection<Age> ages) | … where x.age not in ?1 |
WHERE 절 : 조건절 | True | 참인 데이터를 찾습니다. | findByActiveTrue() | … where x.active = true |
WHERE 절 : 조건절 | False | 거짓인 데이터를 찾습니다. | findByActiveFalse() | … where x.active = false |
WHERE 절 : 조건절 | IgnoreCase | 하나의 필드에 대해 대소문자를 무시하고 데이터를 찾습니다. | findByFirstnameIgnoreCase | … where UPPER(x.firstname) = UPPER(?1) |
WHERE 절 : 조건절 | AllIgnoreCase | 대소문자를 구분하지 않고, 모든 조건을 충족하는 데이터를 찾습니다. | findByFirstnameAndLastnameAllIgnoreCase | … where UPPER(x.firstname) = UPPER(?1) and UPPER(x.lastname) = UPPER(?2) |
WHERE 절 : 조건절 | (Is)Empty | 비어 있는 데이터를 찾습니다. | findByFirstname(Is)Empty | … where x.firstname is empty |
WHERE 절 : 조건절 | (Is)Null | NULL인 데이터를 찾습니다. | findByAge(Is)Null | … where x.age is null |
WHERE 절 : 조건절 | (Is)NotNull | NULL이 아닌 데이터를 찾습니다. | findByAge(Is)NotNull | … where x.age not null |
WHERE 절 : 조건절 | Like | 주어진 패턴과 일치하는 데이터를 찾습니다. | findByFirstnameLike | … where x.firstname like ?1 |
WHERE 절 : 조건절 | NotLike | 주어진 패턴과 불일치하는 데이터를 찾습니다. | findByFirstnameNotLike | … where x.firstname not like ?1 |
WHERE 절 : 조건절 | StartingWith | 주어진 값으로 시작하는 데이터를 찾습니다. | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
WHERE 절 : 조건절 | EndingWith | 주어진 값으로 끝나는 데이터를 찾습니다. | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
WHERE 절 : 조건절 | Containing | 주어진 값을 포함하는 데이터를 찾습니다. | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
WHERE 절 : 조건절(비교) | Between | 주어진 범위 사이에 있는 데이터를 찾습니다. | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
WHERE 절 : 조건절(비교) | LessThan | 주어진 값보다 작은 데이터를 찾습니다. | findByAgeLessThan | … where x.age < ?1 |
WHERE 절 : 조건절(비교) | LessThanEqual | 주어진 값 이하의 데이터를 찾습니다. | findByAgeLessThanEqual | … where x.age <= ?1 |
WHERE 절 : 조건절(비교) | GreaterThan | 주어진 값보다 큰 데이터를 찾습니다. | findByAgeGreaterThan | … where x.age > ?1 |
WHERE 절 : 조건절(비교) | GreaterThanEqual | 주어진 값 이상의 데이터를 찾습니다. | findByAgeGreaterThanEqual | … where x.age >= ?1 |
WHERE 절 : 조건절(비교) | After | 주어진 시간 이후의 데이터를 찾습니다. | findByStartDateAfter | … where x.startDate > ?1 |
WHERE 절 : 조건절(비교) | Before | 주어진 시간 이전의 데이터를 찾습니다. | findByStartDateBefore | … where x.startDate < ?1 |
정렬 | OrderBy | 주어진 필드로 정렬합니다. | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
정렬 | sort | 반환된 문서를 정렬합니다. | sortByName | … order by name desc |
제한 | limit | 반환할 문서의 최대 수를 설정합니다. | limit | … limit 1 |
💡 Query Method를 활용한 다양한 예시
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 1. 이름이 특정 값인 사용자를 찾습니다.
List<User> findByName(String name);
// 2. 이름이 특정 값이 아닌 사용자를 찾습니다.
List<User> findByNameNot(String name);
// 3. 이름이 특정 값인 사용자를 무시하고 찾습니다.
List<User> findByNameIgnoreCase(String name);
// 4. 이름이 특정 값으로 시작하는 사용자를 찾습니다.
List<User> findByNameStartingWith(String prefix);
// 5. 이름이 특정 값으로 끝나는 사용자를 찾습니다.
List<User> findByNameEndingWith(String suffix);
// 6. 이름에 특정 값이 포함된 사용자를 찾습니다.
List<User> findByNameContaining(String word);
// 7. 나이가 특정 값보다 적은 사용자를 찾습니다.
List<User> findByAgeLessThan(int age);
// 8. 나이가 특정 값보다 많은 사용자를 찾습니다.
List<User> findByAgeGreaterThan(int age);
// 9. 나이가 특정 범위에 속하는 사용자를 찾습니다.
List<User> findByAgeBetween(int start, int end);
// 10. 특정 날짜 이후에 가입한 사용자를 찾습니다.
List<User> findByJoinDateAfter(Date date);
// 11. 특정 날짜 이전에 가입한 사용자를 찾습니다.
List<User> findByJoinDateBefore(Date date);
// 12. 이름을 오름차순으로 정렬합니다.
List<User> findByNameOrderByAsc();
// 13. 이름을 내림차순으로 정렬합니다.
List<User> findByNameOrderByDesc();
// 14. 가입일을 기준으로 최근 사용자를 찾습니다.
User findFirstByOrderByJoinDateDesc();
// 15. 가입일을 기준으로 가장 오래된 사용자를 찾습니다.
User findFirstByOrderByJoinDateAsc();
}
2. Query Method 수행과정
1. PersonRepository 인터페이스는 Repository를 상속받습니다.
@Repository
public interface UserJpaRepository extends JpaRepository<UserEntity, Long> {
// ...
}
2. PersonRepository 인터페이스 내에 Query Method 규칙에 따라 메서드 명을 지정합니다.
💡 PersonRepository 인터페이스 내에 Query Method 규칙에 따라 메서드 명을 지정합니다.
- findByUserNm 사용자 이름을 where 조건으로 주며 리스트를 조회합니다.
@Repository
public interface UserJpaRepository extends JpaRepository<UserEntity, Long> {
List<UserEntity> findByUserNm(String userNm);
}
3. 메서드를 호출하면 쿼리가 수행이 됩니다.
💡 메서드를 호출하면 쿼리가 수행이 됩니다
- [POST] /api/v1/user/users로 호출하였을 시 userJpaRepository.findByUserNm() 메서드가 호출되어 해당 값을 반환합니다.
@RestController
@RequestMapping("/api/v1/user")
public class UserController {
private final UserJpaRepository userJpaRepository;
public UserController(UserJpaRepository userJpaRepository) {
this.userJpaRepository = userJpaRepository;
}
/**
* 사용자 리스트를 조회합니다.
*
* @return
*/
@PostMapping("/users")
public ResponseEntity<List<UserEntity>> selectUserList(@RequestParam String userNm) {
List<UserEntity> userEntityList = userJpaRepository.findByUserNm(userNm);
return new ResponseEntity<>(userEntityList, HttpStatus.OK);
}
}
4. 결과 확인
💡 결과 확인
- 아래와 같은 쿼리가 수행됨을 확인하였습니다.
select
userentity0_.user_id as user_id1_3_,
userentity0_.user_nm as user_nm2_3_
from
tb_user userentity0_
where
userentity0_.user_nm=?;
4) @Query
💡 @Query
- 개발자가 직접 JPQL(Java Persistence Query Language) 또는 SQL 쿼리를 작성할 수 있게 해 줍니다. 이는 복잡한 쿼리를 수행해야 하는 경우나, Spring Data JPA에서 제공하는 기본 메서드로 처리할 수 없는 상황에서 유용하게 사용됩니다.
- @Query 어노테이션은 JpaRepository를 상속받은 인터페이스의 메서드 위에 선언하며, 괄호 안에 직접 쿼리를 작성합니다.
- 쿼리를 작성하는 방법에는 JQPL을 이용하거나 SQL문을 사용하는 방법이 있습니다.
- JPQL을 사용하는 경우 엔티티 클래스와 그 멤버 변수를 직접 참조하여 쿼리를 작성하며 SQL문으로 사용하려면 @Query 어노테이션에 nativeQuery 속성을 true로 설정하여 작성합니다.
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.email = ?1")
User findByEmail(String email);
@Query(value = "SELECT * FROM Users u WHERE u.name = ?1", nativeQuery = true)
User findByName(String name);
}
[ 더 알아보기 ]
💡 JPQL(Java Persistence Query Language)
- SQL을 기반으로 한 객체 모델용 쿼리 언어입니다. SQL과 매우 유사한 형태지만 데이터베이스 ‘테이블과 컬럼’이 아닌 ‘자바 클래스와 변수(객체)’에 작업을 수행한다는 점에서 차이가 있습니다.
- 그렇기에 데이터베이스 테이블을 대상으로 쿼리 하는 것이 아닌 엔티티(객체)를 대상으로 쿼리를 수행합니다.
💡JpaRepository 내에서 @Query로 구성을 하였다면 Query Method를 이용한 방식보다 우선순위가 높은가?
- @Query 어노테이션을 사용하면 해당 메서드에 대해 @Query 안에 작성한 JPQL 혹은 SQL 쿼리문이 실행됩니다.
- 이는 메서드 이름에 정의된 쿼리 생성 규칙을 무시하고 @Query에 명시된 쿼리를 수행하게 됩니다. 이를 통해 복잡한 쿼리를 수행하거나, Spring Data JPA의 메서드 이름을 통한 쿼리 생성 규칙만으로는 처리할 수 없는 상황에 유연하게 대응할 수 있습니다.
1. @Query 속성 / 속성값
속성 | 속성 타입 | 설명 |
countName | String | NamedQuery의 이름을 반환합니다. 이것은 페이지네이션을 사용할 때 실행할 count 쿼리입니다. |
countProjection | String | 페이지네이션을 위해 생성된 count 쿼리의 projection 부분을 정의합니다. |
countQuery | String | 페이지에 대한 총 요소 수를 조회하는 페이지네이션 쿼리에 사용될 특별한 count 쿼리를 정의합니다. |
name | String | 사용할 명명된 쿼리입니다. |
nativeQuery | boolean | 주어진 쿼리가 네이티브 쿼리인지 설정합니다. |
queryRewriter | Class<? extends QueryRewriter> | 쿼리 문자열이 완전히 조립된 후에 쿼리 문자열에 적용해야 하는 QueryRewriter를 정의합니다. |
value | String | 주석이 달린 메서드가 호출될 때 실행할 JPA 쿼리를 정의합니다. |
2. @Query 파라미터/객체 처리 방법
💡 @Query 파라미터/객체 처리 방법
- @Query 어노테이션에서는 JQPL에서 사용하는 파라미터 처리와 동일한 ‘위치 기준 파라미터 바인딩’과 ‘이름 기준 파라미터 바인딩’ 처리 방법을 이용합니다.
2.1. 위치 기준 파라미터 바인딩
💡 위치 기준 파라미터 바인딩
- 쿼리에 ‘?1', ‘?2' 등의 형태로 파라미터를 명시하고, 메서드의 파라미터 순서에 따라 값을 바인딩합니다.
- 예를 들어, 아래와 같이 String email이 첫 번째 위치가 되고 ‘?1’값과 바인딩이 되는 형태입니다.
- 두 번째 예제에서 역시 첫 번째 String email이 첫 번째가 되고 두 번째 String userSt가 두 번째 값이 바인딩되어 쿼리가 수행이 됩니다.
@Query("SELECT u FROM User u WHERE u.email = ?1")
User findByEmail(String email);
@Query("SELECT u FROM User u WHERE u.email = ?1 AND u.userSt = ?2")
User findByEmailAndUserSt(String email);
2.2. 이름 기준 파라미터 바인딩
💡 이름 기준 파라미터 바인딩
- 쿼리에 ':파라미터명' 형태로 파라미터를 명시하고, @Param("파라미터명") 어노테이션을 사용하여 메서드 파라미터와 매핑합니다. 이 방식을 사용하면 파라미터의 순서를 신경 쓸 필요가 없습니다.
@Query("SELECT u FROM User u WHERE u.email = :email AND u.name = :name")
User findByEmailAndName(@Param("email") String email, @Param("name") String name);
@Query("SELECT u FROM User u WHERE u.address.city = :city")
List<User> findByCity(@Param("city") String city);
2.3. 객체를 파라미터로 받는 바인딩
💡 객체를 파라미터로 받는 바인딩
- 객체를 바로 파라미터로 받아 해당 객체의 필드를 쿼리에 바인딩할 수 있습니다.
- 이때는 객체의 필드명을 :#{#객체명.필드명} 형태로 쿼리에 명시하고 @Param("객체명") 어노테이션을 사용하여 객체를 매핑합니다.
@Query("SELECT u FROM User u WHERE u.email = :#{#user.email} AND u.name = :#{#user.name}")
User findByUser(@Param("user") User user);
[ 더 알아보기]
💡 @param 말고 @requestbody 형태의 JSON 데이터를 받아서 처리는 불가능한가?
- 직접적으로 JpaRepository 내에서는 User findByUser(@RequestBody User user); 형태로는 이용이 불가능합니다.
- 그러나 컨트롤러 내에서 @RequestBody를 사용하여 클라이언트로부터 데이터를 받고, 그 데이터를 저장 또는 검색하는데 필요한 변수값을 @Query가 정의된 리포지토리 메서드의 파라미터로 전달하는 것은 가능합니다.
💡 사용예시
- Controller 내에서 클라이언트로부터 @RequestBody로 JSON 객체를 전달받습니다.
- 전달받은 JSON 객체를 Repository로 전달하여 쿼리를 수행합니다.
@PostMapping("/users")
public ResponseEntity<UserEntity> selectUserList(@RequestBody UserDto userDto) {
UserEntity selectUser = userJpaRepository.selectUser(userDto);
return new ResponseEntity<>(selectUser, HttpStatus.OK);
}
@Repository
public interface UserJpaRepository extends JpaRepository<UserEntity, Long> {
@Query("SELECT u FROM UserEntity u WHERE u.userId = :#{#userDto.userId}")
UserEntity selectUser(UserDto userDto);
}
3. @Query nativeQuery 속성
💡 @Query nativeQuery 속성
- @Query 어노테이션에 nativeQuery 속성을 true로 설정하면, SQL을 직접 사용하여 쿼리를 작성할 수 있는 네이티브 쿼리 사용이 가능합니다.
- 해당 방식에서는 JPA가 제공하는 JPQL이 아닌 데이터베이스에 특화된 순수 SQL을 사용해야 할 경우 유용합니다.
- 그러나 네이티브 쿼리를 사용하면 데이터베이스 벤더에 따라 SQL 문법이 다르므로, 이식성에 문제가 생길 수 있습니다. 또한, 엔티티가 아닌 테이블을 직접 참조하므로, 엔티티와 테이블 사이의 매핑 정보 변경에 취약하다는 단점이 있습니다.
💡 Query nativeQuery 속성을 사용한 예시
- 네이티브 쿼리 내에서도 동일하게 파라미터를 바인딩하는 방법을 ‘위치’ 혹은 ‘이름’, ‘객체’ 기준으로 파라미터를 바인딩합니다.
// @Query nativeQuery=true, 위치 기반 파라미터 바인딩
@Query(value = "SELECT * FROM Users u WHERE u.name = ?1", nativeQuery = true)
User findByName(String name);
// @Query nativeQuery=true, 이름 기반 파라미터 바인딩
@Query(value = "SELECT t1 FROM TB_USER t1 WHERE t1.userId = : userId", nativeQuery = true)
List<UserEntity> selectUserId(@Param("userId") long userId);
// @Query nativeQuery=true, 객체 기반 파라미터 바인딩
@Query(value = "SELECT t1 FROM TB_USER t1 WHERE t1.userId = :#{#userDto.userId}", nativeQuery = true)
List<UserEntity> selectUserByUserId(UserDto userDto);
5) NamedQuery
💡 NamedQuery
- JPA에서 ‘미리 정해 놓은 쿼리’를 ‘이름’을 통해서 사용할 수 있는 기능을 의미하며 사전에 정의해 두었기에 ‘정적 Query’라고도 합니다.
- 애플리케이션 로딩 시점에 파싱 되어 캐싱되므로, 실행 시점에는 파싱 비용이 없어져서 실행 속도가 빠릅니다.
- 이 쿼리는 엔티티 클래스 위에 @NamedQuery 어노테이션을 사용하여 정의할 수 있으며, 여러 개의 Named Query를 정의하려면 @NamedQueries 어노테이션을 사용할 수 있습니다.
- 각각의 Named Query는 고유한 이름을 가지며, 이 이름을 통해 Repository에서 메서드를 호출할 수 있습니다.
[ 더 알아보기 ]
💡정적 쿼리와 동적 쿼리에는 뭐가 있고 어떤 차이점이 있을까?
- 정적 쿼리의 경우 애플리케이션 로딩 시점에 쿼리를 정의하고 그 이후에는 변경하지 않는 쿼리를 의미합니다.
- 이들은 애플리케이션 로딩 시점에 문법을 체크하고 파싱 하여 캐싱하기 때문에, 실행 시점에는 파싱 비용이 발생하지 않아 성능이 좋습니다. 그러나 정적 쿼리는 애플리케이션 로딩 시점에 정의되므로, 이후에 쿼리를 동적으로 변경할 수 없다는 단점이 있습니다.
- 정적 쿼리의 종류에는 JPQL, SQL, NamedQuery 등이 있습니다.
- 동적 쿼리의 경우 실행 시점에 쿼리를 생성하고 변경할 수 있는 쿼리를 의미합니다. 이들은 쿼리를 프로그래밍적으로 생성하고 변경할 수 있어 유연성이 뛰어나지만, 실행 시점에 쿼리를 생성하고 파싱 하므로 정적 쿼리보다 성능이 조금 떨어질 수 있습니다. 그러나 동적 쿼리는 복잡한 조건이나 동적인 쿼리가 필요한 경우에 유용하게 사용됩니다.
- 동적 쿼리의 종류에는 Criteria API, QueryDSL 등이 있습니다.
1. @NamedQuery 속성 및 속성값
💡 @NamedQuery 속성 및 속성값
- 엔티티 클래스 상위에 @NamedQuery 어노테이션을 통하여서 쿼리를 지정하는 정적 쿼리로 구성합니다.
속성 | 속성 타입 | 필수 여부 | 설명 |
name | String | 필수 | @NamedQuery의 이름을 지정합니다. 이 이름은 JPQL 쿼리를 실행할 때 사용됩니다. |
query | String | 필수 | 실행할 JPQL 쿼리를 지정합니다. |
2. @NamedQueries 속성 및 속성 값
💡 @NamedQueries 속성 및 속성 값
- 엔티티 클래스 상위에 여러 개의 @NamedQuery 어노테이션을 정의하는 데 사용이 됩니다.
속성 | 속성타입 | 필수여부 | 설명 |
value | NamedQuery[] | 필수 | @NamedQuery를 배열 형태로 지정합니다. |
3. NamedQuery 활용예시
💡 NamedQuery 활용예시 - 엔티티
- 엔티티 클래스 내에서 @NamedQuery 어노테이션을 통해서 단건의 NamedQuery를 ‘UserEntity.selectUserList’로 구성하였습니다.
- 사전에 정의해 두었기에 인터페이스만 호출하면 해당 쿼리가 수행이 됩니다.
@Entity
@Getter
@Table(name = "tb_user")
@NamedQuery(name = "UserEntity.selectUserList", query = "SELECT u FROM UserEntity u WHERE u.userNm = :userNm")
public class UserEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private long userId;
@Column(name = "user_nm")
private String userNm;
}
💡 NamedQuery 활용예시 - 인터페이스
- 사전에 엔티티 내에 정의해 둔 쿼리가 수행이 되기에 이름만 매핑하여서 구성합니다.
@Repository
public interface UserJpaRepository extends JpaRepository<UserEntity, Long> {
List<UserEntity> selectUserList(String userNm);
}
💡 NamedQuery 활용예시 - 결과화면
4. NamedQueries 활용예시
💡 NamedQueries 활용예시 - 엔티티
- 엔티티 클래스 내에서 @NamedQueries 어노테이션을 통해서 다건의 NamedQuery를 ‘UserEntity.selectUserList’와 UserEntity.selectUserDetail로 구성하였습니다.
- 사전에 정의해두었기에 인터페이스만 호출하면 해당 쿼리가 수행이 됩니다.
@Entity
@Getter
@Table(name = "tb_user")
@NamedQueries({
@NamedQuery(name = "UserEntity.selectUserList", query = "SELECT u FROM UserEntity u WHERE u.userNm = :userNm"),
@NamedQuery(name = "UserEntity.selectUserDetail", query = "SELECT u FROM UserEntity u WHERE u.userId = :userId")
})
public class UserEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private long userId;
@Column(name = "user_nm")
private String userNm;
}
💡 NamedQueries 활용예시 - 인터페이스
- 사전에 엔티티 내에 정의해 둔 쿼리가 수행이 되기에 이름만 매핑하여서 구성합니다.
@Repository
public interface UserJpaRepository extends JpaRepository<UserEntity, Long> {
List<UserEntity> selectUserList(String userNm);
UserEntity selectUserDetail(long userId);
}
💡 NamedQueries 활용예시 - 결과화면
6) 총 정리
1. JpaRepository 활용방법 비교
💡 JpaRepository 활용방법 비교
- JpaRepository에서 제공하는 기능인 기본 제공 메서드, Query Method, @Query, NamedQuery에 대해 확인합니다.
메서드 종류 | 설명 | 장점 | 단점 |
기본 제공 메서드 | Spring Data JPA에서 제공하는 기본적인 CRUD(Create, Read, Update, Delete) 연산을 처리하도록 도와주는 메서드 | 기본적인 데이터베이스 작업을 간단하고 쉽게 수행할 수 있음 | 복잡한 쿼리에는 적합하지 않음 |
Query Method | 메서드 이름을 통해 SQL 쿼리를 생성하고 실행하는 기능 | 명명 규칙에 따른 자동 SQL 생성, SQL 문을 직접 작성할 필요 없음 | 단순한 쿼리에만 적합, 복잡한 쿼리는 메서드 이름이 길어지고 혼동을 줄 수 있음 |
@Query | 개발자가 직접 JPQL(Java Persistence Query Language) 또는 SQL 쿼리를 작성할 수 있게 해주는 어노테이션 | 복잡하고 사용자 정의 쿼리를 작성할 수 있는 유연성 | JPQL 또는 SQL에 대한 지식이 필요하며, 구문 오류의 가능성이 있음 |
NamedQuery | JPA에서 미리 정의해둔 쿼리를 이름을 통해 사용할 수 있는 기능. 애플리케이션 로딩 시점에 쿼리의 유효성을 검사할 수 있음 | 쿼리를 재사용할 수 있으며, 초기에 쿼리의 유효성을 검사할 수 있음 | 초기 설정이 필요하며, 쿼리가 미리 정의되어 있어서 유연성이 떨어짐 |
2. JpaRepository, JPQL, Criteria API, Query DSL 비교
💡 JpaRepository, JPQL, Criteria API, Query DSL 비교
- JPA를 다루는 기술들인 JpaRepository, JPQL, Creteria API, Query DSL에 대해 비교해 봅니다.
기술 | 설명 | 장점 | 단점 |
JpaRepository | Spring Data JPA에서 제공하는 인터페이스로 기본적인 CRUD 연산을 제공합니다. | 간단한 CRUD 연산을 쉽게 수행할 수 있습니다. 코드의 양을 줄일 수 있습니다. | 복잡한 쿼리를 작성하기 어렵습니다. |
JPQL | SQL을 기반으로 한 객체 모델용 쿼리 언어. | 객체 중심 쿼리를 작성할 수 있습니다. 테이블이 아닌 엔티티 객체를 대상으로 쿼리를 수행합니다. | SQL에 비해 문법이 제한적입니다. 실행 시점에 오류를 발견합니다. |
Criteria API | 타입 세이프한 쿼리를 작성하는데 사용됩니다. | 동적 쿼리 작성이 가능합니다. 컴파일 시점에 오류를 발견할 수 있습니다. | 복잡하고 장황한 코드를 작성해야 할 수 있습니다. |
Query DSL | SQL과 유사한 문법을 사용하여 타입 세이프한 쿼리를 작성하는데 사용됩니다. | 동적 쿼리 작성이 가능하며, SQL과 유사한 문법으로 익히기 쉽습니다. | 추가적인 라이브러리 의존성이 필요합니다. |
오늘도 감사합니다. 😀
반응형