Spring Pageable排序时DTO与Entity属性名映射方案问询
解决方案:Spring Boot中DTO与实体字段不一致的排序映射问题
1. 能否对排序属性名进行映射?
完全可以,有两种常用实现方式:
方式一:自定义Sort转换器
实现Spring的Converter<String, Sort>接口,解析请求中的sort参数,将DTO字段名替换为对应实体字段名:
@Component public class UserSortConverter implements Converter<String, Sort> { // 手动维护DTO到实体的字段映射 private static final Map<String, String> FIELD_MAPPING = Map.of( "userName", "name" ); @Override public Sort convert(String source) { if (source == null || source.isBlank()) { return Sort.unsorted(); } return Arrays.stream(source.split(",")) .map(sortPart -> { String[] parts = sortPart.split(":"); String dtoField = parts[0]; Sort.Direction direction = parts.length > 1 ? Sort.Direction.fromString(parts[1]) : Sort.Direction.ASC; // 替换为实体字段名 String entityField = FIELD_MAPPING.getOrDefault(dtoField, dtoField); return new Sort.Order(direction, entityField); }) .collect(Sort::by, Sort.Order::with, Sort::and); } }
然后在Web配置类中注册这个转换器:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private UserSortConverter userSortConverter; @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(userSortConverter); } }
方式二:自定义Pageable参数解析器
继承PageableHandlerMethodArgumentResolver,重写参数解析逻辑,修改Sort中的字段名:
@Component public class CustomPageableResolver extends PageableHandlerMethodArgumentResolver { private static final Map<String, String> FIELD_MAPPING = Map.of( "userName", "name" ); @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Pageable pageable = (Pageable) super.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory); if (pageable.getSort().isEmpty()) { return pageable; } // 替换排序字段 Sort mappedSort = pageable.getSort().map(order -> { String entityField = FIELD_MAPPING.getOrDefault(order.getProperty(), order.getProperty()); return new Sort.Order(order.getDirection(), entityField); }); return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), mappedSort); } }
替换默认的Pageable解析器:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private CustomPageableResolver customPageableResolver; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.removeIf(resolver -> resolver instanceof PageableHandlerMethodArgumentResolver); resolvers.add(customPageableResolver); } }
2. 能否复用MapStruct的映射关系处理排序?
可以,MapStruct生成的Mapper会保留映射元数据,我们可以通过这些元数据自动获取字段映射关系,无需手动维护映射表:
步骤1:确保MapStruct Mapper定义包含映射规则
@Mapper(componentModel = "spring") public interface UserMapper { UserResponse toUserResponse(UserEntity entity); // 定义反向映射,用于提取字段对应关系 @Mapping(source = "userName", target = "name") UserEntity toUserEntity(UserResponse dto); }
步骤2:编写工具类提取MapStruct映射元数据
@Component public class MapStructSortMappingUtil { private final Map<Class<?>, Map<String, String>> dtoToEntityMappings = new HashMap<>(); @Autowired public MapStructSortMappingUtil(List<Mapper> mappers) { for (Mapper mapper : mappers) { // 获取Mapper接口的泛型参数(DTO类型和实体类型) Type[] genericTypes = ((ParameterizedType) mapper.getClass().getGenericInterfaces()[0]).getActualTypeArguments(); Class<?> dtoType = (Class<?>) genericTypes[0]; Class<?> entityType = (Class<?>) genericTypes[1]; Map<String, String> fieldMap = new HashMap<>(); // 提取所有映射规则 for (Mapping mapping : mapper.getMappings()) { if (mapping.getSource() != null && mapping.getTarget() != null) { fieldMap.put(mapping.getSource(), mapping.getTarget()); } } dtoToEntityMappings.put(dtoType, fieldMap); } } // 根据DTO类型和字段名获取实体字段名 public String getEntityField(Class<?> dtoType, String dtoField) { Map<String, String> mapping = dtoToEntityMappings.get(dtoType); return mapping != null ? mapping.getOrDefault(dtoField, dtoField) : dtoField; } }
步骤3:集成到Pageable解析器中
修改之前的CustomPageableResolver,用工具类替换手动映射:
@Component public class CustomPageableResolver extends PageableHandlerMethodArgumentResolver { private final MapStructSortMappingUtil sortMappingUtil; @Autowired public CustomPageableResolver(MapStructSortMappingUtil sortMappingUtil) { this.sortMappingUtil = sortMappingUtil; } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Pageable pageable = (Pageable) super.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory); if (pageable.getSort().isEmpty()) { return pageable; } // 自动映射UserResponse字段到UserEntity Sort mappedSort = pageable.getSort().map(order -> { String entityField = sortMappingUtil.getEntityField(UserResponse.class, order.getProperty()); return new Sort.Order(order.getDirection(), entityField); }); return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), mappedSort); } }
这样当你修改MapStruct的映射规则时,排序字段的映射会自动同步,无需手动更新映射表。
内容的提问来源于stack exchange,提问作者Robert Strauch
相关产品推荐
相关产品推荐

