- 内容提要
- 序
- 前言
- 第一部分 背景知识
- 第 1 章 Spring Data 项目
- 第 2 章 Repository:便利的数据访问层
- 第 3 章 使用 Querydsl 实现类型安全的查询
- 第二部分 关系型数据库
- 第 4 章 JPA Repository
- 第 5 章 借助 Querydsl SQL 实现类型安全的 JDBC 编程
- 第三部分 NoSQL
- 第 6 章 MongoDB: 文档存储
- 第 7 章 Neo4j:图数据库
- 第 8 章 Redis:键/值存储
- 第四部分 快速应用开发
- 第 9 章 使用 Spring Roo 实现持久层
- 第 10 章 REST Repository 导出器
- 第五部分 大数据
- 第 11 章 Spring for Apache Hadoop
- 第 12 章 使用 Hadoop 分析数据
- 第 13 章 使用 Spring Batch 和 Spring Integration 创建大数据管道
- 第六部分 数据网格
- 第 14 章 分布式数据网格:GemFire
- 关于封面
2.2 定义查询方法
2.2.1 查找查询的策略
刚才看到的接口只声明了一个简单的查询方法。声明的方法会被基础设施探测到并进行解析,最终衍生出与存储相关的查询。但是,随着查询变得更加复杂,方法名会变得很冗长,显得很笨拙。对于更复杂的查询,依靠方法解析器所支持的关键字就不够了。因此,每种存储模块都提供了 @Query 注解,如示例 2-8 所示,它会接受存储相关的查询语言所支持的查询字符串,从而允许查询执行时进一步地定制化。
示例 2-8 使用 @Query 注解手动定义查询
在这里,我们使用 JPA 作为例子并手动定义了一个查询,当然这个查询原本也是可以通过衍生得到的。
查询甚至可以外部化配置到属性文件中(它位于 MATA-INF 目录下的$store-named- queries.properties 文件中),在这里$store 是用于替换 jpa、mongo 以及 neo4j 等的占位符。key 值必须要遵循$domainType.$methodName 这样的约定。因此,为了将我们已有的方法替换成外部配置的命名查询,key 将会是 Customer.findByEmailAddress。如果是使用已命名查询的话,那就不需要使用 @Query 注解了。
2.2.2 衍生查询
如示例 2-9 所示,查询衍生机制内置于 Spring Data Repository 的基础设施之中,对基于 Repository 的实体来构建限制性的查询很有用处。我们会从方法中截取 findBy、readBy 以及 getBy 前缀并解析剩余的部分。一个基础的用法是,基于实体的属性来定义条件并使用 And 和 Or 将它们连接起来。
示例 2-9 由方法名衍生查询
解析的实际结果依赖于我们所使用的数据存储。这里也有一些需要注意的通用事项。表达式通常会是属性的遍历以及操作符,它们可以连接起来。如示例 2-9 所示的那样,可以通过 And 以及 Or 来连接属性表达式。除此之外,对于属性表达式来说,还可以支持各种操作符,如 Between、LessThan、GreaterThan 以及 Like。因为不同的数据存储之间所支持的操作符有所区别,所以要看查阅每种存储对应的章节。
属性表达式
属性表达式可以直接引用所管理实体的属性(如示例 2-9 中所示)。在查询创建的时候,我们已经确保所解析的属性就是领域类的属性。但是,依然可以遍历嵌套的属性来定义限制条件。在前面我们可以看到,Customer 的 Address 中具有 ZipCode 属性。在这种情况下,如下这种方法名的查询将会创建 x.address.zipCode 这样的属性遍历。
这个方案的算法首先会将整体(AddressZipCode)作为一个属性进行解析并检查领域类中是否具有该名称的属性(第一个字母小写)。如果它存在的话,就会使用该属性。如果不存在,它会从右边开始将源信息按照“驼峰”命名的规则将其拆分为头部和尾部,然后尝试查找对应的属性(如 AddressZip 和 Code)。如果按照这个头部信息找到了属性,那么我们将会使用尾部的信息继续往下构建树形的信息。因为示例中,第一次的分割并不匹配,我们将分割点继续左移(从“AddressZip、Code”移到“Address、ZipCode”)。
尽管这在大多数的场景下都是可行的,但是在一定情况下算法可能会选择错误的属性。假设 Customer 同时还有一个 addressZip 属性。那么我们的算法将会在第一次分割的时候就完成了匹配,这实际上选择了错误的属性,并且会导致最终的失败(因为 addressZip 类型可能并没有 code 属性)。为了解决这种模棱两可的问题,可以在方法名中使用下划线(_)来手动定义遍历点。所以,我们的方法名最终看起来是这样的:
2.2.3 分页和排序
如果查询所返回的结果数量增长很明显,那么分块访问数据就很有意义了。为了做到这一点,Spring Data 提供了可与 Repository 一起使用的分页 API。要读取哪一块数据的定义隐藏在 Pageable 接口及其实现 PageRequest 之中。得到的分页数据存放在 Page 中,它不仅包含了数据本身,还包含了元信息,这些信息包括它是不是第一页或最后一页以及一共有多少页等。为了计算这个元数据,除了初始的查询外,我们需要触发第二次查询。
借助于 Repository,我们要使用分页功能时只需添加一个 Pageable 方法参数即可。不像其他的参数那样,这个参数是不与查询绑定的,用来限制返回的结果集。一种可选的方案就是返回 Page 类型,它会对结果集进行限制,但是需要另外一次查询来获取元信息(如可用元素的总数)。另一种可选的方案是使用 List,它会避免额外的查询,但是不会提供元数据。如果不需要分页功能,只是想要排序,那么可以给方法签名上添加 Sort 参数,如示例 2-10 所示。
示例 2-10 使用 Pageable 和 Sort 的查询方法
第一个方法允许传递 Pageable 实例到查询方法中,从而为静态定义的查询动态地增加分页功能。排序的功能可以通过 Sort 参数显式地传递给方法,也可以内嵌到 PageRequest 值对象中,如示例 2-11 所示。
示例 2-11 使用 Pageable 和 Sort
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论