[QueryDsl] QueryDsl 사용해보기
- [ Backend ]/Spring DB, JPA
- 2022. 11. 22.
개요
스프링을 사용할때 기본적으로 JPARespository를 이용하여 DB에 대한 쿼리 메소드 기능을 사용하게 된다. 하지만 기본적인 CRUD기능이 아니라 더 복잡한 쿼리문을 작성하기 위해서는 네이티브 쿼리를 작성하거나, jpql을 사용해야 했다.
하지만 jpql을 작성할 경우, 쿼리문이 복잡해질수록 쿼리 스트링이 길어지게 되고, jpql 문자열 자체에 오타 혹은 문법적인 오류가 존재하는 경우 런타임시 db에 쿼리가 나갈때 오류가 발생하기 때문에 컴파일 타임에 오류를 발견하기 쉽지 않다. 또한 동적쿼리 사용시 조건문을 사용해서 코드가 어마어마하게 복잡해진다는 문제가 있는데, 또 동적쿼리 사용을 위해서는 네이티브 쿼리를 사용해서 스트링을 직접 입력하거나 criteria라는 빌더 클래스를 이용하는 방법 등이 있는데, 모두 사용이 복잡하고 가독성이 좋지 않아서 유지보수가 힘들어 해당 방법만 사용하기에는 실용성이 없다는 단점이 있다.
//Criteria를 사용하여 string이 아닌 자바코드로 JPQL 작성하기 예시
CriteriaBuilder cb = em.getCriteriaBuilder(); //em: EntityManager
CriteriaQuery<Member> query = cb.createQuery(Member.class);
Root<Member> m = query.from(Member.class);
CreteriaQuery<Member> cq = query.select(m).where(cb.equal(m.get("username"), "kim"));
List<Member> resultList = em.createQuery(cq).getResultList();
위 코드는 select * from Member where name like 'kim'; 이라는 간단한 쿼리문을 CreteriaQuery를 이용하여 표현한 방법이라고 한다. 여기서 더 복잡한 쿼리문이라면 매우 복잡하고 알아보기 힘들 것이라는 것을 알 수 있다.
//jpql 예시
var resultList = em.CreateQuery(
"select m FROM Member m where m.age>18"
).getReusltList();
//querydsl 예시
var resultList = queryFactory.selectFrom(member)
.where(member.age.gt(18))
.fetch()
따라서 쿼리 스트링을 직접 입력하거나 CriteriaBuilder와 같은 복잡한 방법을 사용하는 대신, QClass와 JPAQueryFactory를 사용하여 문자가 아닌 QueryFactory의 메소드를 이용하여 쿼리를 대신 생성할 수 있는 프레임워크가 querydsl이다.
개인적으로 querydsl 이용시에 가장 작성이 힘들었던 부분은 gradle과 환경설정 이었는데, 이건 중요한건 아니니까 생략하고 동작예시만 써놓겠다.
사용 예시
gradle 설정을 제대로 했다면 @Entity 어노테이션을 사용해서 등록한 클래스들이 Q라는 접두사가 붙은 형태로 gradle에서 설정한 경로에 생성되는데, 이것을 QClass라고 한다. querydsl 작성시에는 이러한 QClass가 엔티티 테이블의 역할을 하여 from절에 오게 된다.
@Repository
interface UserRepository:JpaRepository<User, Long> {
fun findByEmail(email:String?): User?
fun existsByEmail(email:String):Boolean
}
일반적인 Repository 생성방식이다. jpa에서 제공하는 JPARepoisoty 인터페이스를 구현하고 Entity 클래스를 제네릭으로 명시해주면, save, find, delete등의 키워드를 사용해서 CRUD기능을 사용할 수 있게 된다.
@Repository
interface UserRepository:JpaRepository<User, Long>, UserCustomRepository {
fun findByEmail(email:String?): User?
fun existsByEmail(email:String):Boolean
}
거기에 querydsl를 사용할 custom repository 인터페이스를 구현하도록 추가하자. 그러면 JPARepository에서 제공하는 메소드들 뿐 아니라, CustomRepository에서 우리가 별도로 정의한 메소드들도 사용할 수 있게 된다.
interface UserCustomRepository {
fun searchAllUser(createdAtStart: LocalDate?, createdAtEnd:LocalDate?):List<User>
fun searchAllUserByEmail(email:String, createdAtStart: LocalDate?, createdAtEnd: LocalDate?):List<User>
fun searchAllUserByUsername(username:String, createdAtStart: LocalDate?, createdAtEnd: LocalDate?):List<User>
}
@Repository
class UserCustomRepositoryImpl:UserCustomRepository {
@Autowired
private lateinit var queryFactory: JPAQueryFactory
val user = QUser("user")
override fun searchAllUser(createdAtStart: LocalDate?, createdAtEnd: LocalDate?):List<User> {
val builder = BooleanBuilder()
//createdAtStart와 createdAtEnd 사이의 경우
if(createdAtStart!=null && createdAtEnd!=null) {
builder.and(user.created_at.between(
createdAtStart.atStartOfDay(), //하루 시작시간
LocalDateTime.of(createdAtEnd, LocalTime.MAX).withNano(0) //하루 끝
))
}
//createdAtStart 이전에 생성된 경우만
else if(createdAtStart!=null) {
builder.and(user.created_at.gt(
createdAtStart.atStartOfDay()
))
}
//createdAtEnd 이후에 생성된 경우만
else if(createdAtEnd!=null) {
builder.and(user.created_at.lt(
LocalDateTime.of(createdAtEnd, LocalTime.MAX).withNano(0)
))
}
val result = queryFactory
.selectFrom(user)
.where(user.role.eq(Role.USER).and(builder)) //role==user
.orderBy(user.user_id.desc())
.fetch()
return result
}
config에서 @Bean으로 등록해둔 JPAQueryFactory를 주입받고, QUser 클래스 인스턴스를 생성한 후에, 적절하게 sql문을 생성해주면 된다. 이때 jpql과 가장 큰 차이는 직접 sql문을 작성하는 것이 아니라, selectfrom, and, eq등의 메소드를 사용해서 쿼리를 작성할 수 있다. (http://querydsl.com/static/querydsl/latest/apidocs/com/querydsl/jpa/impl/JPAQueryFactory.html 참고)
'[ Backend ] > Spring DB, JPA' 카테고리의 다른 글
[JPA] 연관관계 설정 고민 (0) | 2023.05.07 |
---|---|
[JPA] JDBC (0) | 2023.02.05 |
[JPA] 순환참조, 무한참조 (0) | 2022.07.26 |
[Spring JPA] 변경감지 vs 병합 (0) | 2022.02.02 |
[JPA] 객체지향 쿼리 언어(JPQL) 이해하기 (0) | 2022.01.17 |