如何为Spring Data REST所有仓库统一添加查询端点,无需重复编写控制器?
通用Spring Data REST查询端点实现方案
核心思路
借助Spring Data REST的RepositoryRestController结合Spring泛型、动态代理能力,编写通用控制器,通过实体元数据适配所有仓库,避免为每个实体编写重复控制器代码。
实现步骤
1. 确认基础仓库接口
确保自定义基础仓库包含目标查询方法:
public interface QueryableRepository<T, ID extends Serializable> extends JpaRepository<T, ID> { Page<T> findAllByQuery(String query, Pageable pageable); }
2. 编写通用RepositoryRestController
创建泛型控制器,动态匹配所有实体仓库,处理/api/{entity}/query和/api/{entity}/search/query请求:
@RepositoryRestController public class GenericQueryController<T, ID extends Serializable> { private final QueryableRepository<T, ID> repository; private final EntityInformation<T, ID> entityInfo; private final RepositoryRestConfiguration restConfig; // Spring自动注入对应实体的仓库与元数据 public GenericQueryController(QueryableRepository<T, ID> repository, EntityInformation<T, ID> entityInfo, RepositoryRestConfiguration restConfig) { this.repository = repository; this.entityInfo = entityInfo; this.restConfig = restConfig; } // 处理/api/{entity}/query端点 @GetMapping("/{entity}/query") public ResponseEntity<Resources<Resource<T>>> handleQuery( @PathVariable String entity, @RequestParam String query, Pageable pageable, PersistentEntityResourceAssembler assembler) { // 校验请求实体与当前控制器处理实体是否匹配 String entityPath = restConfig.getResourceMappingForDomainType(entityInfo.getJavaType()).getPath(); if (!entity.equals(entityPath)) { return ResponseEntity.notFound().build(); } Page<T> result = repository.findAllByQuery(query, pageable); return ResponseEntity.ok(assembler.toResources(result)); } // 处理/api/{entity}/search/query端点(兼容Spring Data REST搜索规范) @GetMapping("/{entity}/search/query") public ResponseEntity<Resources<Resource<T>>> handleSearchQuery( @PathVariable String entity, @RequestParam String query, Pageable pageable, PersistentEntityResourceAssembler assembler) { return handleQuery(entity, query, pageable, assembler); } }
3. 注册通用控制器实例
通过配置类遍历所有QueryableRepository实例,为每个实体生成对应的控制器实例:
@Configuration public class GenericQueryConfig { @Autowired private ListableBeanFactory beanFactory; @Autowired private RepositoryRestConfiguration restConfig; @PostConstruct public void registerControllers() { Map<String, QueryableRepository<?, ?>> repositories = beanFactory.getBeansOfType(QueryableRepository.class); for (QueryableRepository<?, ?> repo : repositories.values()) { // 解析仓库对应的实体类型 ParameterizedType paramType = (ParameterizedType) repo.getClass().getGenericInterfaces()[0].getGenericSuperclass(); Class<?> entityClass = (Class<?>) paramType.getActualTypeArguments()[0]; // 创建并注册控制器实例 EntityInformation<?, ?> entityInfo = beanFactory.getBean(EntityInformation.class, entityClass); GenericQueryController<?, ?> controller = new GenericQueryController<>(repo, entityInfo, restConfig); beanFactory.registerSingleton(entityClass.getSimpleName() + "QueryController", controller); } } }
4. 关键注意事项
- 确保
findAllByQuery方法的参数名与请求参数query一致,Spring会自动完成参数绑定。 - 若仓库有自定义实现逻辑,需保证
QueryableRepository的实现类正确重写findAllByQuery方法。 - 通过
restConfig.getResourceMappingForDomainType()获取实体的REST路径,确保请求路径匹配配置。
简化方案:直接暴露查询方法
若无需自定义端点路径,可通过RepositoryRestConfigurer强制暴露findAllByQuery方法:
@Configuration public class RepositoryRestConfig implements RepositoryRestConfigurer { @Override public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) { config.getRepositoryMappings().forEach(mapping -> { mapping.getExportedQueries().put("findAllByQuery", ExportedQuery.of("findAllByQuery")); }); } }
注:此方式会将方法暴露为
/api/{entity}/search/findAllByQuery,若要改为query路径,仍需结合通用控制器实现。
内容的提问来源于stack exchange,提问作者Denis
相关产品推荐
相关产品推荐

