프로젝션은 : select 대상을 지정하는 것
프로젝션 대상이 하나면 타입을 명확하게 지정할 수 있음
프로젝션 대상이 둘 이상이면 튜플이나 DTO로 조회
List<String> result = queryFactory
.select(member.username)
.from(member)
.fetch();
List<Tuple> result = queryFactory
.select(member.username, member.age)
.from(member)
.fetch();
우선 MemberDto를 만든다
@Data
@NoArgsConstructor
public class MemberDto {
private String username;
private int age;
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
그 다음으로 디렉토리 파일 위치와 함께 MemberDto 객체를 생성한다
List<MemberDto> result = em.createQuery("select new study.querydsl.dto.MemberDto(m.username,m.age) from Member m", MemberDto.class)
.getResultList();
3가지 방식이 있다.
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class
,member.username
,member.age))
.from(member)
.fetch();
List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.class
,member.username
,member.age))
.from(member)
.fetch();
별칭이 다를 때
QMember memberSub = new QMember("memberSub");
List<UserDto> result = queryFactory
.select(Projections.fields(UserDto.class
,member.username.as("name")
,ExpressionUtils.as(JPAExpressions
.select(memberSub.age.max())
.from(memberSub),"age")
))
.from(member)
.fetch();
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class
,member.username
,member.age))
.from(member)
.fetch();
사용법
장단점
constructor를 이용했을 때는 에러가 발생하면 런타임 즉, 에러가 발생했을때 오류는 잡는다
QMemberDto를 이용해서 하게되면 만들어진것 이외에 , 파라미터 타입이 잘못된값이 들어가면 빨간줄이 뜬다.
querydsl 어노테이션을 유지해야 하는 점과 DTO까지 Q파일을 생성해야하는 단점이 있다.
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
검색(조회)을 하는 경우 이름과 나이를 검색하는데, 이름이 있을 수도 있고 없을 수도 있고, 나이가 있을 수도 있고, 나이가 없을 수도 있고 이러한 방식으로 회원을 검색하는 쿼리
BooleanBuilder 라는 객체를 생성하고 builder에 이름과 나이를 조건에
따라 로직을 구성하고 .where() 조건 문에 넣어준다.
TestCode
@Test
public void dynamicQuery_BooleanBuilder(){
String usernameParam = "member1";
Integer ageParam = 10;
List<Member> result = searchMember1(usernameParam,ageParam);
assertThat(result.size()).isEqualTo(1);
}
searchMember1 메서드
private List<Member> searchMember1(String usernameParam, Integer ageParam) {
//usernameParam값이 null인 경우,ageParam값이 null인 경우
BooleanBuilder builder = new BooleanBuilder();
if(usernameParam != null){
builder.and(member.username.eq(usernameParam));
}
if(ageParam != null){
builder.and(member.age.eq(ageParam));
}
return queryFactory
.selectFrom(member)
.where(builder)
.fetch();
}
원래 where문에 ','를 붙이면 and가 취급되는데 null값이 오면 무시해버리는 성질을 이용한 동적쿼리 해결 방법이다.
TestCode
@Test
public void dynammicQuery_WhereParam(){
String usernameParam = "member1";
Integer ageParam = 10;
List<Member> result = searchMember2(usernameParam,ageParam);
assertThat(result.size()).isEqualTo(1);
}
searchMember2 메서드
private List<Member> searchMember2(String usernameCond, Integer ageCond) {
return queryFactory
.selectFrom(member)
.where(usernameEq(usernameCond),ageEq(ageCond))
//.where(allEq(usernameCond,ageCond))
.fetch();
}
usernameEq,ageEq,allEq
private BooleanExpression ageEq(Integer ageCond) {
return ageCond != null ? member.age.eq(ageCond) : null;
}
private BooleanExpression usernameEq(String usernameCond) {
return usernameCond != null ? member.username.eq(usernameCond) : null;
}
private BooleanExpression allEq(String usernameCond, Integer ageCond){
return usernameEq(usernameCond).and(ageEq(ageCond));
}
수정,삭제 배치 쿼리 ( 별크 연산 )
1) 벌크 조회
public void bulkUpdate(){
long count = queryFactory
.update(member)
.set(member.username, "비회원")
.where(member.age.lt(28)) //lt 미만
.execute();
em.flush();
em.clear();
List<String> result = queryFactory
.select(member.username)
.from(member)
.fetch();
}
벌크 연산은 항상 문제점이 존재한다. update쿼리는 db에 바로 정보를 인젝션 하기 때문에 영속성 컨텍스트와 db의 값이 다르게 된다. 이 상태에서 member의 정보를 출력하게 되면 db의 값과 영속성 컨텍스트의 값이 다르기 때문에 먼저 우선권을 갖는 영속성 컨텍스트의 member.username이 출력된다.그렇기 때문에 flush,clear를 해줘서 영속성 컨텍스트와 db의 정보가 동일하게 만들어주고 영속성 컨텍스트를 초기화한다. 그럼 문제 없다. JPQL은 트랜잭션이 끝날 때 마다 flush를 해주고 , Data Jpa는 @modifying(clearautomatically = true)라는 어노테이션으로 flush를 지원해준다. 하지만 querydsl에서는 그런 기능이 없기 때문에 직접 flush를 선언 해줘야한다.
2) 벌크 합
long count = queryFactory
.update(member)
.set(member.age, member.age.add(1))
.execute();
3) 벌크 수정
long count = queryFactory
.delete(member)
.where(member.age.gt(18))
.execute();
SQL function
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
member -> M
@Test
public void sqlFunction(){
List<String> result = queryFactory
.select(
Expressions.stringTemplate(
"function('regexp_replace',{0},{1},{2})",
member.username, "member", "M"))
.from(member)
.fetch();
}
소문자로 변경하기
@Test
public void sqlFunction2(){
queryFactory
.select(member.username)
.from(member)
.where(member.username.eq(
Expressions.stringTemplate("function('lower', {0}",member.username)))
.fetch();
}