반응형
Introduction
서비스 로직에서 특정 데이터를 업데이트, 즉 변경 하기 위해서 JPA 를 사용할 경우 일반적으로 DirtyChecking 을 통해 업데이트를 진행한다. 하지만 다수의 데이터를 일괄적으로 변경을 해야하는 경우에는 다수의 조회 및 업데이트 쿼리가 발생하기 때문에 성능적으로 좋지 않다. 그런 경우 JPQL 을 통해 업데이트 쿼리를 직접 작성하여 Bulk 업데이트를 진행하기도 하는데. Querydsl를 통해 문자열로 이루어진 JPQL 업데이트 쿼리의 Set 파라미터를 동적으로 업데이트를 진행할 수 있다.
Bulk update
1.JPQL
JPQL을 통한 기본적은 Bulk Update는 단순한 문자열 형태로 진행된다. 아래 코드는 Repository에서 나이와 유저아이디로 해당 유저들의 나이를 일괄 변경한다.
@Transactional
@Modifying
@Query("update Member m set m.age = :age where m.id in (:meberIds)")
void updateAgeByMemberIds(Integer age, List<Long> memberIds);
만약 나이뿐만이 아니라 주소가 달라질 경우 새로운 함수를 만들어야 한다. 이렇게 Set 해주는 파라미터의 갯수의 따라서 함수가 계속 추가적으로 쌓이는 불편함이 생긴다.
@Transactional
@Modifying
@Query("update Member m set m.age = :age where m.id in (:meberIds)")
void updateAgeByMemberIds(Integer age, List<Long> memberIds);
@Transactional
@Modifying
@Query("update Member m set m.age = :age, m.address = :address where m.id in (:meberIds)")
void updateAgeAndAddressByMemberIds(Integer age, String address, List<Long> memberIds);
혹은 문자열 및 매개변수를 동적으로 변환시켜주는 로직을 직접 구성해주어야 한다.
String queryString = "update Member m set";
if (newName != null) {
queryString += " m.name = :newName";
}
if (newAge != null) {
queryString += ", m.age = :newAge";
}
queryString += " where m.id = :id";
Query query = em.createQuery(queryString);
if (newName != null) {
query.setParameter("newName", newName);
}
if (newAge != null) {
query.setParameter("newAge", newAge);
}
query.setParameter("id", memberId);
int updatedCount = query.executeUpdate();
이는 반복적인 코드를 증가시키고, 가독성이 매우 떨어지며 단순 String 이기 때문에 띄어쓰기로 인한 식별 문제 등등 컴파일 단계에서 오류를 발견하기가 힘들다.
2. QueryDSL
QueryDSL 은 컴파일 단계에서의 오류를 발견할 수 있도록 코드 레벨에서 문법적인 에러를 발견할 수 있으며, 여러가지 지원하는 기능들 가운데에는 동적으로 Query 를 Build 하는 기능을 지원한다.
그 중에는 동적 검색(https://mokggang.tistory.com/69)뿐만이 아니라 위와 같은 불편한 동적 업데이트 쿼리 생성을 손쉽게 개발할 수 있도록 지원한다.
public void dynamicUpdateMember(MemberSearchCondition condition){
JPAUpdateClause clause = new JPAUpdateClause(entityManager, member);
if(StringUtils.hasText(condition.getName)){
clause.set(member.name, condition.getName);
}
if(condition.getAge!=null){
clause.set(member.age, condition.getAge);
}
...
clause.where(member.id.eq(condition.getId))
clause.execute();
}
업데이트 할 파라미터를 set 해주기만 하면 되기 때문에 많은 양의 코드를 줄여준다.
3. 확인
동적 업데이트를 지원하는 JPAUpdateClause.class를 확인해보면 JPQL 로 직접 동적 업데이트를 만드는 방식을 사용하여 QType객체를 대상으로 Query 를 생성해 execute 해주고 있다.
@Override
public <T> JPAUpdateClause set(Path<T> path, T value) {
if (value != null) {
updates.put(path, Expressions.constant(value));
} else {
setNull(path);
}
return this;
}
@Override
public long execute() {
JPQLSerializer serializer = new JPQLSerializer(templates, entityManager);
serializer.serializeForUpdate(queryMixin.getMetadata(), updates);
Query query = entityManager.createQuery(serializer.toString());
if (lockMode != null) {
query.setLockMode(lockMode);
}
JPAUtil.setConstants(query, serializer.getConstants(), queryMixin.getMetadata().getParams());
return query.executeUpdate();
}
public void serializeForUpdate(QueryMetadata md, Map<Path<?>, Expression<?>> updates) {
append(UPDATE);
handleJoinTarget(md.getJoins().get(0));
append(SET);
boolean first = true;
for (Map.Entry<Path<?>, Expression<?>> entry : updates.entrySet()) {
if (!first) {
append(", ");
}
handle(entry.getKey());
append(" = ");
handle(entry.getValue());
first = false;
}
if (md.getWhere() != null) {
append(WHERE).handle(md.getWhere());
}
}
반응형
'개발 > QueryDSL' 카테고리의 다른 글
[QueryDSL] 정렬 (동적 정렬) (0) | 2024.03.05 |
---|---|
[QueryDSL] 엔티티 조회 시에 DTO 로 매핑 하는 방법 2탄 (ObjectList in Object) (0) | 2024.03.05 |
[QueryDSL] 동적 검색 (0) | 2024.03.04 |
[QueryDSL] 엔티티 조회 시에 DTO 로 매핑 하는 방법 (0) | 2024.02.29 |