java 1.8
spring boot 2.7.11
스프링부트 버전 별 설정법
dependencies {
...
//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}
//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거
clean {
delete file('src/main/generated')
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
annotationProcessor
Java 컴파일러 플러그인
컴파일 단계에서 어노테이션을 분석 및 처리함으로써 추가적인 파일을 생성할 수 있음
querydsl-apt가 @Entity 및 @Id 등의 애너테이션을 알 수 있도록, javax.persistence과 javax.annotation을 annotationProcessor에 함께 추가
프로젝트 내의 @Entity 어노테이션을 선언한 클래스를 탐색하고, JPAAnnotationProcessor를 사용해 Q 클래스를 생성
@Configuration
public class QueryDSLConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
public interface UserCustomRepository {
void signUpUser(UserRequest request);
Optional<User> loginUser(String userKey, String pw);
Page<UserResponse> read(String kewWord, Pageable pageable);
}
import static com.task.model.QUser.user;
@Repository
@RequiredArgsConstructor
public class UserCustomRepositoryImpl implements UserCustomRepository {
private final JPAQueryFactory jpaQueryFactory;
@Override
public void signUpUser(UserRequest request) {
jpaQueryFactory.insert(user)
.columns(
user.userKey, user.email, user.pw,
user.name, user.hp, user.agency
).values(
request.getUserKey(), request.getEmail(), request.getPw(),
request.getName(), request.getHp(), request.getAgency()
)
// .set(user.userKey, request.getUserKey())
// .set(user.email, request.getEmail())
// .set(user.pw, request.getPw())
// .set(user.name, request.getName())
// .set(user.hp, request.getHp())
// .set(user.agency, request.getAgency())
.execute();
}
@Override
public Optional<User> loginUser(String userKey, String pw) {
return Optional.ofNullable(jpaQueryFactory
.selectFrom(user)
.where(
user.userKey.eq(userKey),
user.pw.eq(pw)
)
.fetchOne());
}
@Override
public Page<UserResponse> read(String kewWord, Pageable pageable) {
List<UserResponse> users = jpaQueryFactory
.select(
new QUserResponse(
user.userKey, user.email, user.name, user.hp
)
)
.from(user)
.where(
user.userKey.contains(kewWord)
)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(user.createDate.desc())
.fetch();
return new PageImpl(
users,pageable,userTotal(kewWord)
);
}
private Long userTotal(String keyWord){
return jpaQueryFactory.select(user.count())
.from(user)
.where(
userKeyContains(keyWord)
)
.fetchOne();
}
private BooleanExpression userKeyContains(String keyWord) {
return hasText(keyWord) ? user.userKey.contains(keyWord) : null;
}
}
@Repository
public interface UserRepository extends JpaRepository<User, String>, UserCustomRepository {
...
}
@RequiredArgsConstructor
class Task12QueryDSLApplicationTests {
JPAQueryFactory queryFactory;
@DisplayName("QueryDSL 테스트 메소드 - userKey에 gu를 포함하며 agency가 SKT인 유저를 내림차순 정렬")
public void test(){
QUser qUser = QUser.user;
List<User> users = queryFactory
.selectFrom(qUser)
.where(
qUser.userKey.contains("gu"),
qUser.agency.eq("SKT")
)
.orderBy(qUser.createDate.desc())
.fetch();
}
}
QUser user = QUser.user;
QMsg msg = QMsg.msg;
List<User> list =
query.selectFrom(user)
.join(user.msg, msg)
.where(user.name.eq("gu"))
.fetch();
List<User> list =
queryFactory.selectFrom(user)
.where(user.age.gt(18))
.orderBy(user.name.desc())
// 페이징을 위해 limit, offset도 그냥 넣어줄 수 있다.
.limit(10)
.offset(10)
.fetch();
return queryFactory.selectFrom(coupon)
.where(
coupon.type.eq(typeParam),
isServiceable()
)
.fetch();
}
...
private BooleanExpression isServiceable() {
return coupon.status.wq("LIVE")
.and(marketing.viewCount.lt(markting.maxCount));
}
떤 쿼리가 나가는지 예측하기 힘들다는 단점이 있음
BooleanBuilder builder = new BooleanBuilder();
if(name != null) {
builder.and(user.name.contains("gu"));
}
if(age != 0) {
builder.and(user.age.gt(9));
}
List<User> list =
queryFactory.selectFrom(user)
.where(builder)
.fetch();
private BooleanExpression userNameEq(String userName) {
return hasText(userName) ? user.name.eq(userName) : null;
}
...
List<User> list =
queryFactory.selectFrom(user)
.where(userNameEq(request.userName))
.fetch();
@QueryProjection
public UserResponse(String userKey, String email, String name, String hp) {
this.userKey = userKey;
this.email = email;
this.name = name;
this.hp = hp;
}
@Override
public Optional<UserResponse> loginUser(String userKey, String pw) {
return Optional.ofNullable(jpaQueryFactory
.select(
new QUserResponse(
user.userKey, user.email, user.name, user.hp
)
)
.from(user)
.where(
user.userKey.eq(userKey),
user.pw.eq(pw)
)
.fetchOne());
}
@Repository
@RequiredArgsConstructor
public class UserRepositoryCustom {
private final JpaQueryFactory queryFactory;
// query문 선언
}
// BooleanBuilder
BooleanBuilder builder = new BooleanBuilder();
if (hasText(condition.getUsername())) {
builder.and(member.username.eq(condition.getUsername()));
}
// BooleanExpression
private BooleanExpression userNameEq(String userName) {
return hasText(userName) ? user.name.eq(userName) : null;
}
JPAQueryFactory 의 경우 Hibernate 6.0 버전 이상부터 insert를 지원함
5.6 버전을 이용할 경우 EntityManager의 persist를 이용해 insert 하는 방법이 있음
아래 사이트를 확인해보면 5.6 버전에서는 Column 선택이 불가하다고 함
(삽입할 명시적 값을 지정할 수 없음)
docs 사이트
5.6 (supports INSERT-SELECT only)
6.0 (supports both INSERT-SELECT and INSERT-VALUES)
java 8 버전은 5.6 버전까지만 사용 가능함
EntityManager 선언 방법
사용할 클래스에 @RequiredArgsConstructor, @Transactional 선언 후 호출
@Repository
@RequiredArgsConstructor
@Transactional
public class UserRepository{
private final JPAQueryFactory jpaQueryFactory;
private final EntityManager entityManager;
...
public void signUpUser(UserRequest request) {
entityManager.persist(
User.of(request)
);
}
Spring Boot에 QueryDSL을 사용해보자 - Tecoble
[JPA] Spring Data JPA와 QueryDSL 이해, 실무 경험 공유 - Namjun Kim
우아한 형제들의 Querydsl 사용법 - youngerjesus.log