我在 Spring Boot 项目中使用 Spring-data-mongodb 存储库框架。我有一个 API,用户可以在搜索对象时指定 1+ 个查询参数,我想使用
@Query
注释将这些查询参数转换为搜索条件。
由于搜索字段的数量不会一致,因为用户可以指定不同数量的查询参数,因此我无法像官方文档示例所示那样对字段名称进行硬编码。我尝试将一些 SpEl 字符串放在一起,以在
@Query
注释中对条件进行“动态排除”,但是 Spring 正在转义我返回的字符串,因此我无法将查询零碎地连接在一起。有没有办法单独使用 @Query
注释来做到这一点?
(我不想声明存储库扩展,编写调用 Spring Mongo DSL 方法的 Java 代码,也不想使用
QueryByExample
框架。我只想坚持使用 @Query
注释)
虚拟示例代码:
BookRepo.java
:
@Repository
public interface BookRepo extends MongoRepository<Book, String> {
// Want to make the fields dynamic in this query so that if "author" or "publisher" is null/blank, then it's not included in the query
@Query("{'author': ?0, 'publisher': ?1}")
List<Book> getAllBooksBy(String author, String publisher);
}
这似乎没有在 Spring-data-mongodb 存储库框架文档中公开记录,但您可以将整个查询转换为 SpEl 表达式,以便可以动态构造查询。
SpEl 有地图的概念,写法如下:
{author: 'Joe'}
。映射会在 Spring Mongo 存储库的 @Query
注释中转换为标准 json 查询,因此您可以定义一个小辅助方法来接收 SpEl 映射,过滤掉空白,并返回原始 @Query
注释的过滤后的映射用于查询。
首先我们定义一个 bean,其方法是过滤掉地图中的空白:
QueryHelper.java
:
@Component("qHelper")
public class QueryHelper {
/**
* Removes blank values from the given map
*/
public Map<String, Object> noBlanks(Map<String, Object> params) {
Map<String, Object> filteredMap = new HashMap<>(params);
for (Map.Entry<String, Object> entry : params.entrySet()) {
if (StringUtils.isBlank(entry.getValue().toString())) {
filteredMap.remove(entry.getKey());
}
}
return filteredMap;
}
}
然后,我们调整
@Query
注释以 ?#{
开头,并使用填充的所有查询字段的预初始化映射来调用我们的查询帮助器方法:
BookRepo.java
:
@Repository
public interface BookRepo extends MongoRepository<Book, String> {
@Query("?#{@qHelper.noBlanks( {author: [0], publisher: [1]} )}")
List<Book> getAllBookBy(String author, String publisher);
}
我们可以使用这个参数运行我们的 Spring Boot 服务器:
-Dlogging.level.org.springframework.data.mongodb.core.MongoTemplate=DEBUG
它会打印出当作者为空时实际执行的查询:
find using query: { "publisher" : "Random House"} fields: Document{{}} for class...
不错!这是非常强大的,因为您可以将
QueryHelper
重复用于具有可选参数的其他查询。这也适用于 project
注释的 @Query
参数,这意味着您可以根据用户传递给 API 的内容动态地将理论上的“过滤器”字符串数组转换为投影图。
技术说明:触发此行为的代码似乎位于 ParameterBindingJsonReader 中,其定义了以下正则表达式:
private static final Pattern ENTIRE_QUERY_BINDING_PATTERN = Pattern.compile("^\\?(\\d+)$|^[\\?:]#\\{.*\\}$");