JpaRepository를 확인해보면 PagingAndSortingRepository 외에 QueryByExampleExecutor를 상속받고 있는것을 볼 수 있다.

 

QueryByExampleExecutor는 흔히 QBE로 축약해서 부르기도 한다고 한다.

Entity를 Example로 만들고 Matcher를 통해 필요한 쿼리들을 만드는 방법이다.

 

이전 포스팅과 동일한 환경에서 진행했다.

  • Intelli J
  • SpringBoot 2.6.2
  • Lombok
  • Gradle 7.3.2

 

Jpa Docs에서는 QueryByExample에 대해 간단한 인터페이스를 사용하는 사용자 친화적인 쿼리기술이라고 하고 있다.

동적 쿼리 생성이 가능하며 필드 이름이 포함된 쿼리를 작성할 필요가 없다고 한다.

 

QBE는 세부분으로 구성되는데 probe, ExampleMatcher, Example로 구성된다.

 

probe는 도메인 개체의 실제 예시. Entity가 된다. Entity라고 해서 실제 Entity를 의미하진 않고 가짜 Entity라고 볼 수 있다.

 

ExampleMatcher는 특정 필드를 일치시키는 방법에 대한 세부 정보를 전달한다.

 

Example은 probe와 ExampleMatcher로 구성되어있고 사용할 query를 생성하는 역할을 한다.

 

@Data
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Entity
public class User{
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @NonNull
    private String name;
    
    @NonNull
    private String email;
}
public interface UserRepository extends JpaRepository<User, Long>{

}
import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.endsWith;
//endsWith()는 static으로 import 해준다.

@SpringBootTest
class UserRepositoryTest{

    @Autowired
    private UserRepository userRepository;
    
    @Test
    void crudTest(){
        
        ExampleMatcher matcher = ExampleMatcher.matching()
                .withIgnorePaths("name") //매칭할때 name은 매칭을 하지 않겠다는 것.
                .withMatcher("email", endsWith()); //withMatcher는 "email"을 통해 확인하겠다는것.
                
                
        Example<User> example = Example.of(new User("aaaa", "coco@gmail.com"), matcher);
        /*
            Example.of(probe, matcher); 형태로 구성.
        */
        
        userRepository.findAll(example).forEach(System.out::println);
}

 

주석부분에 설명했듯이 withIgnorePaths로 매칭하지 않을 필드를 명시하고 withMatcher로 매칭할 필드를 명시한다.

 

그럼 실제로 데이터에 aaaa라는 이름을 갖고 있는 데이터는 존재하지 않지만 coco@gmail.com 이라는 이메일을 갖고 있는 데이터는 존재하기 때문에 해당 데이터를 조회해 출력해주게 된다.

 

example은 예제 이전에 설명했듯이 probe와 matcher로 구성되어 있다.

Example.of를 보면 Example.of(T probe) 와 Example.of(T probe, ExampleMatcher matcher) 두가지를 볼 수 있다.

 

그래서 of 의 첫 부분인 probe 부분에 new User로 엔티티를 넣어주고 Probe를 User로 넣어줬으니 Example의 타입에도

probe의 타입과 동일한 User를 넣어주면 된다.

그리고 matcher를 넣어주면 된다.

 

실행해서 보면

이러한 쿼리를 처리한것을 볼 수 있다.

probe에는 name과 email이 존재하지만 ignore로 name을 제외하도록 했기 때문에 email만을 통해 조회한다.

 

그럼 ignore를 사용하지 않는다면?

 

이렇게 쿼리를 실행한다.

여기에서 차이를 보면 matcher에 명시한 email은 like를 통해 조회하지만 name의 경우는 like가 아닌 exact match로 조회한다.

즉, email은 부분적으로 일치하는 데이터를 찾을 수 있는 like를 사용하지만 matcher에 명시하지 않은 name은 완전 일치해야 하는 형태로 조회한다는것.

 

이 예제를 보면 probe에서 담고 있는 name, email을 모두 포함해 쿼리를 생성할 수 있지만 matcher의 ignore를 통해 원하는 필드를 제외하고 쿼리 실행이 가능하다는것을 알 수 있다.

 

하지만 실제로 이렇게 사용할 일은 없고 보통은 set을 통해 넣어주는 경우가 많기 때문에 아래와 같은 예제도 추가한다.

 

import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.endsWith;
//endsWith()는 static으로 import 해준다.

@SpringBootTest
class UserRepositoryTest{

    @Autowired
    private UserRepository userRepository;
    
    @Test
    void crudTest(){
        
        User user = new User();
        user.setName("aaaa");
        user.setEmail("coco@gmail.com");
        
        
        ExampleMatcher matcher = ExampleMatcher.matching()
                .withIgnorePaths("name")
                .withMatcher("email", endsWith());
                
                
        Example<User> example = Example.of(user, matcher);
        
        userRepository.findAll(example).forEach(System.out::println);
}

 

이렇게 default Constructor로 생성하고 set을 통해 넣어준 다음 처리한다.

 

 

하지만 Example은 문자열에 관련된 것만 쓸 수 있는 등 한계점이 있어서 조금 복잡한 쿼리문을 만들기에는 적합하지 않아 Example을 사용하기보다는 QueryDSL을 사용한다고 한다.

 

Reference

  • 패스트캠퍼스 java/spring 초격차 패키지 Spring Data JPA

 

  • Jpa docs

 

 

Spring Data JPA - Reference Documentation

Example 109. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del

docs.spring.io

 

+ Recent posts