⚡ 개요
Spring Boot에서 QueryDsl을 사용하는 방법에는 여러 가지 방법이 존재한다.
내가 가장 많이 사용하는 방식은 queryFactory를 이용하는 방식을 사용한다. 회사에서 개발을 하면서 단일 테이블 기준으로 데이터를 뽑아낼 수 있는 부분이 적고 데이터량이 많다. 이외, 다른 방식들이 있는데 이부분에 대해서 정리를 하면서 잘못 쓰고 있는 부분이 있는지 체크를 해보려고 한다.
⚡ 사용 방법
querydsl을 사용하는 이유부터 생각을 해보자.
만약 spring boot JPA 및 JPQL을 사용해서 쿼리를 구현 한다고 생각해봤을때 단건 조회 또는 적당한 조건에 따른 조회는 크게 문제가 되지 않는다. 하지만 쿼리가 복잡해지고 조건이 많아지는 순간 실수가 발생할 가능성이 존재하고 유지보수에 어려움이 생기게 된다
공식 홈페이지 샘플소스 기준으로 설명을 하도록 하겠다.
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
List<User> findByUserIdAndUserPassAndEmail...(Sting userId, String userPass, String emaill ..);
}
사용자 정보를 조회한다고 했을때 조건에 따라서 메소드명도 길어지고, 조건에 따른 파라미터도 증가하게 된다.
조건이 서로 다른 조회를 한다고 했을때 메소드는 계속해서 추가가 될것이다. JPQL을 사용 한다면 컴파일 단계에서 에러를 찾지도 못하고 실수가 발생할 가능성이 높아지는 코드가 된다.
이제 queryDsl 사용 방법에 대해서 알아보자.
Spring Boot에서 QueryDsl을 사용하는 방법에는 3가지 방법이 있다.
1. Spring Data Jpa Custom Repository
2. QueryRepositorySupport
3. JPAQueryFactory
🖐 Spring Data Jpa Custom Repository
1. 가장 먼저 Entity 클래스를 생성한다.
Entity 클래스는 데이터 베이스의 테이블과 1:1 매핑되는 자바 클래스이며, @Entity 어노테이션을 사용해서 표시한다.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int age;
// 생성자, getter, setter 등 필요한 메서드 작성
}
2. JpaRepository 인터페이스를 생성
Spring Data JPA에서는 JpaRepository 인터페이스를 상속하여 Repository 인터페이스를 생성 한다.
JpaRepository 는 데이터 베이스를 조작 가능한 다양한 메소드를 제공한다.
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
// 추가적인 쿼리 메서드를 정의할 수 있다.
}
3. Custom repository 인터페이스 및 구현 클래스 생성
기존 JpaRepository를 상속받는 Custom repository 인터페이스를 생성한다. 이 인터페이스에는 원하는 비즈니스 로직을 정의하는 메서드를 추가해서 사용한다. 위의 이미지와 같이 구조가 되어있으며 Repository 가 다중 상속을 받고 사용자는 Repository 인터페이스를 DI 받아서 사용한다.
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long>, CustomUserRepository {
// 상속 받는 부분에 Custom 추가
}
// Custom Interface
public interface CustomUserRepository {
List<Person> findByNameAndAge(String name, int age);
}
// Custom Interface 구현 클래스
@Repository
// 해당 어노테이션을 사용해서 스프링에 해당 클래스가 Repository로 사용 되도록 등록한다.
public class CustomUserRepositoryImpl implements CustomUserRepository {
@Override
public List<Person> findByNameAndAge(String name, int age) {
// 구현 작업 진행.
}
}
여기서 주의 사항은 커스텀 구현 클래스의 경우 반드시 Interface 이름 + Impl 로 만들어야 한다는것이다.
JPA에서 Injection을 할때 해당 이름으로 생성된 클래스를 기준으로 객체를 삽입해주기 때문이다.
Custom repository 사용 할때는 Service 단에서 repository를 주입 받아서 사용을 하면 된다.
@Service
public class UserService {
private final CustomUserRepository customUserRepository;
public List<User> findUserInfo(String name, int age) {
// Custom repository의 메서드 사용
return customUserRepository.findByNameAndAge(name, age);
}
}
가장 기본적으로 사용하고 있는 내용이기 때문에 풀어서 설명을 했다.
이 방법에서는 기본적으로 제공하는 find(), save(), delete() 등의 메소드를 사용할수도 있고, 추가적으로 복잡한 쿼리를 작성 할때에는 커스텀 내에서 추가적으로 메소드를 정의해서 사용할수 있다.
하지만 만들어야 하는 클래스 파일이 엄청 많고, 관리가 힘들수도 있다는 단점이 존재한다.
🖐 QueryRepositorySupport
QueryDsl을 사용하는 Custom repository를 더 편리하게 구현하기 위한 Spring Data JPA의 기능 중 하나이다. 이를 사용하면 Custom repository 구현에서 더 복잡한 구현이 가능해진다.
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
public class CustomUserRepositoryImpl extends QuerydslRepositorySupport implements CustomUserRepository {
// ...
}
여기서 QueryRepositorySupport 는 기본 생성자가 없어서 직접 생성자를 작성해야 한다. 또한 상속을 하게 되면서 JPAQueryFactory를 사용할수 있게 된다.
import javax.persistence.EntityManager;
import org.springframework.stereotype.Repository;
@Repository
public class CustomUserRepositoryImpl extends QuerydslRepositorySupport implements CustomUserRepository {
private final JPAQueryFactory queryFactory;
public CustomUserRepositoryImpl(EntityManager entityManager) {
super(Person.class); // Entity Path를 전달.
this.queryFactory = new JPAQueryFactory(entityManager);
}
// Custom repository 메서드 구현
// ...
}
JPAQueryFactory를 사용해서 메소드를 구현하는 방법도 있지만 QueryDsl 라이브러리 내에서 제공하는 JPQLQuery 를 사용해서 구현 하는 방법도 있다.
🖐 JPAQueryFactory
JPAQueryFactory 는 QueryDsl의 핵심 클래스라고 생각을 한다.
JPA 기반으로 쿼리 작성을 더욱 편리하게 해주며 EntityManager를 기반으로 JPA 쿼리를 생성하고 실행하는 기능을 제공 해준다.
해당 클래스만 있다면 모든 기능을 다 사용 가능하며, 사용하는데 있어 IDE 자동 완성 기능도 제공 받을수 있고, 동적 쿼리, 재사용, 복잡한 쿼리등 장점이 많아서 가장 많이 사용하고 있다.
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
// Repository 인터페이스가 있는 패키지를 지정한다.
@Configuration
@EnableJpaRepositories("com.example.repository")
public class JpaConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
위의 설정은 Spring boot에서 JPAQueryFactory를 빈으로 등록하고, repository 패키지 내의 Spring Data JPA Repository들이 이 빈을 주입받아 QueryDSL을 사용하여 쿼리를 작성 할수 있도록 설정 하였다.
사용한 어노테이션에 대해서 짧게 정리 하도록 하겠다.
🔴 @Configuration : Spring 의 설정 클래스임을 나타내는 어노테이션. 이 어노테이션이 붙은 클래스를 컨테이너에서 빈을 정의하는 기본 구성으로 간주한다.
🔴 @EnableJpaRepositories : Spring Data JPA의 기능을 활성화하는 어노테이션. 어노테이션에 repository를 지정함으로써 해당 패키지 내의 Spring Data JPA Repository들을 자동으로 스캔하고 설정 하도록 한다.
🔴 @PersistenceContext : JPA의 EntityManager를 주입받기 위한 어노테이션. entityManager 필드는 JPAQueryFactory 빈을 생성할 때 사용 된다.
기본적인 설정을 했다면 사용 방법은 매우 간단하다.
@Service
public class UserService {
private final JPAQueryFactory queryFactory;
public List<User> getUserList() {
return queryFactory
.selectFrom(user)
.where(user.name.eq(name), user.age.eq(age))
.fetch();
}
}
간단하게 조회하는 쿼리를 작성해 보았다. (user의 경우 Qclass 기반으로 사용을 해야한다)
queryFactory의 사용 방법의 경우는 실제 쿼리를 작성하는 방식과 거의 비슷해서 쿼리에 대해서 기본적인 지식이 좋다면 더 쉽게 동적인 쿼리를 작성하는데 도움이 된다.
⚡생각
내가 QueryDsl을 사용하면서 느낀점 대해서 설명하도록 하겠다.
QueryDsl을 사용할때 JPA, 쿼리에 대해서는 더 깊게 공부해야할 필요가 있다. N+1 문제 부터 대용량 데이터를 처리할때 속도 이슈등 기본 개념을 이해하지 못하면 개선을 할 수 없다고 느끼는 부분이 생기고, QueryDsl 문제라고 생각할수 있는 문제들이 많이 있다.
물론 타입 안정성을 활용하여 런타임에 발생할 수 있는 오류를 컴파일 시점에 알수 있다거나 재사용성등 많은 장점을 가지고 있는것도 사실이다. 장점과 단점이 명확 하다고 생각하고 사용할때 생각하면서 사용해야 겠다.
'Spring' 카테고리의 다른 글
[Spring] Spring boot 3.0 + JPA 관련 gradle 설정 정리 (0) | 2023.09.25 |
---|---|
[Spring] Spring Event 기능 구현하기 ApplicationEventPublisher 이용하기 (0) | 2023.08.05 |
[Linux] JAVA에서 SFTP 접속 관련 이슈 사항 내용 정리 (0) | 2023.06.10 |
[Spring Boot] QueryDsl Qclass 에 대한 내용 정리 (0) | 2023.04.23 |
[Spring Boot] spring boot + mybatis 사용 방법 정리 (0) | 2023.04.15 |