Hello

JPA 정적쿼리, 동적쿼리(feat. JpaSpecificationExecutor)

by 볼빵빵오춘기

정적쿼리(JPA문법 사용)

  • 정적 쿼리는 조건이 고정된 쿼리로, 쿼리 메서드를 통해 정의된다.
  • 정적쿼리 사용 방법은 단순하고 직관적이지만, 조건이 고정되어 있어 동적인 요구사항을 처리하기 어렵다.
  • 모든 조합의 메서드를 만들어야 하므로, 조건이 많아지면 관리가 복잡해진다.
public interface ProtectRepository extends JpaRepository<ProtectEntity, Long> {
    Page<ProtectEntity> findByKindAndGenderAndDate(String kind, String gender, String date, Pageable pageable);
}

 

동적쿼리(JpaSpecificationExecutor 사용)

  • 동적 쿼리는 조건을 런타임에 결정할 수 있으며, Specification 인터페이스를 사용하여 정의된다.
  • 여러 조건을 조합하거나 조건을 선택적으로 적용할 수 있어 유연하다.
  • 동적쿼리 사용 방법은 조건이 많고 다양하게 조합되어야 하는 경우에 유리하므로 코드를 재사용할 수 있어 유지보수가 용이하다.
public interface ProtectRepository extends JpaRepository<ProtectEntity, Long>, JpaSpecificationExecutor<ProtectEntity> {
}

 

정적쿼리, 동적쿼리 장단점

  정적쿼리 동적쿼리
장점 - 단순함: 조건이 몇 개 없고, 조합이 많지 않을 때는 직관적으로 작성할 수 있다.
- 명시성: 어떤 쿼리가 실행되는지 명확하게 알 수 있다.
- 유연성: 여러 조건을 동적으로 조합할 수 있다.
- 재사용성: 조건을 정의하는 코드의 재사용성이 높다.
- 유지보수 용이: 새로운 조건이 추가되더라도 기존 코드를 크게 변경하지 않고 추가할 수 있다.
단점 - 유연성 부족: 조건이 많아지면 메서드의 조합이 기하급수적으로 늘어날 수 있다.
- 유지보수 어려움: 조건이 추가되거나 변경될 때마다 새로운 메서드를 추가해야 한다.
- 복잡도: 처음 사용 시 이해하고 설정하는 과정이 조금 더 복잡할 수 있다.
- 추상화: 쿼리가 추상화되어 있어, 어떤 SQL이 실행되는지 바로 알기 어려울 수 있다.
결론 조건이 단순하고 고정된 경우, 메서드 명만으로 충분히 표현 가능한 경우에 적합하다. 조건이 다양하고, 동적으로 조합해야 하는 경우, 그리고 유지보수와 확장성이 중요한 경우에 적합하다.

 

JpaSpecificationExecutor

  • Spring Data JPA에서 제공하는 인터페이스로, JPA Criteria API를 사용하여 동적 쿼리를 생성할 수 있게 해준다.
  • 이 인터페이스를 사용하면 복잡한 검색 조건을 쉽게 정의하고, 동적으로 쿼리를 생성하여 실행할 수 있다.
  • 주요 기능으로는 동적쿼리를 생성하고 검색 조건 쿼리를 작성할 때 유연하다.

 

동적 쿼리 사용방법

엔티티 클래스

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String firstName;
    private String lastName;
    private int age;

    // getters and setters
}

 

리포지토리 인터페이스

JpaSpecificationExecutor 인터페이스를 상속받는 리포지토리를 정의한다.

public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}

 

스펙 정의

Specification을 사용하여 동적 쿼리를 정의한다.

public class UserSpecifications {

    public static Specification<User> hasFirstName(String firstName) {
        return (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            return cb.equal(root.get("firstName"), firstName);
        };
    }

    public static Specification<User> hasLastName(String lastName) {
        return (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            return cb.equal(root.get("lastName"), lastName);
        };
    }

    public static Specification<User> isOlderThan(int age) {
        return (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            return cb.greaterThan(root.get("age"), age);
        };
    }
}

 

서비스 클래스

서비스 클래스에서 JpaSpecificationExecutor를 사용하여 동적 쿼리를 실행한다.

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public List<User> findUsers(String firstName, String lastName, int age) {
        Specification<User> spec = Specification.where(null);

        if (firstName != null) {
            spec = spec.and(UserSpecifications.hasFirstName(firstName));
        }

        if (lastName != null) {
            spec = spec.and(UserSpecifications.hasLastName(lastName));
        }

        if (age > 0) {
            spec = spec.and(UserSpecifications.isOlderThan(age));
        }

        return userRepository.findAll(spec);
    }
}

 

정리

  • 두 방법을 상황에 맞게 혼용하여 사용해야할 것 같다.
  • 조건이 많고 복잡한 경우 JpaSpecificationExecutor를 사용하고, 조건이 단순한 경우 JPA의 정적 쿼리를 사용하면 될 것 같다.
  • 프로젝트의 요구사항과 복잡도에 따라 적절한 방법을 선택하면 될 듯 하다.
  • 간단하게 동적쿼리를 사용해 보긴 했는데 처음 쓰는거라 그런지 정신은 없다. 

블로그의 정보

Hello 춘기's world

볼빵빵오춘기

활동하기