我正在尝试为数据库表实现正确的搜索功能。我尝试了这种方法:
控制器:
@GetMapping public Page<TransactionDTO> find(TransactionFilterDTO filter, Pageable page) { return searchRepository .findTransactionsByFilter(mapper.toFilter(filter), page) .map(mapper::toDTO); }
Filer DTO:
public class TransactionFilterDTO { private String name; private Integer id; @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) private LocalDateTime from; @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) private LocalDateTime to; ... // getters and setter }
搜索实现:
@Repository public class TransactionSearchRepositoryImpl implements TransactionSearchRepository { @Autowired private TransactionRepository transactionRepository; @Autowired private TransactionSpecification specification; @Override public Page<Transaction> findTransactionsByFilter(TransactionFilter filter, @Nullable Pageable page) { List<Transaction> transactions = transactionRepository .findAll(specification.getFilter(filter)); int totalCount = transactions.size(); if(page != null) { transactions = transactions .stream() .skip(page.getOffset()) .limit(page.getPageSize()) .collect(Collectors.toList()); } return new PageImpl<>(transactions, page, totalCount); } }
仓库:
public interface TransactionSearchRepository { Page<Transaction> findTransactionsByFilter(TransactionFilter filter, Pageable page); }
有没有更好的方法来实现搜索功能?我认为这种解决方案非常难看。
我可以推荐几种解决此任务的方法:
1)简单的,但不灵活:在控制器中使用刚请求参数根据自己的滤波器特性(name,from,to),并且在回购制备相应的查询,例如:
name
from
to
@GetMapping("/q") public List<ResponseDto> getAllByQuery( @RequestParam(value = "name", required = false) String name, @RequestParam(value = "from", required = false) @DateTimeFormat(iso = ISO.DATE) LocalDate from, @RequestParam(value = "to", required = false) @DateTimeFormat(iso = ISO.DATE) LocalDate to, Pageable pageable ) { return service.getByQuery(name, from, to, pageable); }
服务:
public Page<ResponseDto> getByQuery(String name, LocalDate from, LocalDate to, Pageable pageable) { return repo.getByQuery(name, from, to, pageable).map(mapper::toResponseDto); }
@Query("select m from MyEntity m where " + "(?1 is null or upper(m.name) like concat('%', upper(?1), '%')) " + "and (?2 is null or m.createdAt >= ?2) " + "and (?3 is null or m.createdAt <= ?3)") Page<MyEntity> getByQuery(String name, final LocalDate from, final LocalDate to, final Pageable pageable);
然后执行一个请求:
GET http://localhost:8080/q?name=john&from=2019-04-19&to=2019-04-19
2)使用 QueryDsl 。您应该将其添加到您的项目中(您可以在此处找到详细信息),从QuerydslPredicateExecutor和扩展您的仓库QuerydslBinderCustomizer,向其中添加一些“调整”:
QuerydslPredicateExecutor
QuerydslBinderCustomizer
public interface MyEntityRepo extends JpaRepository<MyEntity, Integer>, QuerydslPredicateExecutor<MyEntity>, QuerydslBinderCustomizer<QMyEntity> { @Override default void customize(@NonNull QuerydslBindings bindings, @NonNull QMyEntity entity) { // Make case-insensitive 'like' filter for all string properties bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase); // Add 'between' and 'greater or equal' filter date property bindings.bind(entity.createdAt).all((path, value) -> { Iterator<? extends LocalDate> it = value.iterator(); LocalDate from = it.next(); if (value.size() >= 2) { LocalDate to = it.next(); return Optional.of(path.between(from, to)); // between } else { return Optional.of(path.goe(from)); // greater than or equal } }); }
添加服务方式:
public Page<ResponseDto> getAllByQueryDsl(Predicate predicate, Pageable pageable) { return repo.findAll(predicate, pageable).map(mapper::toResponseDto); }
添加控制器方法:
@GetMapping("/query-dsl") public Page<ResponseDto> getAllByQueryDsl( @QuerydslPredicate(root = MyEntity.class, bindings = MyEntityRepo.class) Predicate predicate, Pageable pageable ) { return service.getAllByQueryDsl(predicate, pageable); }
并将@DateTimeFormat注释添加到实体的“日期”属性中:
@DateTimeFormat
@Entity public class MyEntity { // ... @DateTimeFormat(iso = ISO.DATE) private LocalDate createdAt; }
然后,您可以执行这样的请求:
GET http://localhost:8080/query-dsl?name=john&createdAt=2019-04-15&createdAt=2019-04-19
其中第一个日期是“ from”参数,第二个日期是“ to”参数。如果您仅使用一个日期-它将是’from’参数(大于或等于)。
3)使用 Specification-arg-resolver 库。将其添加到您的项目中(请参阅说明:1和2),然后从JpaSpecificationExecutor以下位置扩展您的存储库:
JpaSpecificationExecutor
public interface MyEntityRepo extends JpaRepository<MyEntity, Integer>, JpaSpecificationExecutor<MyEntity> {}
将这样的方法添加到您的控制器中:
@GetMapping("/specification") public Page<ResponseDto> getAllBySpecification( @And({ @Spec(path = "name", spec = LikeIgnoreCase.class), @Spec(path = "createdAt", params = "from", spec = GreaterThanOrEqual.class), @Spec(path = "createdAt", params = "to", spec = LessThanOrEqual.class) }) Specification<MyEntity> specification, Pageable pageable ) { return service.getAllBySpecification(specification, pageable); }
更新您的服务:
public Page<ResponseDto> getAllBySpecification(final Specification<MyEntity> specification, final Pageable pageable) { return repo.findAll(specification, pageable).map(mapper::toResponseDto); }
然后请求您的数据:
GET http://localhost:8080/specification?name=john&from=2019-04-10&to=2019-04-19
4)手动建立规范:
创建一个过滤器类:
@Data public class MyFilter implements Specification<MyEntity> { private String name; @DateTimeFormat(iso = ISO.DATE) private LocalDate from; @DateTimeFormat(iso = ISO.DATE) private LocalDate to; @Override public Predicate toPredicate(Root<MyEntity> root, CriteriaQuery<?> query, CriteriaBuilder builder) { List<Predicate> predicates = new ArrayList<>(); if (name != null) predicates.add(builder.like(builder.upper(root.get("name")), "%" + name.toUpperCase() + "%")); if (from != null) predicates.add(builder.greaterThanOrEqualTo(root.get("createdAt"), from)); if (to != null) predicates.add(builder.lessThanOrEqualTo(root.get("createdAt"), to)); return builder.and(predicates.toArray(new Predicate[0])); } }
创建一个控制器方法:
@GetMapping("/filter") public Page<ResponseDto> getAllByMyFilter(MyFilter filter, Pageable pageable) { return service.getAllBySpecification(filter, pageable); }
然后运行请求:
GET http://localhost:8080/filter?name=john&from=2019-04-10&to=2019-04-19