[Spring Boot] JPA, QueryDSL 설정 및 가이드라인

김희정·2023년 10월 26일
1

Spring

목록 보기
7/18

💎 들어가며

JPA, QueryDSL를 처음 도입하면서 같이 개발하는 후임들의 질문 사항을 바탕으로 작성했습니다.

  • JPA, QueryDSL 너무 어려워요
  • Query는 대체 어디다 짜는건가요?
  • QueryDSL을 왜 쓰는건가요?

JPA 및 QueryDSL 설정 및 개발 가이드라인에 대해 작성하겠습니다.


1. 의존성 추가

먼저 jpaquerydsl관련 의존성과 플러그인을 설정합니다.

  • 저는 Maven을 많이 사용하기 때문에 Maven 기준으로 작성했습니다.
  • pom.xml에서 아래 코드를 추가해줍니다.

라이브러리 추가

<!-- JPA -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- QueryDSL APT Config -->
<dependency>
	<groupId>com.querydsl</groupId>
	<artifactId>querydsl-apt</artifactId>
	<version>${querydsl.version}</version>
	<scope>provided</scope>
</dependency>

<!-- QueryDSL JPA Config -->
<dependency>
	<groupId>com.querydsl</groupId>
	<artifactId>querydsl-jpa</artifactId>
	<version>${querydsl.version}</version>
</dependency>

플러그인 추가

QueryDSLEntity에 대해 자신 만의 클래스(QClass)를 생성하여 객체로 사용하기 때문에 아래와 같은 설정을 반드시 해주어야 합니다.

<plugins>
  		....
		<plugin>
			<groupId>com.mysema.maven</groupId>
			<artifactId>apt-maven-plugin</artifactId>
			<version>1.1.3</version>
			<executions>
				<execution>
					<goals>
						<goal>process</goal>
					</goals>
					<configuration>
						<outputDirectory>target/generated-sources/java</outputDirectory>
                      		<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
					</configuration>
				</execution>
			</executions>
		</plugin>
		....
 <plugins>  


2. Spring Bean 생성

JPAQueryFactory 전역 Bean을 생성합니다.

@Configuration
public class QueryDslConfiguration {
    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}

3. Entity 생성

Entity User 생성합니다.

@Entity
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "rec_key")
    private Long recKey;

    @Column(name = "user_id", length = 20, nullable = false)
    private String userId;

    @Column(name = "user_name", length = 20, nullable = false)
    private String userName;

    @Column(name = "create_date")
    @Builder.Default
    private LocalDateTime createDate = LocalDateTime.now();

    @Column(name = "edit_date")
    private LocalDateTime editDate = LocalDateTime.now();
}

4. Maven 업데이트

Entity를 추가했으면, QueryDSL을 이용하기 위해 소스 폴더를 생성해야 합니다.

  • Maven > 소스 및 업데이트 폴더 생성

  • target > classes > 패키지 경로에서 QClass 생성 유무를 확인합니다.
    => User, QUser 클래스 확인

5. Repository 생성

5.1 CrudRepository 상속

CrudRepository<Entity, ID> 인터페이스를 상속 받아, Repository 기능을 이용합니다.

public interface UserRepository extends CrudRepository<User, Long> {
    @Override
    List<User> findAll();
}

해당 레포지토리에서 Custom 기능을 이용하려면 커스텀 인터페이스를 생성하여 추가적으로 상속해주면 됩니다.

public interface UserRepository extends CrudRepository<User, Long>, UserRepositoryCustom {
    @Override
    List<User> findAll();
}

5.2 Custom Repository 생성

Custom Interface 생성

QueryDSL을 이용하여 구현할 기능들을 Custom Interface로 추상화합니다. 저는 사용자 조회 Request시 받은 UserSearchParam 객체를 이용하여 검색하는 기능을 예시로 구현했습니다.

public interface UserRepositoryCustom {
    Page<User> findAllBy(UserSearchParam param);
}

페이징 구현 예시를 위해 페이징 옵션을 넣었습니다.

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserSearchParam {
	@NotNull
    private int pageIdx = 1;
    
    @NotNull
    private int pageSize = 25;
    
    private String userId;
    private String userName;
}

Interface 구현

이 구현체에서 실제 QueryDSL을 사용한 코드를 작성합니다.

@RequiredArgsConstructor
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
    private final JPAQueryFactory queryFactory;
    
    @Override
    public Page<User> findAllBy(UserSearchParam param) {
        PageRequest request = PageRequest.of(param.getPageIdx() - 1, param.getPageSize());
        return new PageImpl<>(getBy(param), request, countBy(param));
    }
    
    /**
     * [User] 검색 조건문 생성
     * @param param 사용자 조회 파라미터
     * @return 검색 조건
     */
    public BooleanBuilder getCondition(UserSearchParam param) {
        QUser qUser = QUser.user;
        BooleanBuilder builder = new BooleanBuilder();
        if (param.getUserId() != null && !param.getUserId().isEmpty()) {
            builder.and(qUser.userId.like("%" + param.getUserId() + "%"));
        }
        if (param.getUserName() != null && !param.getUserName().isEmpty()) {
            builder.and(qUser.userId.like("%" + param.getUserName() + "%"));
        }
        return builder;
    }

    /**
     * [User] 조회 - Count
     * @param param 사용자 조회 파라미터
     * @return 사용자 수
     */
    public Long countBy(UserSearchParam param) {
        QUser qUser = QUser.user;

        return queryFactory
                .select(qUser.count())
                .from(qUser)
                .where(getCondition(param))
                .fetchOne();
    }

    /**
     * [User] 조회 - List
     * @param param 사용자 조회 파라미터
     * @return 사용자 목록
     */
    public List<User> getBy(UserSearchParam param) {
        QUser qUser = QUser.user;

        return queryFactory
                .selectFrom(qUser)
                .where(getCondition(param))
                .offset(param.getPageIdx() - 1)
                .limit(param.getPageSize())
                .fetch();
    }
}

6. Service 단에서 사용

UserRepositoryUserRepositoryCustom을 상속받았기 때문에, 따로 메소드를 추가해주지 않아도 원래 Repository의 메소드인 것처럼 사용할 수 있습니다.

@Service
@AllArgsConstructor
public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;

    @Override
    public PageVO<User> findAll() {
        return PageVO.builder(userRepository.findAll()).build();
    }

    @Override
    public PageVO<User> findAllBy(UserSearchParam param) {
        return PageVO.builder(userRepository.findAllBy(param)).build();
    }
 }
profile
Java, Spring 기반 풀스택 개발자의 개발 블로그입니다.

0개의 댓글