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
댓글을 사용할 수 없습니다.