팀프로젝트 중 데이터 조회 시 의도하지 않은 컬럼에 값이 매핑되는 문제가 발생했습니다.
이 문제를 해결하기 위해 애플리케이션에서 잘못된 매핑을 후처리하는 방식으로 해결했지만,
오픈채팅방에서 받은 조언을 통해 다시 한번 해결 방안을 고민하게 되었습니다.
이를 통해 DB 쿼리 성능을 보다 적극적으로 활용할 수 있는 방법을 찾았습니다.
원래의 쿼리에서 데이터를 조회했을 때, 원하는 컬럼에 의도한 데이터가 매핑되지 않는 문제가 있었습니다.
예를 들어, PlaceHierarchy 객체가 데이터를 받아올 때, 각 필드에 제대로 값이 매핑되지 않아서 애플리케이션에서 이를 후처리하여 값을 맞추는 방식으로 작업을 마무리했습니다.
public PlaceHierarchy(PlaceHierarchyProjection projection) {
switch(projection.getPlaceType()) {
case A:
this.aId = projection.getDefaultId();
this.aName = projection.getDefaultName();
break;
case B:
this.aId = projection.getAId();
this.bId = projection.getDefaultId();
this.aName = projection.getAName();
this.bName = projection.getDefaultName();
break;
default:
this.aId = projection.getAId();
this.bId = projection.getBId();
this.defaultId = projection.getDefaultId();
this.aName = projection.getAName();
this.bName = projection.getBName();
this.defaultName = projection.getDefaultName();
break;
}
}
위의 코드에서는 PlaceHierarchyProjection을 기반으로 조건에 따라 각각의 필드를 매핑하고 있습니다.
그러나 이 방식은 쿼리 결과가 복잡하게 매핑되어 후처리하는 로직이 많아지는 문제를 야기합니다.
"CaseBuilder를 사용하여 쿼리에서 올바른 매핑 형태로 데이터를 조회해보면 어떨까요?"
시니어 개발자 님은 Querydsl을 활용해 쿼리 내에서 필요한 데이터 컬럼을 조건에 맞게 매핑할 수 있는 방법을 제시했습니다.
이 방법은 쿼리 실행 시 데이터를 정확하게 매핑해주는 방식으로, 애플리케이션 코드에서 복잡한 후처리 작업을 줄일 수 있는 좋은 접근법이었습니다.
switch (placeType) {
case A:
selectFields.add(new CaseBuilder().when(qDomain.type.eq(Type.A).then(qDomain.id).otherwise(qDomain.qA_Domain.id).as("aId")));
selectFields.add(new CaseBuilder().when(qDomain.type.eq(Type.A).then(qDomain.name).otherwise(qDomain.qA_Domain.name).as("aName")));
queryBuilder.leftJoin(qA_Domain).on(qA_Domain.id.eq(qDomain.A_Domain.id));
break;
case B:
selectFields.add(new CaseBuilder().when(qDomain.type.eq(Type.B).then(qDomain.id).otherwise(qDomain.qB_Domain.id).as("bId")));
selectFields.add(new CaseBuilder().when(qDomain.type.eq(Type.B).then(qDomain.name).otherwise(qDomain.qB_Domain.name).as("bName")));
queryBuilder.leftJoin(qB_Domain).on(qB_Domain.id.eq(qDomain.B_Domain.id));
break;
default:
selectFields.add(new CaseBuilder().when(qDomain.type.eq(Type.C).then(qDomain.id).otherwise(qDomain.qC_Domain.id).as("cId")));
selectFields.add(new CaseBuilder().when(qDomain.type.eq(Type.C).then(qDomain.name).otherwise(qDomain.qC_Domain.name).as("cName")));
break;
}
위의 코드에서는 CaseBuilder를 사용하여 조건에 맞는 컬럼을 조회하는 방식으로, 각 데이터 타입에 맞는 컬럼을 쿼리에서 직접 매핑하도록 했습니다.
이를 통해 데이터가 의도한 대로 매핑될 수 있었습니다.
쿼리에서 이미 데이터를 올바르게 매핑한 결과, 애플리케이션 코드에서는 복잡한 후처리 없이 데이터를 가져올 수 있었습니다.
이제 PlaceHierarchy 객체는 다음과 같이 간단하게 매핑됩니다.
public PlaceHierarchy(PlaceHierarchyProjection projection) {
this.aId = projection.getAId();
this.bId = projection.getBId();
this.defaultId = projection.getDefaultId();
this.aName = projection.getAName();
this.bName = projection.getBName();
this.defaultName = projection.getDefaultName();
}
복잡한 switch 문을 없애고, 각 필드에 필요한 값들을 쿼리에서 바로 매핑할 수 있게 되었습니다.
이 문제를 해결하면서, 애플리케이션 코드에서 처리해야 할 부분과 DB에서 처리해야 할 부분에 대한 고민이 생겼습니다.
예전에는 애플리케이션에서 쿼리로 가져온 데이터를 가공하는 방식이 더 깔끔하고, 비즈니스 로직의 명확한 분리가 이루어졌다고 생각했었습니다.
하지만 시니어님의 조언을 따라 DB에서 처리하는 방식으로 전환하면서, 애플리케이션의 가독성은 높아졌고, 쿼리 성능을 더 적극적으로 활용할 수 있었습니다.
특히, CaseBuilder와 같은 쿼리 빌더를 활용해 데이터의 매핑을 직접 처리함으로써, DB 성능을 극대화할 수 있다는 점에서 긍정적인 효과를 보았습니다.
이번 작업을 통해 DB와 애플리케이션 각각의 역할에 대해 다시 한번 생각해볼 기회가 되었습니다.
애플리케이션에서 처리할 수 있는 부분은 최소화하고, DB에서 할 수 있는 작업은 쿼리 자체에서 처리하여 성능을 높이는 방식으로 접근하는 것이 더 효율적이라는 결론에 도달했습니다.
시니어님의 조언에 따라 쿼리에서 데이터 매핑을 처리하는 방식으로 진행했고, 결과적으로 더 효율적인 해결책을 찾을 수 있었습니다.
향후에는 더 복잡한 쿼리에서도 Querydsl과 같은 쿼리 빌더를 활용하여 성능과 가독성을 동시에 고려한 작업을 해나갈 계획입니다.