Spring Boot+QueryDsl REST API不使用projection实现动态返回自定义实体字段
可行实现方案
以下两种方案都不需要重构现有业务逻辑、不需要提前定义静态Projection,改造成本极低,可以直接适配你现有的BaseRestCRUDController架构。
方案1:Jackson动态序列化过滤(推荐,改造成本最低)
该方案只在JSON序列化阶段过滤返回字段,不影响现有DAO层逻辑,10分钟即可完成适配:
- 首先给你的
BaseDTO父类添加Jackson序列化过滤注解:
@JsonFilter("dynamicFieldFilter") public class BaseDTO { // 原有父类逻辑保持不变 }
- 修改
BaseRestCRUDController的findAll方法,新增fields参数处理逻辑:
@GetMapping("/list") public ResponseEntity<Object> findAll(Predicate predicate, Pageable pageable, @RequestParam(required = false) String fields) { log.info("FindAll"); List<V> data = getCRUDService().findAll(predicate, pageable); // 未传fields参数直接返回原全字段结果 if (fields == null || fields.isBlank()) { return ResponseEntity.ok(data); } // 拆分要保留的字段集合 Set<String> includeFields = new HashSet<>(Arrays.asList(fields.split(","))); // 配置Jackson动态过滤规则 FilterProvider filterProvider = new SimpleFilterProvider() .addFilter("dynamicFieldFilter", SimpleBeanPropertyFilter.filterOutAllExcept(includeFields)); MappingJacksonValue jacksonValue = new MappingJacksonValue(data); jacksonValue.setFilters(filterProvider); return ResponseEntity.ok(jacksonValue); }
- 该方案完全兼容你现有的所有接口调用逻辑,不需要修改任何子类Controller、Service、DAO层代码。
方案2:QueryDSL动态Tuple查询(兼顾查询性能)
如果你的接口数据量较大,希望连数据库查询都只取需要的字段减少IO开销,可以用该方案:
- 在
BaseRestCRUDController中新增动态查询方法,根据传入的fields参数构造QueryDSL查询表达式:
// 父类通用方法,所有子类可直接复用 protected List<Map<String, Object>> findDynamicFields(Predicate predicate, Pageable pageable, String fields, Class<T> entityClass) { Set<String> fieldList = new HashSet<>(Arrays.asList(fields.split(","))); JPAQuery<Tuple> query = new JPAQuery<>(entityManager); // 动态构造查询字段表达式 PathBuilder<T> entityPath = new PathBuilder<>(entityClass, entityClass.getSimpleName().toLowerCase()); List<Expression<?>> expressions = fieldList.stream() .map(entityPath::get) .collect(Collectors.toList()); // 执行只返回指定字段的查询 List<Tuple> tuples = query.select(expressions.toArray(new Expression[0])) .from(entityPath) .where(predicate) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .orderBy(pageable.getSort().stream() .map(order -> order.isAscending() ? entityPath.get(order.getProperty()).asc() : entityPath.get(order.getProperty()).desc()) .toArray(OrderSpecifier[]::new)) .fetch(); // 查询结果Tuple转Map返回 return tuples.stream().map(tuple -> { Map<String, Object> map = new HashMap<>(); for (int i = 0; i < fieldList.size(); i++) { map.put(fieldList.get(i), tuple.get(expressions.get(i))); } return map; }).collect(Collectors.toList()); }
- 修改
findAll方法的逻辑,有fields参数时调用动态查询方法即可:
@GetMapping("/list") public ResponseEntity<Object> findAll(Predicate predicate, Pageable pageable, @RequestParam(required = false) String fields) { log.info("FindAll"); if (fields != null && !fields.isBlank()) { // 自动获取当前子类对应的实体类Class Class<T> entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; return ResponseEntity.ok(findDynamicFields(predicate, pageable, fields, entityClass)); } return new ResponseEntity(getCRUDService().findAll(predicate, pageable), HttpStatus.OK); }
- 该方案除了过滤返回字段,还能减少数据库查询的字段开销,适合大数据量接口的性能优化。
内容的提问来源于stack exchange,提问作者vcima
相关产品推荐
相关产品推荐

