本节书摘来自异步社区《Spring Data实战》一书中的第2章,第2.2节,作者: 【美】Mark Pollack , Oliver Gierke , Thomas Risberg , Jon Brisbin , Michael Hunger著,更多章节内容可以访问云栖社区“异步社区”公众号查看
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