【问题标题】:Java Spring REST API Handling Many Optional ParametersJava Spring REST API 处理许多可选参数
【发布时间】:2017-04-25 10:28:03
【问题描述】:

出于教学目的,我目前正在处理一个 Spring Boot REST API 项目。我有一个相当大的表,其中 22 列加载到 MySQL 数据库中,并试图让用户能够按多列过滤结果(在本示例中假设为 6)。

我目前正在扩展存储库并已初始化方法,例如 findByParam1 和 findByParam2 和 findByParam1OrderByParam2Desc 等,并已验证它们按预期工作。我对你们的问题是让用户能够利用所有 6 个可选的 RequestParams 而无需编写大量的条件/存储库方法变体的最佳方法。例如,我想让用户能够点击 url home/get-data/ 以获取所有结果, home/get-data?param1=xx 以基于 param1 进行过滤,并且可能, home/get-data?param1=xx&param2=yy...&param6=zz 过滤所有可选参数。

作为参考,这是我的控制器的相关块(大致)的样子。

@RequestMapping(value = "/get-data", method = RequestMethod.GET)
public List<SomeEntity> getData(@RequestParam Map<String, String> params) {
    String p1 = params.get("param1");
    if(p1 != null) {
        return this.someRepository.findByParam1(p1);
    }
    return this.someRepository.findAll();
}

到目前为止,我的问题是我处理这件事的方式意味着我基本上需要 n!我的存储库中支持此功能的方法数量,其中 n 等于我要过滤的字段/列的数量。有没有更好的方法来处理这个问题,也许我正在“就地”过滤存储库,这样我就可以在检查地图以查看用户确实填充了哪些过滤器时简单地过滤“就地”?

编辑:所以我目前正在实施一个可能与 J. West 下面的评论有关的“hacky”解决方案。我假设用户将在请求 URL 中指定所有 n 个参数,如果他们没有指定(例如,他们指定 p1-p4 但没有指定 p5 和 p6),我生成的 SQL 只与 LIKE '%' 的语句匹配不包含的参数。它看起来像......

@Query("select u from User u where u.p1 = :p1 and u.p2 = :p2 ... and u.p6 = :p6") 
List<User> findWithComplicatedQueryAndSuch;

在控制器中,我会检测映射中的 p5 和 p6 是否为空,如果是,只需将它们更改为字符串“%”。我确信有一种更精确和直观的方法可以做到这一点,尽管我还没有找到任何类似的东西。

【问题讨论】:

  • 嗯,这很难。我确信 Spring 支持这样的东西,我只是不知道它到底是什么;我正在查看文档,因为我现在很好奇。作为一个潜在的“hacky”解决方案,您可以只返回整个对象,然后循环遍历并 null 您不想包含的字段。由于您正在映射一个对象,即使您以正确的方式执行此操作,您未从查询中返回的字段将为空。
  • 我不是 100% 确定你的意思是 null 我不想包含的字段,也就是说,这将如何帮助我不必写出 n 阶乘的存储库函数,如 findByP1和 findByP1andP2 和 findByP1andP3 等等?这可能类似于我现在正在实施的 hacky 方法(更新答案),我只是假设用户总是过滤 6 个参数,如果不是,则生成与 LIKE '%' 等效的 SQL 以基本上不过滤根本......这就是你不想包含的字段的意思吗?
  • 我想我误解了你在问什么。看看这个关于创建自定义JpaSpecificationExecutor 的链接,看起来它可能在正确的轨道上。 cubrid.org/wiki_ngrinder/entry/…
  • @fapple 这就是人们使用 QueryDsl querydsl.com 的原因。在 Spring 应用程序中使用它很常见 - 并且易于集成 - spring.io/blog/2011/04/26/…

标签: java mysql rest spring-boot spring-data-jpa


【解决方案1】:

您可以使用JpaSpecificationExecutor 和自定义Specification 轻松做到这一点:https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/

我会将 HashMap 替换为包含所有可选 get 参数的 DTO,然后基于该 DTO 构建规范,显然您也可以保留 HashMap 并基于它构建规范。

基本上:

public class VehicleFilter implements Specification<Vehicle>
{
    private String art;
    private String userId;
    private String vehicle;
    private String identifier;

    @Override
    public Predicate toPredicate(Root<Vehicle> root, CriteriaQuery<?> query, CriteriaBuilder cb)
    {
        ArrayList<Predicate> predicates = new ArrayList<>();

        if (StringUtils.isNotBlank(art))
        {
            predicates.add(cb.equal(root.get("art"), art));
        }
        if (StringUtils.isNotBlank(userId))
        {
            predicates.add(cb.equal(root.get("userId"), userId));
        }
        if (StringUtils.isNotBlank(vehicle))
        {
            predicates.add(cb.equal(root.get("vehicle"), vehicle));
        }
        if (StringUtils.isNotBlank(identifier))
        {
            predicates.add(cb.equal(root.get("identifier"), fab));
        }

        return predicates.size() <= 0 ? null : cb.and(predicates.toArray(new Predicate[predicates.size()]));
    }

// getter & setter
}

还有控制器:

@RequestMapping(value = "/{ticket}/count", method = RequestMethod.GET)
public long getItemsCount(
    @PathVariable String ticket,
    VehicleFilter filter,
    HttpServletRequest request
) throws Exception
{
    return vehicleService.getCount(filter);
}

服务:

@Override
public long getCount(VehicleFilter filter)
{
    return vehicleRepository.count(filter);
}

存储库:

@Repository
public interface VehicleRepository extends JpaRepository<Vehicle, Integer>, JpaSpecificationExecutor<Vehicle>
{
}

只是一个从公司代码改编的简单示例,你明白了!

【讨论】:

  • 啊,看来我是在正确的轨道上。非常有趣,我从来没有这样的用例,但我很高兴我现在知道如果必须要怎么做。
  • 这是一个非常简单和干净的解决方案,用于支持大型查询修改,基本上零开销,因为您可以直接从控制器使用“增强”DTO(不再是真正的 DTO)并将其传递给存储库。
【解决方案2】:

另一种编码更少的解决方案是使用 QueryDsl 与 Spring MVC 的集成。

通过使用这种方法,您的所有请求参数都将自动解析为您的域属性之一并附加到您的查询中。

参考文档https://spring.io/blog/2015/09/04/what-s-new-in-spring-data-release-gosling#querydsl-web-support和示例项目https://github.com/spring-projects/spring-data-examples/tree/master/web/querydsl

【讨论】:

    【解决方案3】:

    如果您的存储库类实现了 JpaRepository 接口,您可以使用按示例查询 (QBE) 技术更轻松地完成此操作,因为该接口实现了提供 QueryByExampleExecutor 接口findAll 方法,将 Example 的对象作为参数。

    使用这种方法确实适用于您的场景,因为您的实体有很多字段,并且您希望用户能够获取那些匹配过滤器的字段,这些字段表示为实体字段的子集,并且它们的对应值必须匹配。

    假设实体是 User (就像在您的示例中一样),并且您想要创建端点以获取其属性值等于指定值的用户。这可以通过以下代码来完成:

    实体类:

    @Entity
    public class User implements Serializable {
        private Long id;
        private String firstName;
        private String lastName;
        private Integer age;
        private String city;
        private String state;
        private String zipCode;
    }
    

    控制器类:

    @Controller
    public class UserController {
        private UserRepository repository;
        private UserController(UserRepository repository) {
            this.repository = repository;
        }
    
        @GetMapping
        public List<User> getMatchingUsers(@RequestBody User userFilter) {
            return repository.findAll(Example.of(userFilter));
        }
    }
    

    存储库类:

    @Repository
    public class UserRepository implements JpaRepository<User, Integer> {
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-04
      • 2014-04-17
      • 1970-01-01
      • 2016-09-21
      • 1970-01-01
      • 2016-04-03
      相关资源
      最近更新 更多