- 데이터베이스를 쉽게 다루기 위한 ‘데이터 액세스 기술’로 ORM(Object-Relational Mapping) 기법을 사용하여 자바 애플리케이션에서 사용하는 객체와 관계형 데이터베이스 사이의 매핑을 관리하는 ORM 기술에 대한 API 표준 명세서(인터페이스) 의미합니다.
- 이 API를 사용하여 개발자가 직접적인 SQL을 작성하지 않고도 데이터베이스에서 데이터를 저장, 업데이트, 삭제, 조회하는 등의 작업을 수행할 수 있게 해 줍니다. - JPA는 표준화된 API를 제공함으로써, 다양한 ORM 프레임워크(예: Hibernate, EclipseLink, OpenJPA 등)와의 호환성을 보장합니다. 이로 인해 개발자는 특정 ORM 프레임워크에 종속되지 않고 필요에 따라 다른 프레임워크로 쉽게 전환할 수 있습니다.
💡 JpaRepository - Spring Data JPA에서 제공하는 기능 중 하나로 개발자가 데이터베이스와의 CRUD(Create, Read, Update, Delete) 연산을 더욱 쉽게 처리할 수 있도록 도와주는 인터페이스입니다. - JpaRepository 인터페이스를 상속받은 인터페이스를 만들면, Spring Data JPA가 자동으로 해당 인터페이스의 구현체를 만들어서 Bean으로 등록해 줍니다. - 이를 통해 개발자는 SQL 쿼리를 직접 작성하지 않고도 메서드 이름만으로 데이터베이스 연산을 수행할 수 있게 됩니다.
- 하단의 ORM 내의 JpaRepository의 구조를 확인해 보면 Repository, CRUDRepository, PagingAndSortingRepository 인터페이스로부터 상속을 받습니다. - 그렇기에 JpaRepository 인터페이스를 사용하면 각각의 상속받은 기능을 모두 이용할 수 있습니다.
💡 Query Method 활용 예시 - 이후에 나오는 Query Method를 통해서 ‘메서드 명’으로 쿼리문을 수행하는 방법으로 활용이 가능합니다.
@RepositorypublicinterfaceUserJpaRepositoryextendsJpaRepository<UserEntity, Long> {
// 메서드 명을 기준으로 사용자 아이디 기준으로 정렬하여 조회합니다.
List<UserEntity> findByOrderByUserId(long userId);
// 메서드 명을 기준으로 사용자 이름을 기준으로 조회합니다.
List<UserEntity> findByUserNm(String userNm);
}
1.3. @Query 활용 예시
💡 @Query 활용 예시 - 이후에 나오는 @Query 어노테이션을 통해 JPQL을 이용한 엔티티 접근 방식 또는 실제 SQL문을 작성하는 nativeQuery 방법을 통해 구성이 가능합니다.
💡 Query Method - Spring Data JPA에서 제공하는 기능 중 하나로 ‘메서드 이름’을 통해 SQL 쿼리를 생성하고 실행하는 기능을 의미합니다.
- 개발자는 SQL 쿼리를 직접 작성하지 않고도 메서드 이름만으로 간단한 조회 쿼리를 수행할 수 있습니다. - 복잡한 쿼리의 경우에는 @Query 어노테이션을 사용하여 JPQL(Java Persistence Query Language) 또는 SQL을 직접 작성할 수 있습니다
💡 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'라고 지을 수 있습니다.
@RepositorypublicinterfaceUserRepositoryextendsJpaRepository<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();
}
💡 @Query - 개발자가 직접 JPQL(Java Persistence Query Language) 또는 SQL 쿼리를 작성할 수 있게 해 줍니다. 이는 복잡한 쿼리를 수행해야 하는 경우나, Spring Data JPA에서 제공하는 기본 메서드로 처리할 수 없는 상황에서 유용하게 사용됩니다. - @Query 어노테이션은 JpaRepository를 상속받은 인터페이스의 메서드 위에 선언하며, 괄호 안에 직접 쿼리를 작성합니다. - 쿼리를 작성하는 방법에는 JQPL을 이용하거나 SQL문을 사용하는 방법이 있습니다. - JPQL을 사용하는 경우 엔티티 클래스와 그 멤버 변수를 직접 참조하여 쿼리를 작성하며 SQL문으로 사용하려면 @Query 어노테이션에 nativeQuery 속성을 true로 설정하여 작성합니다.
@RepositorypublicinterfaceUserRepositoryextendsJpaRepository<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의 메서드 이름을 통한 쿼리 생성 규칙만으로는 처리할 수 없는 상황에 유연하게 대응할 수 있습니다.
- @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가 정의된 리포지토리 메서드의 파라미터로 전달하는 것은 가능합니다.
@RepositorypublicinterfaceUserJpaRepositoryextendsJpaRepository<UserEntity, Long> {
@Query("SELECT u FROM UserEntity u WHERE u.userId = :#{#userDto.userId}")
UserEntity selectUser(UserDto userDto);
}
💡 @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);
- JPA에서 ‘미리 정해 놓은 쿼리’를 ‘이름’을 통해서 사용할 수 있는 기능을 의미하며 사전에 정의해 두었기에 ‘정적 Query’라고도 합니다.
- 애플리케이션 로딩 시점에 파싱 되어 캐싱되므로, 실행 시점에는 파싱 비용이 없어져서 실행 속도가 빠릅니다. - 이 쿼리는 엔티티 클래스 위에 @NamedQuery 어노테이션을 사용하여 정의할 수 있으며, 여러 개의 Named Query를 정의하려면 @NamedQueries 어노테이션을 사용할 수 있습니다. - 각각의 Named Query는 고유한 이름을 가지며, 이 이름을 통해 Repository에서 메서드를 호출할 수 있습니다.
[ 더 알아보기 ] 💡정적 쿼리와 동적 쿼리에는 뭐가 있고 어떤 차이점이 있을까?
- 정적 쿼리의 경우 애플리케이션 로딩 시점에 쿼리를 정의하고 그 이후에는 변경하지 않는 쿼리를 의미합니다. - 이들은 애플리케이션 로딩 시점에 문법을 체크하고 파싱 하여 캐싱하기 때문에, 실행 시점에는 파싱 비용이 발생하지 않아 성능이 좋습니다. 그러나 정적 쿼리는 애플리케이션 로딩 시점에 정의되므로, 이후에 쿼리를 동적으로 변경할 수 없다는 단점이 있습니다. - 정적 쿼리의 종류에는 JPQL, SQL, NamedQuery 등이 있습니다.
- 동적 쿼리의 경우 실행 시점에 쿼리를 생성하고 변경할 수 있는 쿼리를 의미합니다. 이들은 쿼리를 프로그래밍적으로 생성하고 변경할 수 있어 유연성이 뛰어나지만, 실행 시점에 쿼리를 생성하고 파싱 하므로 정적 쿼리보다 성능이 조금 떨어질 수 있습니다. 그러나 동적 쿼리는 복잡한 조건이나 동적인 쿼리가 필요한 경우에 유용하게 사용됩니다. - 동적 쿼리의 종류에는 Criteria API, QueryDSL 등이 있습니다.
- 엔티티 클래스 내에서 @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")
})publicclassUserEntityimplementsSerializable {
@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "user_id")privatelong userId;
@Column(name = "user_nm")private String userNm;
}