@Query
Spring Data JPA의 Query Methods는 간단하게 메서드의 이름을 바탕으로 조회하는 데이터의 조건을 지정할 수 있어서 매우 간편하다.
그러나, 이름이 길어진다는 점, 조금 복잡한 조건, 집계함수 등을 활용하기 어렵다는 단점이 있다.
=> 직접 실행할 SQL을 작성하기 위해서 @Query 어노테이션을 활용할 수 있다.
ex) ERD
1. @Query
: 어노테이션으로 MyBatis의 Mapper Annotation처럼 SQL을 직접 작성할 수 있게 해주는 기능.
1) JPQL과 SQL
- JPQL (Java Persistence Query Language)라는 언어를 기본으로 사용. (엔티티와 속성을 바탕으로 동작하는 언어)
- 또는 그냥 SQL을 전달할 수 있다 -> nativeQuery = true로 설정.
- 표현법이 다르다.
- SQL을 사용해서 데이터 조회 시에 조회된 데이터의 컬럼과 엔티티가 잘 일치해야 정상적으로 동작한다.
ex)
// JPQL
@Query("SELECT l from Lecture l where l.startTime<11")
List<Lecture> findLecturesBeforeLunch();
// Native SQL
@Query(value = "select * from lecture where start_time<11",
nativeQuery = true)
List<Lecture> findLecturesBeforeLunchNative();
// Controller
@GetMapping("test-query")
public String testQuery() {
List<Lecture> jpql = lectureRepository.findLecturesBeforeLunch();
List<Lecture> nativeSql = lectureRepository.findLecturesBeforeLunchNative();
for (Lecture l : jpql) {
log.info("{}: {}", l.getName(), l.getStartTime());
}
log.info("==================================");
for (Lecture l : nativeSql) {
log.info("{}: {}", l.getName(), l.getStartTime());
}
//ver2. Stream API 사용도 가능하다.
lectureRepository.findLecturesBeforeLunch().forEach(lecture ->
log.info("{}: {}", lecture.getName(), lecture.getStartTime()));
lectureRepository.findLecturesBeforeLunchNative().forEach(lecture ->
log.info("{}: {}", lecture.getName(), lecture.getStartTime()));
return "done";
}
이렇게 결과값도 잘 나왔다.
2) 매개변수 전달
- ?n(숫자)으로 순서에 맞는 매개변수를 인자로 전달 가능하다. (매개변수는 숫자에 맞게 할당된다)
- 매개변수에 이름을 지정할 수도 있다. (Named Parameter)
:<이름> 설정하고, 인자에 @Param("<이름>")으로 동일하게 통일해주면 된다.
- native sql에도 적용 가능하다.
- IN, NOT IN과 같은 조건에 Collection 전달 가능
// 매개변수 설정
// 순서 매개변수
@Query("select l from Lecture l where l.startTime =?1 and l.endTime=?2")
List<Lecture> findLecturesByTime(
Integer startTime, Integer endTime
);
// Named Parameter
@Query("select l from Lecture l where l.startTime = :start and l.endTime = :end")
List<Lecture> findLecturesByTimeNamed(
@Param("start") Integer startTime,
@Param("end") Integer endTime
);
// native query도 가능
@Query(value = "select * from lecture where start_time=?1 and end_time=?2",
nativeQuery = true)
List<Lecture> findLecturesByTimeNative(Integer startTime, Integer endTime);
// IN, NOT IN과 같은 조건에 Collection 전달 가능
@Query("select l from Lecture l where l.day in :days")
List<Lecture> findByDayIn(
@Param("days")Collection<String> days
);
// Controller
@GetMapping("test-query")
public String testQuery() {
log.info("=====순서 매개변수");
lectureRepository.findLecturesByTime(10, 13).forEach(lecture ->
log.info("{}: {} ~ {}", lecture.getName(), lecture.getStartTime(), lecture.getEndTime()));
log.info("======named parameters");
lectureRepository.findLecturesByTimeNamed(10, 13).forEach(lecture ->
log.info("{}: {}~{}", lecture.getName(), lecture.getStartTime(), lecture.getEndTime()));
log.info("======native sql");
lectureRepository.findLecturesByTimeNative(10, 15).forEach(lecture ->
log.info("{}: {}~{}", lecture.getName(), lecture.getStartTime(), lecture.getEndTime()));
log.info("======in 조건절 Collection");
lectureRepository.findByDayIn(Set.of("mon", "tue")).forEach(lecture ->
log.info("{}: {}", lecture.getName(), lecture.getDay()));
return "done";
}
3) Pageable과 Sort
- Pageable과 Sort를 받을 수도 있다! (메서드 오버로딩도 가능~~~)
- native이면 Pageable만 가능 : 따로 설정을 해주어야 한다. >> countQuery로!
// JPQL Pagination
@Query("select l from Lecture l where l.startTime < 11")
Page<Lecture> findLecturesBeforeLunch(Pageable pageable);
// JPQL Sort
@Query("select l from Lecture l where l.startTime < 11")
List<Lecture> findLecturesBeforeLunch(Sort sort);
// native sql Pagination
@Query(value = "select * from lecture where start_time<11",
countQuery = "select count(*) from Lecture where lecture.start_time<11",
nativeQuery = true)
List<Lecture> findLecturesBeforeLunchNative(Pageable pageable);
// native sql은 Sort 구현 불가.
@GetMapping("test-query")
public String testQuery() {
// Pagination (4개씩 0쪽)
log.info("======Pagination (3개씩 0쪽)");
Page<Lecture> lecturePage = lectureRepository.findLecturesBeforeLunch(
PageRequest.of(0, 3));
lecturePage.stream().forEach(lecture
-> log.info("{}: {}", lecture.getName(), lecture.getStartTime()));
log.info("======Sort id 기준으로 내림차순 정렬");
lectureRepository.findLecturesBeforeLunch(Sort.by(Sort.Direction.DESC, "id"))
.forEach(lecture -> log.info("{}: {}", lecture.getId(), lecture.getStartTime()));
log.info("======native sql Pagination (3개씩 0쪽");
lectureRepository.findLecturesBeforeLunchNative(PageRequest.of(0, 3))
.forEach(lecture -> log.info("{}: {}", lecture.getName(), lecture.getStartTime()));
return "done";
}

4) @Modifying
: 상황에 따라 update, delete를 하고 싶다면 @Query와 함께 @Modifying을 추가해주어야 한다!
ex) 3시간 초과 강의를 찾고, 그 강의들을 모두 3시간짜리로 수정하자.
- 이 때 @Modifying을 붙여주자.
// 3시간 초과 강의 찾기
@Query("select l from Lecture l where (l.endTime-l.startTime)>3")
List<Lecture> tooLongLectures();
// 그 강의들을 시작시간+3으로 바꿔서 3시간짜리로 바꾼다.
@Modifying
@Query("update Lecture l set l.endTime = l.startTime+3 where l.endTime-l.startTime>3")
Integer setLectureMaxHour3();
@Transactional
@GetMapping("test-modifying")
public String modifying() {
log.info("modifying===================");
log.info("lectures over 3 hours() : {}", lectureRepository.tooLongLectures().size());
lectureRepository.setLectureMaxHour3();
log.info("lectures over 3 hours() : {}", lectureRepository.tooLongLectures().size());
return "done";
}
>> 14개에서 update 이후 0개로 바뀌었다!
+ 참고!
- JPQL에서 INSERT는 지원하지 않는다...! JPA를 사용하거나,,,,, nativeQuery를 설정해서 INSERT문을 작성해줘야한다.
// native Query로 insert문 작성하기
@Modifying
@Query(value = "insert into lecture (end_time, start_time, instructor_id, day, name) " +
"VALUES (:endTime, :startTime, :instructorId, :day, :name)",
nativeQuery = true)
void insertLecture(
@Param("endTime") Integer endTime,
@Param("startTime") Integer startTime,
@Param("instructorId") Long instructorId,
@Param("day") String day,
@Param("name") String name
);
// Controller
@Transactional
@GetMapping("test-modifying")
public String modifying() {
lectureRepository.insertLecture(13, 9, 2L, "mon", "sql");
lectureRepository.findLecturesByTime(9, 13).forEach(
lecture -> log.info("{}: {}", lecture.getName(), lecture.getDay())
);
return "done";
}
2. 집계 함수와 Projection
: @Query에 집계함수를 사용할 수 있다.
1) List<Object[]> 반환
기본 반환형은 List<Object[]>이다. >> 활용 시에 여러 불편함을 야기하게 된다. ㅠ ㅠ
public interface InstructorRepository extends JpaRepository<Instructor, Long> {
//집계 함수
@Query("select l.instructor, count(*) " +
"from Lecture l group by l.instructor")
List<Object[]> selectILCountObject();
}
@GetMapping("test-agg")
public String testAggregate() {
// List<Object[]>형태로 받아서
List<Object[]> results = instructorRepository.selectILCountObject();
// 하나하나 Object[]배열을 까서 객체에 형변환하여 넣어주어야 한다...
for (Object[] row : results) {
Instructor instructor = (Instructor) row[0];
Long count = (Long) row[1];
log.info("instructor: {}, count: {}", instructor.getName(), count);
}
return "done";
}
2) 새로운 인스턴스를 만들어서 (Dto)로 결과를 표현하자.
- dto 생성 : 결과 데이터가 가질 모습을 정의해준다. (Instructor 객체와 count(*)의 결과인 Long)
@Getter
@AllArgsConstructor
public class ILCountDto {
private Instructor instructor;
private Long count;
}
- 쿼리문 작성 : 쿼리문 안에서 new 객체를 생성해준다.
** (중요한 것은 패키지 이름을 포함한 전체 클래스 이름을 지정해주어야 한다!!!)
@Query("select new com.example.jpasecond.school.dto.ILCountDto(l.instructor, count(*)) " +
"from Lecture l group by l.instructor")
List<ILCountDto> selectILCountDto();
- 쿼리 메서드 사용 : 메서드 사용은 아까보다 훨씬 낫다..
@GetMapping("test-agg")
public String testAggregate() {
List<ILCountDto> resultDtos = instructorRepository.selectILCountDto();
for (ILCountDto dto : resultDtos) {
log.info("instructor: {}, count: {}",
dto.getInstructor().getName(),
dto.getCount());
}
return "done";
}
3) Projection
- Projection interface 생성 : 안에 get 메서드도 함께 넣어주기.
public interface ILCountProjection {
Instructor getInstructor();
Long getLectureCount();
}
- alias 사용해 쿼리문 작성
@Query("select l.instructor as instructor, count(*) as lectureCount " +
"from Lecture l group by l.instructor")
List<ILCountProjection> selectILCountProjection();
- 쿼리 메서드 사용 : get 메서드 사용.
@GetMapping("test-agg")
public String testAggregate() {
List<ILCountProjection> resultProjections = instructorRepository.selectILCountProjection();
for (ILCountProjection i : resultProjections) {
log.info("instructor: {}, count: {}",
i.getInstructor().getName(),
i.getLectureCount());
}
return "done";
}
'Programming > Spring, SpringBoot' 카테고리의 다른 글
낙관적 락 & 비관적 락 (1) | 2024.02.05 |
---|---|
[JPA] 영속성 컨텍스트 (Persistence Context) (0) | 2024.02.05 |
Relations (M : N 관계) (1) | 2024.02.01 |
[JPA] MappedSuperclass / JpaAuditing (0) | 2024.01.31 |
OAuth2 - Kakao Login 구현 (1) | 2024.01.30 |