QueryDSL을 쓰며 궁금했던 점이 왜 굳이 Qclass를 쓰는 거지? 궁금했었다.
타입 안정성 등의 여러 이유로 Qclass를 쓴다는데... Entity는 이걸 못하는 건가?
궁금해졌다. 그래서 열심히 살펴보며 흐름 파악하는 글...
자세한 글이 아님...
Qclass
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "username", "age"})
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
private int age;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
public Member(String username) {
this(username, 0);
}
public Member(String username, int age) {
this(username, age, null);
}
public Member(String username, int age, Team team) {
this.username = username;
this.age = age;
if (team != null) {
changeTeam(team);
}
}
private void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
이런 Entity 클래스가 있다고 하자
queryDsl 의존성을 추가 했으면, Gradle Build를 하면 자동으로 Qclass를 생성해준다.
위치는 build\genrated\sources\annotationProcessor\java\main\PROJECT_DIRECTORY...\QMember 로 생성된다
PROJECT_DIRECTORY...는 자신이 만든 프로젝트 구조를 생략한 것임
package study.querydsl.entity;
import static com.querydsl.core.types.PathMetadataFactory.*;
import com.querydsl.core.types.dsl.*;
import com.querydsl.core.types.PathMetadata;
import javax.annotation.processing.Generated;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.dsl.PathInits;
/**
* QMember is a Querydsl query type for Member
*/
@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QMember extends EntityPathBase<Member> {
private static final long serialVersionUID = -769675599L;
private static final PathInits INITS = PathInits.DIRECT2;
public static final QMember member = new QMember("member1");
public final NumberPath<Integer> age = createNumber("age", Integer.class);
public final NumberPath<Long> id = createNumber("id", Long.class);
public final QTeam team;
public final StringPath username = createString("username");
public QMember(String variable) {
this(Member.class, forVariable(variable), INITS);
}
public QMember(Path<? extends Member> path) {
this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS));
}
public QMember(PathMetadata metadata) {
this(metadata, PathInits.getFor(metadata, INITS));
}
public QMember(PathMetadata metadata, PathInits inits) {
this(Member.class, metadata, inits);
}
public QMember(Class<? extends Member> type, PathMetadata metadata, PathInits inits) {
super(type, metadata, inits);
this.team = inits.isInitialized("team") ? new QTeam(forProperty("team")) : null;
}
}
이런 Qclass가 생성되는데... 이게 뭐야...
사진을 보면 Number 추상 클래스를 상속 받는 클래스들은 createNumber 메서드로 맞게 만들어주고
String 타입들은 createString으로 알맞게 만들어 주는 거 같다.
NumberPath?
StringPath?
이 Path 관련 메서드
들은 BeanPath에 있다.. 음... 내 레벨에선 깊게 들어가질 못하겠음 🥹🥹🥹🥹🥹🥹
어 쨌 든!!
BeanPath에서는 SimpleExpression을 상속받고 있는데,
이 SimpleExpression은 eq, loe, goe 등의 메서드를 제공한다
as, isNotNull... 등등
/*
* Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.querydsl.core.types.dsl;
import java.util.Arrays;
import java.util.Collection;
import org.jetbrains.annotations.Nullable;
import com.querydsl.core.types.*;
import com.querydsl.core.util.CollectionUtils;
/**
* {@code SimpleExpression} is the base class for {@link Expression} implementations.
*
* @author tiwe
*
* @param <T> expression type
*/
public abstract class SimpleExpression<T> extends DslExpression<T> {
private static final long serialVersionUID = -4405387187738167105L;
@Nullable
private transient volatile NumberExpression<Long> count;
@Nullable
private transient volatile NumberExpression<Long> countDistinct;
@Nullable
private transient volatile BooleanExpression isnull, isnotnull;
public SimpleExpression(Expression<T> mixin) {
super(mixin);
}
/**
* Create an alias for the expression
*
* @return alias expression
*/
@Override
public SimpleExpression<T> as(Path<T> alias) {
return Expressions.operation(getType(),Ops.ALIAS, mixin, alias);
}
/**
* Create an alias for the expression
*
* @return alias expression
*/
@Override
public SimpleExpression<T> as(String alias) {
return as(ExpressionUtils.path(getType(), alias));
}
/**
* Create a {@code this is not null} expression
*
* @return this is not null
*/
public BooleanExpression isNotNull() {
if (isnotnull == null) {
isnotnull = Expressions.booleanOperation(Ops.IS_NOT_NULL, mixin);
}
return isnotnull;
}
/**
* Create a {@code this is null} expression
*
* @return this is null
*/
public BooleanExpression isNull() {
if (isnull == null) {
isnull = Expressions.booleanOperation(Ops.IS_NULL, mixin);
}
return isnull;
}
/**
* Get the {@code count(this)} expression
*
* @return count(this)
*/
public NumberExpression<Long> count() {
if (count == null) {
count = Expressions.numberOperation(Long.class, Ops.AggOps.COUNT_AGG, mixin);
}
return count;
}
/**
* Get the {@code count(distinct this)} expression
*
* @return count(distinct this)
*/
public NumberExpression<Long> countDistinct() {
if (countDistinct == null) {
countDistinct = Expressions.numberOperation(Long.class, Ops.AggOps.COUNT_DISTINCT_AGG, mixin);
}
return countDistinct;
}
/**
* Create a {@code this == right} expression
*
* <p>Use expr.isNull() instead of expr.eq(null)</p>
*
* @param right rhs of the comparison
* @return this == right
*/
public BooleanExpression eq(T right) {
if (right == null) {
throw new IllegalArgumentException("eq(null) is not allowed. Use isNull() instead");
} else {
return eq(ConstantImpl.create(right));
}
}
/**
* Create a {@code this == right} expression
*
* @param right rhs of the comparison
* @return this == right
*/
public BooleanExpression eq(Expression<? super T> right) {
return Expressions.booleanOperation(Ops.EQ, mixin, right);
}
/**
* Create a {@code this == all right} expression
*
* @param right
* @return this == all right
*/
public BooleanExpression eqAll(CollectionExpression<?, ? super T> right) {
return eq(ExpressionUtils.all(right));
}
/**
* Create a {@code this == < right} expression
*
* @param right
* @return this == any right
*/
public BooleanExpression eqAny(CollectionExpression<?, ? super T> right) {
return eq(ExpressionUtils.any(right));
}
/**
* Create a {@code this == all right} expression
*
* @param right
* @return this == all right
*/
public BooleanExpression eqAll(SubQueryExpression<? extends T> right) {
return eq(ExpressionUtils.all(right));
}
/**
* Create a {@code this == any right} expression
*
* @param right
* @return this == any right
*/
public BooleanExpression eqAny(SubQueryExpression<? extends T> right) {
return eq(ExpressionUtils.any(right));
}
/**
* Create a {@code this in right} expression
*
* @param right rhs of the comparison
* @return this in right
*/
public BooleanExpression in(Collection<? extends T> right) {
if (right.size() == 1) {
return eq(right.iterator().next());
} else {
return Expressions.booleanOperation(Ops.IN, mixin, ConstantImpl.create(right));
}
}
/**
* Create a {@code this in right} expression
*
* @param right rhs of the comparison
* @return this in right
*/
public BooleanExpression in(T... right) {
if (right.length == 1) {
return eq(right[0]);
} else {
return Expressions.booleanOperation(Ops.IN, mixin, ConstantImpl.create(CollectionUtils.unmodifiableList(Arrays.asList(right))));
}
}
/**
* Create a {@code this in right} expression
*
* @param right rhs of the comparison
* @return this in right
*/
public BooleanExpression in(CollectionExpression<?,? extends T> right) {
return Expressions.booleanOperation(Ops.IN, mixin, right);
}
/**
* Create a {@code this in right} expression
*
* @param right rhs of the comparison
* @return this in right
*/
public BooleanExpression in(SubQueryExpression<? extends T> right) {
return Expressions.booleanOperation(Ops.IN, mixin, right);
}
/**
* Create a {@code this in right} expression
*
* @param right rhs of the comparison
* @return this in right
*/
public BooleanExpression in(Expression<? extends T>... right) {
return Expressions.booleanOperation(Ops.IN, mixin, Expressions.set(right));
}
/**
* Create a {@code this <> right} expression
*
* @param right rhs of the comparison
* @return this != right
*/
public BooleanExpression ne(T right) {
if (right == null) {
throw new IllegalArgumentException("ne(null) is not allowed. Use isNotNull() instead");
} else {
return ne(ConstantImpl.create(right));
}
}
/**
* Create a {@code this <> right} expression
*
* @param right rhs of the comparison
* @return this != right
*/
public BooleanExpression ne(Expression<? super T> right) {
return Expressions.booleanOperation(Ops.NE, mixin, right);
}
/**
* Create a {@code this != all right} expression
*
* @param right
* @return this != all right
*/
public BooleanExpression neAll(CollectionExpression<?, ? super T> right) {
return ne(ExpressionUtils.all(right));
}
/**
* Create a {@code this != any right} expression
*
* @param right
* @return this != any right
*/
public BooleanExpression neAny(CollectionExpression<?, ? super T> right) {
return ne(ExpressionUtils.any(right));
}
/**
* Create a {@code this not in right} expression
*
* @param right rhs of the comparison
* @return this not in right
*/
public BooleanExpression notIn(Collection<? extends T> right) {
if (right.size() == 1) {
return ne(right.iterator().next());
} else {
return Expressions.booleanOperation(Ops.NOT_IN, mixin, ConstantImpl.create(right));
}
}
/**
* Create a {@code this not in right} expression
*
* @param right rhs of the comparison
* @return this not in right
*/
public BooleanExpression notIn(T... right) {
if (right.length == 1) {
return ne(right[0]);
} else {
return Expressions.booleanOperation(Ops.NOT_IN, mixin, ConstantImpl.create(Arrays.asList(right)));
}
}
/**
* Create a {@code this not in right} expression
*
* @param right rhs of the comparison
* @return this not in right
*/
public BooleanExpression notIn(CollectionExpression<?,? extends T> right) {
return Expressions.booleanOperation(Ops.NOT_IN, mixin, right);
}
/**
* Create a {@code this not in right} expression
*
* @param right rhs of the comparison
* @return this not in right
*/
public BooleanExpression notIn(SubQueryExpression<? extends T> right) {
return Expressions.booleanOperation(Ops.NOT_IN, mixin, right);
}
/**
* Create a {@code this not in right} expression
*
* @param right rhs of the comparison
* @return this not in right
*/
public BooleanExpression notIn(Expression<? extends T>... right) {
return Expressions.booleanOperation(Ops.NOT_IN, mixin, Expressions.list(right));
}
/**
* Create a {@code nullif(this, other)} expression
*
* @param other
* @return nullif(this, other)
*/
public SimpleExpression<T> nullif(Expression<T> other) {
return Expressions.operation(this.getType(), Ops.NULLIF, this, other);
}
/**
* Create a {@code nullif(this, other)} expression
*
* @param other
* @return nullif(this, other)
*/
public SimpleExpression<T> nullif(T other) {
return nullif(ConstantImpl.create(other));
}
/**
* Create a case expression builder
*
* @param other
* @return case expression builder
*/
public CaseForEqBuilder<T> when(T other) {
return new CaseForEqBuilder<T>(mixin, ConstantImpl.create(other));
}
/**
* Create a case expression builder
*
* @param other
* @return case expression builder
*/
public CaseForEqBuilder<T> when(Expression<? extends T> other) {
return new CaseForEqBuilder<T>(mixin, other);
}
}
이런 메서드들!! QueryDsl 작성할 때 아주 편하게 썼던 메서드들이다.
타입 안정성은 어디서??
Path에서 확인하나??
/*
* Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.querydsl.core.types;
import java.lang.reflect.AnnotatedElement;
/**
* {@code Path} represents a path expression. Paths refer to variables, properties and collection members access.
*
* @author tiwe
* @param <T> expression type
*/
public interface Path<T> extends Expression<T> {
/**
* Get the metadata for this path
*
* @return path metadata
*/
PathMetadata getMetadata();
/**
* Get the root for this path
*
* @return root of path
*/
Path<?> getRoot();
/**
* Return the annotated element related to the given path
* <p>For property paths the annotated element contains the annotations of the
* related field and/or getter method and for all others paths the annotated element
* is the expression type.</p>
*
* @return annotated element
*/
AnnotatedElement getAnnotatedElement();
}
여기서 getMetadata 라는 추상 메서드를 갖고 있는데 이걸 구현하는 부분이
근데 return null..? 구현 안 한 건가..?
gpt한테 물어보니까 return null은 예시 코드일 가능성이 크다고 한다
실제 EntityPathBase는 getMetadata가 구현되어 돌아간다고 한다.
APT에 의해서 자동으로 만들어지고 구동되는데, APT(Annotation Processing Tool) 는 Lombok을 생각하면 편할 거 같다.
@Getter @Setter 를 달면 자동으로 반환타입 딱딱 맞춰서 메서드를 자동 생성해주는데, 이걸 이용해서 만들어주는 듯,,?
이렇게 Qclass 는 DB 컬럼과 맞게 타입 안정성을 지키기 위해서 여러가지 클래스를 상속 받고 구현하고 있기 때문에 안정적으로 돌아가는 것이다...
JPQL의 단점이 뭘까? 타입 안정성에 대한 보장을 안 해준다. 하지만 QueryDSL은 해준다.
그걸 Qclass로 해주는 것이고.. 메타데이터.. Path 등등... 으로 확인해주는 거 같다...
정확하게 알고 싶은데... 공식 문서에도 안 나옴... 내가 못찾는 건가
- 타입 안정성을 어떻게 지켜주나?
- 엔티티에 명시된 타입을 알고 있다가, Qclass에서 타입을 맞춰서 만들어준다. 그래서 DSL을 작성할 때 안틀리게끔 해준다.
- 동일한 타입으로만 Where문에 넣을 수 있게해줌
- PathMetadata 는 더욱 추상화한 개념
- Java 최상위 클래스는 Object
'TIL' 카테고리의 다른 글
TIL 2023-12-20 스프링 해시태그 기능 구현 (0) | 2023.12.20 |
---|---|
TIL 2023-12-19 @WebMvcTest 와 Application JPAQueryFactory Bean 등록 오류 (2) | 2023.12.19 |
TIL 2023-12-15 QueryDSL 기본 사용법 (0) | 2023.12.15 |
TIL 2023-12-14 정적쿼리 동적쿼리 그리고 QueryDSL 약간 (0) | 2023.12.14 |
TIL 2023-12-13 AOP Logging 구현 (0) | 2023.12.13 |