지금 현재 리팩토링을 진행중인 프로젝트가 있는데,
내가 스터디를 했던 JPA를 적용시켜 Mybatis에서 JPA로 이전작업을 하는 중에
생각했던것 보다 QueryDSL을 더 많이 사용하는 것 같다.
많은 분들이 올리신 JPA와 같이 쓰는 QueryDSL은 별도의 support
클래스를 만들어서
할당하는 방식으로 사용했다.
나는 그런데 조금 다르게 설정을 했다.
공식문서의 4.6.1챕터 에서 보면 Querydsl을 같이 사용하려면
해당 엔티티 Repository
인터페이스에 다중상속으로 RepositorySupport
인터페이스를 상속해주고
RepositorySupportImpl
에서 Support
인터페이스를 구현해주었다.
이렇게 말이다.
@Repository
public interface UserRepository extends JpaRepository<User, Long>, UserRepositorySupport {
}
public interface UserRepositorySupport {
}
public class UserRepositorySupportImpl implements UserRepositorySupport {
}
이렇게 인터페이스를 상속받아주게 되면 기본이 Impl
인데 상속받아주게 되면
커스텀 Repository
가 등록이 된다.
그리고 JpaRepository
를 구현하고 있는 SimpleJpaRepository
에는 @Repository
어노테이션이
붙어있으므로 자연스럽게 전부 어노테이션은 달지 않아줘도 된다.
그리고 이제 저 SupportImpl
에는 Querydsl
세팅하면서 등록해준 빈을 생성자 주입으로 할당받아
사용하면 되겠다.
일단 사실 부딪히면서 생긴 에로사항들이 꽤 나왔다.
음 단순한 이론책이 아니라, 실습을 해서 몸으로 깨달아야 하는 지식은
책보면서 그냥 이론만 학습해서는 상당히 휘발성인 지식이 되어버린다.
몸으로 깨달으면서 이렇게 포스팅으로 정리까지 한다? 아주 👍
조금 찾아봤던 문법들을 정리해보겠다.
일단 update
과정에서 조회수를 +1
해야하는 로직을 예로 들어보겠다.
UPDATE board SET read_cnt = read_cnt + 1 where board_no = ?
이렇게 될 것이다.
이걸 근데 어떻게 해줄까 고민을 했었다.
그냥 수식에 + 1을 해주려고 로직에 넣었더니 에러가 발생했다. 😭
jpaQueryFactory.update(board)
.set(board.readCnt, board.readCnt.add(1)) // + 1의 경우
.set(board.readCnt, board.readCnt.subtract(1)) // - 1 의 경우
.where(board.id.eq(id))
.execute();
두가지를 동시에 적었지만 하나만 사용했었다.
상당히 간단했던 에로사항인데 +1을 해야한다는 생각만 있어서 깊어지게 됐던것 같다.
이건 너무 간단하고 그리고 단순한 add()
와 subtract()
로 해결했다.
진행했던 스터디분들에게도 여쭤봤었다.
감사합니다 ㅠㅠ 🙏
너무 간단해서 이 방식은 여기서 끝내도록 하겠다.
검색기능을 사용하기 위해서 sql문에선 like를 사용했다.
여기서부터 이제 문법이란 문법은 다 **Expression
에 들어있는 것을 확인했다.
이 like
, contains
, startswith
, endswith
전부 StringExpression
에
포함된 메소드들 이었다.
발견했던 것은 like는 해당하는 문자열이랑 같은 것을 뽑아주었고,
contains
는 단어의 의미대로 앞뒤로 %
를 붙여준 것과 같아서 나는 contains
를 사용했다.
startswith
는 특정 문자로 시작하는 것,
endswith
는 특정문자로 끝나는 쿼리문을 만들어준다. 👍
public class NewBookRepositorySupportImpl implements NewBookReposirotySupport {
public List<NewBook> selectGenre(String nbGenre) {
return jpaQueryFactory.selectFrom(newBook)
.where(newBook.nbGenre.contains(nbGenre))
.fetch();
}
}
최고 수치값을 쓸때는 단순하게 max를 써주면 되는데 왜 이 max
를 여기에 포함시켰냐 라고 묻는다면
내 경우에는 맥스 수치값을 뽑아내는게 아니라 맥스에 해당하는 컬럼을 조회했어야 했다.
select * from newbook where nb_scount = (select max(nb_scount) from newbook);
요런 형식의 sql문이었다.
자세히 들여다보니 max
는 NumberExpression
에 있었다.
근데 저 괄호에 다시 select
를 하려면 JPAExpressions
에서 JPQLQuery 인스턴스를 제공해주는데
그 클래스에서 쿼리를 새로 만들어주어야 했다.
아래는 클래스의 일부분이다.
이런식으로 쿼리를 만들어 줄 수 있었기 때문에
@Override
public NewBook selectBestSeller() {
return jpaQueryFactory.selectFrom(newBook)
.where(newBook.nbScount
.eq(JPAExpressions.select(newBook.nbScount.max())
.from(newBook)))
.fetchOne();
}
위의 소스처럼 eq()
안에 JPQL쿼리를 넣어서 만들어 주었다.
일단 이런 문법 자체를 잘 파악해서 필요한걸 매칭을 재빠르게 해주려면 필요한 것은
DB SQL문을 잘 알고 있어야한다.
아직 더 큰 이슈사항은 만나지 않아서 여기까지 정리하도록 하겠다.
한가지 의문점은 원래 QueryDSL이 많아지는가? 에 대한 개인적인 생각이다.
기초적인 CRUD와 쿼리메소드가 아무리 강력하다고 한들 상세하게 조회하는 쿼리들에 대해선
QueryDSL이 빠질 수는 없으니까 말이다.
현재 단위테스트 진행은 이런 자잘한 이슈들을 겪으면서 한 테이블씩 천천히 진도를 나가는 중이다.
더 나아가서 서비스, 컨트롤러 등등 단위 테스트를 넣을 예정이며 통합테스트도 고려해야겠다.