20-08-02
banq
搜索和过滤是可以对数据集执行的最简单的操作之一。简而言之:过滤器类似于where典型数据库查询中的子句。
例如,考虑以下实体及其对应的dao存储库:
@Data @Entity public class User { @Id @GeneratedValue private Long id; private String name; @ManyToOne @JoinColumn(name = "account_id", updatable = false) private Account account; } @Repository public interface UserRepository extends JpaRepository<User, Long> { List<User> findByAccountId(Long accountId); List<User> findByName(String name); List<User> findByAccountIdAndName(Long accountId, String name); } |
正如我们所看到的,对于每个字段,只有一个查询用于对该特定字段进行过滤,并具有与所有其余字段的所有可能组合的查询。因此,在此实现中,例如,如果添加了新字段groupId,我们将不得不为所有可能的新组合添加大量查询。
幸运的是,这个O(n)问题可借助于Spring Data JPA 的Specification 接口,可以解决上述具有指数复杂性的问题。我们将看到如何将这个简单而有效的框架集成到我们的应用程序中。
可以将规格/规范(Specification )视为类似于where数据库查询的子句。第一部分包括创建规范接口的通用实现,该接口将能够处理通常用于过滤数据集的不同类型的操作。
@Slf4j public class GenericSpecification<T> implements Specification<T> { private SearchCriteria searchCriteria; public GenericSpecification(final SearchCriteria searchCriteria){ super(); this.searchCriteria = searchCriteria; } @Override public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { List<Object> arguments = searchCriteria.getArguments(); Object arg = arguments.get(0); switch (searchCriteria.getSearchOperation()) { case EQUALITY: return criteriaBuilder.equal(root.get(searchCriteria.getKey()), arg); case GREATER_THAN: return criteriaBuilder.greaterThan(root.get(searchCriteria.getKey()), (Comparable) arg); ... case IN: return root.get(searchCriteria.getKey()).in(arguments); } } } @Data public class SearchCriteria { private String key; private SearchOperation searchOperation; private boolean isOrOperation; private List<Object> arguments; } public enum SearchOperation { EQUALITY, NEGATION, GREATER_THAN, LESS_THAN, LIKE, STARTS_WITH, ... } |
显而易见,该代码GenericSpecificationsBuilder基于Builder设计模式,该模式添加了规范,并在build函数中创建了一个统一的规范,将所有不同的标准组合到一个谓词中。
尽管我们可以直接使用上面的构建器,但是我也创建了一个工厂,该工厂包装了引擎盖支持的所有可能的搜索操作。
@Component public class SpecificationFactory<T> { public Specification<T> isEqual(String key, Object arg) { GenericSpecificationsBuilder<T> builder = new GenericSpecificationsBuilder<>(); return builder.with(key, SearchOperation.EQUALITY, Collections.singletonList(arg)).build(); } ... public Specification<T> isGreaterThan(String key, Comparable arg) { GenericSpecificationsBuilder<T> builder = new GenericSpecificationsBuilder<>(); return builder.with(key, SearchOperation.GREATER_THAN, Collections.singletonList(arg)).build(); } } |
现在,如果我们需要基于新条件添加过滤器,我们要做的就是将规范条件包装在另一个if语句中。
缺点:
使用上述实现的缺点之一是我们不再具有类型安全性。例如,如果发生以下情况,将抛出运行时异常:
- 参数不匹配:例如,将类型的字段int传递一个String值作为参数。
- 找不到字段:规范基于实体中不存在的字段。
结论
搜索和过滤问题对于所有现代应用程序都是微不足道的,并且Spring Data JPA规范提供了一种简洁而优雅的方法来创建动态查询。请分享您对如何解决搜索和过滤问题的想法和建议。