从 3.4 版开始,MongoDB 支持用于集合和索引创建以及各种查询操作的排序规则。排序规则根据 ICU 排序规则定义字符串比较规则,排序规则文档由封装在排序规则中的各种属性组成,如下所示:
Collation collation = Collation.of("fr") // (1) .strength(ComparisonLevel.secondary() // (2) .includeCase()) .numericOrderingEnabled() // (3) .alternate(Alternate.shifted().punct()) // (4) .forwardDiacriticSort() // (5) .normalizationEnabled(); // (6)
其中:
(1)排序规则的创建需要一个区域,区域可以是字符串表示,或者是 Locale 或 CollationLocale 对象。注意:创建排序规则时必须设置区域。
(2)排序强度定义了表示字符之间差异的比较级别,您可以根据所选的强度配置各种选项(如:区分大小写、按大小写顺序等)。
(3)指定是将数字字符串作为数字还是字符串进行比较。
(4)指定排序规则是否应将空白和标点符号作为比较的基础字符。
(5)指定带有变音符的字符串是否从字符串的后面排序,例如使用某些法语字典排序。
(6)指定是否检查文本是否需要规范化以及是否执行规范化。
排序规则可用于创建集合和索引。如果创建指定排序规则的集合,则排序规则将应用于索引创建和查询,除非您指定了其他排序规则。排序规则对整个操作有效,不能基于每个字段指定。
与其他元数据一样,排序规则可以通过 @Document 注解的 collation 属性从领域类型派生出来,并将在运行查询、创建集合或索引时直接应用。
注意:当 MongoDB 在第一次交互时自动创建集合时,将不使用带注解的排序规则。这将需要额外的存储交互来延迟整个过程。对于这些情况,请使用MongoOperations.createCollection。例如:
Collation french = Collation.of("fr"); Collation german = Collation.of("de"); template.createCollection(Person.class, CollectionOptions.just(collation)); template.indexOps(Person.class).ensureIndex(new Index("name", Direction.ASC).collation(german));
注意:MongoDB 在没有指定排序规则时,将使用简单的二进制比较规则(collation.simple())。
将排序规则与集合操作一起使用是在查询或操作选项中指定排序规则实例,如以下两个示例所示:
示例1:对查找使用排序规则
// de 表示德国,访问 https://www.hxstrive.com/article/1167.htm 查看更多关于 Local 代码的定义 // 定义索引规则 Collation collation = Collation.of("de"); // 创建查询 Query query = new Query(Criteria.where("firstName").is("Amél")).collation(collation); // 执行查找操作 List<Person> results = template.find(query, Person.class);
示例2:对聚合使用排序规则
// 索引规则 Collation collation = Collation.of("de"); // 将索引规则应用到聚合查询 AggregationOptions options = AggregationOptions.builder().collation(collation).build(); Aggregation aggregation = newAggregation( project("tags"), unwind("tags"), group("tags").count().as("count") ).withOptions(options); // 执行聚合查询 AggregationResults<TagCount> results = template.aggregate(aggregation, "tags", TagCount.class);
警告:仅当用于操作的排序规则与索引排序规则匹配时,才使用索引。
值排序中要执行比较的级别,取值如下:
Primary:排序规则仅执行基本字符的比较,忽略其他差异,例如:重音符号和大小写。因此,å、ä 和 a 都将被视为同一个字符。
Secondary:排序规则执行比较直到次要差异,例如:重音符号。也就是说,基本字符 + 重音符号。请注意,基本字符之间的差异优先于次要差异。
Tertiary(默认级别):排序规则执行比较直到第三级差导,例如大小写和字母变体。也就是说,排序规则执行基本字符、重音符号以及大小写和变体的比较。虽然英语只有大小宜变体,但有些语言有不同但等同的字符,即简化中文与繁体中文。在此级别,基本字符之间的差异优先于重音,而重音优先于大小写和变体差异。
Quaternary:仅限于特定使用案例,以便在级别 1 到 3 忽略标点符号时考虑标点符号或处理日语文本。
Identical:限于仲裁成员(Tie-breaker)的特定使用案例。
用于确定是将数字字符串作为数字还是字符串进行比较:
如果是 “on”,则按数字进行比较; 即 “10” 大于 “2”。
如果是 “off”(默认),则按字符串进行比较; 即 “10” 小于 “2”。
用于确定排序是否应将空格和标点符号视为基本字符以进行比较。它只有两个可能的值:
non-ignorable:空格和标点符号被视为基本字符。
shifted:空格和标点符号不被视为基本字符,仅在强度级别大于 3 时区分。
当 “替代” 设置为 “shifted” 时,此字段确定直到哪些字符可被忽略。当 “替代” 设置为 “non-ignorable” 时,它不起作用。它只有两个可能的值:
punct:空格和标点符号都是 “可忽略的”,即不被视为基础字符。
space:只有空格是 “可忽略的”,即不被视为基本字符。
用于确定带有重音的字符串是否从字符串的后面排序,例如:使用一些法语字典排序。它只有两个可能的值:
如果是 “on”,则从后到前进行比较。
如果是 “off”(默认),则从前到后进行比较。
用于确定是否检查文本是否需要规范化并执行规范化。通常,大多数文本不需要规范化处理。它只有两个可能的值:
如果是 “on”,检查是否完全规范化并执行规范化以比较文本。
如果是 “off”(默认),则不检查。
ICU (International Components for Unicode)是为软件应用提供Unicode和全球化支持的一套成熟、广泛使用的C/C++、Java和.NET 类库集,可在所有平台的C/C++、Java和C# 软件上获得一致的结果,用于支持软件国际化的开源项目, 软件开发者几乎可以使用ICU 解决任何国际化的问题,根据各地的风俗和语言习惯,实现对数字、货币、时间、日期、和消息的格式化、解析,对字符串进行大小写转换、整理、搜索和排序等功能。
ICU的功能主要有:
代码页转换: 对文本数据进行Unicode、几乎任何其他字符集或编码的相互转换。ICU的转化表基于IBM过去几十年收集的字符集数据,在世界各地都是最完整的。
排序规则(Collation): 根据特定语言、区域或国家的管理和标准比较字数串。ICU的排序规则基于Unicode排序规则算法加上来自公共区域性数据仓库(Common locale data repository)的区域特定比较规则。
格式化: 根据所选区域设置的惯例,实现对数字、货币、时间、日期、和利率的格式化。包括将月和日名称转换成所选语言、选择适当缩写、正确对字段进行排序等。这些数据也取自公共区域性数据仓库。
时间计算: 在传统格里历基础上提供多种历法。提供一整套时区计算API。
Unicode支持: ICU紧密跟进Unicode标准,通过它可以很容易地访问Unicode标准制定的很多Unicode字符属性、Unicode规范化、大小写转换和其他基础操作。
正则表达式: ICU的正则表达式全面支持Unicode并且性能极具竞争力。
Bidi: 支持不同文字书写顺序混合文字(例如从左到右书写的英语,或者从右到左书写的阿拉伯文和希伯来文)的处理。
文本边界: 在一段文本内定位词、句或段落位置、或标识最适合显示文本的自动换行位置。
将用户姓名保存为数字字符串,然后自定义排序规则,将数字字符串使用数字进行排序。
(1)Person 实体代码如下:
package com.hxstrive.springdata.mongodb.entity; import lombok.Builder; import lombok.Data; import lombok.ToString; import org.springframework.data.annotation.TypeAlias; import org.springframework.data.annotation.Version; import org.springframework.data.mongodb.core.mapping.Document; /** * 用户 * @author hxstrive.com */ @Document("person") @TypeAlias("pers") @Data @Builder @ToString public class Person extends Contact { /** ID,自动映射到 MongoDB 的 _id 字段 */ private String id; /** 姓名 */ private String name; /** 年龄 */ private Integer age; /** 版本 */ @Version private Long version; }
(2)客户端代码,创建一个中国 Local 排序规则,该排序规则设置强度为 Secondary,且设置将数字字符串作为数字进行比较。代码如下:
package com.hxstrive.springdata.mongodb.query; import com.hxstrive.springdata.mongodb.entity.Person; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import java.util.List; @SpringBootTest public class CollectionQueryTest { @Autowired private MongoTemplate mongoTemplate; @Test public void contextLoads() { // 删除集合,清空数据 mongoTemplate.dropCollection(Person.class); // 准备数据 mongoTemplate.insert(Person.builder().id("1").name("Tom").age(22).build()); mongoTemplate.insert(Person.builder().id("2").name("Helen").age(29).build()); mongoTemplate.insert(Person.builder().id("3").name("Bill").age(37).build()); mongoTemplate.insert(Person.builder().id("4").name("9").age(48).build()); mongoTemplate.insert(Person.builder().id("5").name("2").age(42).build()); mongoTemplate.insert(Person.builder().id("6").name("10").age(40).build()); // 查询数据 // de 表示德国,访问 https://www.hxstrive.com/article/1167.htm 查看更多关于 Local 代码的定义 // 定义索引规则,采用中国 Local Collation collation = Collation.of("zh") // 设置排序规则强度为 Secondary .strength(Collation.ComparisonLevel.secondary().includeCase()) // 将数字字符串作为数字进行比较 .numericOrderingEnabled(); // 创建查询 Query query = new Query(Criteria.where("name").regex(".+")).collation(collation); // 根据 name 属性升序排序 query.with(Sort.by(Sort.Direction.ASC, "name")); // 执行查找操作 List<Person> results = mongoTemplate.find(query, Person.class); for(Person person : results) { System.out.println(person); } } }
运行代码,输出如下:
Person(id=5, name=2, age=42, version=0) Person(id=4, name=9, age=48, version=0) Person(id=6, name=10, age=40, version=0) Person(id=3, name=Bill, age=37, version=0) Person(id=2, name=Helen, age=29, version=0) Person(id=1, name=Tom, age=22, version=0)
上面输出信息中,2 < 9 < 10,这是按照数字进行比较的。如果按照字符串进行比较,则为 10 < 2 < 9。