Spring-data-JPA [4] Update와 @Query
1. @Query
Repository의 메서드 위에 선엄함으로써 sql 쿼리를 작성할 수 있습니다. 명명법으로 해결하기 힘든 쿼리를 사용할 때 유용합니다.
1-1. 기본 사용법
메서드 위에 @Query(sql)을 해주시면 됩니다.
@Query("select m from Member m")
List<Member> findMemberBy();
- @Query 안에 들어가는 테이블은 별칭을 부여해서 사용할 수 있습니다.
- Member 객체를 m이라는 별칭을 이용했습니다.
@Test
@DisplayName("@Query 사용 basic")
void selectAll(){
List<Member> members = createMember();
for(Member member: members){
memberRepository.save(member);
}
List<Member> memberList = memberRepository.findMemberBy();
for(Member member: memberList){
System.out.println(member);
}
assertThat(memberList.size()).isEqualTo(4);
}
1-2. 인자 넘기기
메서드로 넘어온 인자들을 쿼리로 넘겨주는 방법은 총 3가지가 존재합니다.
Name과 Age를 넘기는 방법을 알아보겠습니다.
1. 메서드 인자 순서
@Query("select m from Member m where m.name = ?1 and m.age = ?2")
Optional<Member> findMemberToSequenceByNameAndAge(String name, int age);
2. 메서드 인자 이름 그대로 적용
@Query("select m from Member m where m.name = :name and m.age = :age")
Optional<Member> findMemberToTargetNotDesignateByNameAndAge(String name, int age);
3. 메서드 인자 이름 변경하여 적용
@Query("select m from Member m where m.name = :name and m.age = :age")
Optional<Member> findMemberToTargetDesignateByNameAndAge(@Param("name") String n, @Param("age") int a);
2. Update
find와 delete가 있으니 Update도 명명법으로 해결할 수 있을 것 같습니다. 하지만 Update는 명명법으로 지원하지 않습니다. 그렇기에 @Query를 사용하는 방법이 있습니다.
@Query("update Member m set m.age=m.age+1 where m.age>:age")
int bulkByAgeGreaterThanPlus(int age);
인자로 들어온 age보다 나이가 큰 Member들에 대해서 나이를 한 살 증가시킵니다.
@Test
@DisplayName("update 사용 @Modifying 없을 때 오류 발생")
void updateNotModifying(){
List<Member> members = createMember();
for(Member member: members){
memberRepository.save(member);
}
assertThatThrownBy(() -> memberRepository.bulkByAgeGreaterThanPlus(11))
.isInstanceOf(InvalidDataAccessApiUsageException.class);
}
하지만 이렇게 작성한다면 오류가 발생합니다. 그 이유는 Dirty Checking이 아닌 여러 건의 벌크 연산을 수행할 경우 영속성 콘텍스트에 있는 1차 캐시를 사용하지 않고 DB에 직접 쿼리를 날리기 때문에 예외를 발생시킵니다.
예외 발생을 막기 위해선 @Modifying을 붙여줘야 합니다.
@Modifying
@Query("update Member m set m.age=m.age+1 where m.age>:age")
int bulkModifyingByAgeGreaterThanPlus(int age);
그리고 또 해줘야 할 부분이 있습니다. 위에서 말씀드렸다시피 1차 캐시를 사용하지 않고 DB에 직접 쿼리를 날리기 때문에 1차캐시를 비워줘야 합니다. 안 비워줬을 경우 다음과 같이 1차캐시와 DB가 동기화 되지 않은 모습을 볼 수 있습니다.
@Test
@DisplayName("update 사용 @Modifying 있을 때 속성 x")
void updateModifyingButNotAttribute(){
List<Member> members = createMember();
for(Member member: members){
memberRepository.save(member);
}
// m1 나이는 12살이며, findAll()을 통해서 1차캐시에 Member들의 Entity존재
List<Member> all = memberRepository.findAll();
// DB에 직접 쿼리를 날려 m1의 나이를 13살로 증가
int i = memberRepository.bulkModifyingByAgeGreaterThanPlus(11);
// 1차캐시에는 m1에 대한 정보가있으므로 1차캐시에서 가져옴
Member result = memberRepository.findByName("m1").get();
// DB에는 13살이지만, 1차캐시에는 12살이므로 테스트를 통과함
assertThat(result.getAge()).isEqualTo(12);
}
따라서 1차 캐시를 비워주는 설정을 부여해야합니다. 그것을 @Modifying으로 할 수 있습니다.
bulk연산이 실행된 후 1차캐시를 비워주는 속성인 clearAutomatically를 true로 설정해주시면 됩니다. default는 false입니다.
@Modifying(clearAutomatically = true)
@Query("update Member m set m.age=m.age+1 where m.age=:age")
int bulkModifyingAttributeByAgeGreaterThanPlus(int age);
@Test
@DisplayName("update 사용 @Modifying 있을 때 속성 o")
void updateModifyingAndAttribute(){
List<Member> members = createMember();
for(Member member: members){
memberRepository.save(member);
}
List<Member> all = memberRepository.findAll();
int i = memberRepository.bulkModifyingAttributeByAgeGreaterThanPlus(11);
Member result = memberRepository.findByName("m1").get();
assertThat(result.getAge()).isEqualTo(13);
}
테스트가 성공한 것을 볼 수 있습니다.
정리하기
1. 명명법으로 복잡한 쿼리를 @Query로 해결할 수 있고 인자로 넘기는 방법을 알아봤습니다.
1-1. 메서드 인자 순서
1-2. 메서드 인자 이름 그대로 적용
1-33. 메서드 인자 이름 변경하여 적용
2. Update쿼리에 대해 알아봤고, @Modifying의 필요성에 대해서 알아봤습니다.
2-1. Update 쿼리는 1차캐시를 거치지 않고 DB에 직접 쿼리를 날린다.
2-2. 따라서 Update 쿼리를 날리기 위해선 @Modifying이 동반돼야 한다.
2-3. Update 쿼리 수행 후 1차 캐시와 DB의 동기화를 위해 1차 캐시를 다 비워줘야 한다.
2-4. 이때 @Modifying의 clearAutomactically = true 속성을 줌으로써 편리하게 할 수 있다.
'SpringBoot > JPA' 카테고리의 다른 글
Spring-Data-JPA [6] Index 적용하기 (3) | 2022.05.16 |
---|---|
Spring-Data-JPA [5] Fetch Join (0) | 2022.03.24 |
Spring-data-JPA [3] 명명법 (0) | 2022.03.22 |
Spring-data-JPA(2) 개념 (0) | 2022.01.06 |
Spring-data-JPA(1) [엔티티] Entity (0) | 2022.01.06 |
댓글
이 글 공유하기
다른 글
-
Spring-Data-JPA [6] Index 적용하기
Spring-Data-JPA [6] Index 적용하기
2022.05.16 -
Spring-Data-JPA [5] Fetch Join
Spring-Data-JPA [5] Fetch Join
2022.03.24 -
Spring-data-JPA [3] 명명법
Spring-data-JPA [3] 명명법
2022.03.22 -
Spring-data-JPA(2) 개념
Spring-data-JPA(2) 개념
2022.01.06