1. 概述

默认情况下,MongoDB引擎在排序提取的数据时会考虑字符的大小写。可以通过指定聚合(Aggregations)或排序规则(Collations)来执行不区分大小写的排序查询。

在这个简短的教程中,我们将分别使用MongoDB Shell和Java来探讨这两种解决方案。

2. 环境设置

首先,我们需要运行一个MongoDB服务器。让我们使用Docker镜像:

$ docker run -d -p 27017:27017 --name example-mongo mongo:latest

这将创建一个名为“example-mongo”的临时Docker容器,暴露出端口27017。接下来,我们需要创建一个基本的Mongo数据库,并插入用于测试的所需数据。

首先,让我们在容器内打开Mongo Shell:

$ docker exec -it example-mongo mongosh

一旦进入Shell,切换到名为“sorting”的数据库:

> use sorting

最后,我们在一些文档的naming字段插入了相似的值。唯一的区别是首字母的大小写。此时,数据库已创建并数据已正确插入,我们准备进行操作。

3. 默认排序

现在运行标准查询,不进行任何定制:

> db.getCollection('users').find({}).sort({name:1})

返回的数据将按照大小写顺序排列。例如,这意味着大写字母“B”将被视为在小写字母“a”之前:

[
  {
    _id: ..., name: 'Aen', surname: 'Not'
  },
  {
    _id: ..., name: 'Ben', surname: 'Matter'
  },
  {
    _id: ..., name: 'aen', surname: 'Does'
  },
  {
    _id: ..., name: 'ben', surname: 'ThisField'
  }
]

接下来,我们将看看如何使排序不区分大小写,以便Ben和 ben能一起出现。

4. 在Mongo Shell中的不区分大小写排序

4.1. 使用Collation进行排序

让我们尝试使用MongoDB Collation。它从MongoDB 3.4及更高版本开始可用,允许根据特定语言的规则进行字符串比较。

*Collation的ICU locale参数决定了数据库如何进行排序。* 我们使用英语(“en”locale

> db.getCollection('users').find({}).collation({locale: "en"}).sort({name:1})

这将使名称按字母分组:

[
  {
    _id: ..., name: 'aen', surname: 'Does'
  },
  {
    _id: ..., name: 'Aen', surname: 'Not'
  },
  {
    _id: ..., name: 'ben', surname: 'ThisField'
  },
  {
    _id: ..., name: 'Ben', surname: 'Matter'
  }
]

4.2. 使用Aggregation进行排序

现在让我们使用聚合功能:

> db.getCollection('users').aggregate([{
        "$project": {
            "name": 1,
            "surname": 1,
            "lowerName": {
                "$toLower": "$name"
            }
        }
    },
    {
        "$sort": {
            "lowerName": 1
        }
    }
])

通过使用project功能,我们添加了一个lowerName字段,它是naming字段的小写版本。这样我们可以使用该字段进行排序。这将返回一个对象,其中包含我们想要的排序顺序的额外字段:

[
  {
    _id: ..., name: 'aen', surname: 'Does', lowerName: 'aen'
  },
  {
    _id: ..., name: 'Aen', surname: 'Not', lowerName: 'aen'
  },
  {
    _id: ..., name: 'ben', surname: 'ThisField', lowerName: 'ben'
  },
  {
    _id: ..., name: 'Ben', surname: 'Matter', lowerName: 'ben'
  }
]

5. 使用Java进行不区分大小写排序

现在让我们尝试在Java中实现相同的方法。

5.1. 配置基础代码

首先,添加mongo-java-driver依赖:

<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongo-java-driver</artifactId>
    <version>3.12.10</version>
</dependency>

然后,使用MongoClient连接:

MongoClient mongoClient = new MongoClient();
MongoDatabase db = mongoClient.getDatabase("sorting");
MongoCollection<Document> collection = db.getCollection("users");

5.2. 在Java中使用Collation进行排序

让我们看看如何在Java中实现“Collation”解决方案:

FindIterable<Document> nameDoc = collection.find().sort(ascending("name"))
  .collation(Collation.builder().locale("en").build());

这里,我们使用了“en” locale构建了Collation。接着,我们将创建的*Collation对象传递给FindIterable对象的collation*方法。

接下来,我们可以使用*MongoCursor*逐个读取结果:

MongoCursor cursor = nameDoc.cursor();
List expectedNamesOrdering = Arrays.asList("aen", "Aen", "ben", "Ben", "cen", "Cen");
List actualNamesOrdering = new ArrayList<>();
while (cursor.hasNext()) {
    Document document = cursor.next();
    actualNamesOrdering.add(document.get("name").toString());
}
assertEquals(expectedNamesOrdering, actualNamesOrdering);

5.3. 在Java中使用Aggregation进行排序

我们也可以使用Aggregation对集合进行排序。让我们使用Java API重写命令行版本。

首先,我们依赖于project方法创建一个*Bson对象。这个对象还将包括由名字中的每个字符转换为小写使用Projections类计算的lowerName*字段:

Bson projectBson = project(
  Projections.fields(
    Projections.include("name","surname"),
    Projections.computed("lowerName", Projections.computed("$toLower", "$name"))));

接下来,我们将包含上一步中Bson对象的列表传递给aggregate方法和sort方法:

AggregateIterable<Document> nameDoc = collection.aggregate(
  Arrays.asList(projectBson,
  sort(Sorts.ascending("lowerName"))));

同样,在这个案例中,我们也可以轻松地使用*MongoCursor*读取结果。

6. 总结

在这篇文章中,我们学习了如何对MongoDB集合进行简单的不区分大小写的排序。

我们使用了MongoDB Shell中的聚合和排序规则,并将这些查询转化为Java代码,使用了mongo-java-driver库。如往常一样,文章的完整源代码可以在GitHub上找到。