一、問題概述
最近在使用mongodb時遇見了一個問題,因為精度問題,在保存價格的時候使用了字符串!這樣做也一直沒遇見什么問題,只是有一天,突然有個需求,在展示商品的時候需要按照價格排序,結果悲劇了,因為價格是字符串類型的,排序的時候是按照字符串的規則進行排序的,最終導致查詢出來的結果雜亂無章!
二、問題模擬
下面,我們就模擬一下自己遇見的問題。
我們先向數據庫中插入如下數據:
> db.product.insertMany([
... {
... "name":"手機",
... "price":"3000"
... },
...
... {
... "name":"充電器",
... "price":"50"
... },
...
... {
... "name":"耳機",
... "price":"900"
... }
... ]);
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("6219c823ca5ebc1273fd6421"),
ObjectId("6219c823ca5ebc1273fd6422"),
ObjectId("6219c823ca5ebc1273fd6423")
]
}
在這里,我們的插入的數據價格都是字符串類型的,我們來驗證一下:
> db.product.find({
... price:{$type:'string'}
... }).pretty(); # 查詢價格類型為string的記錄
{
"_id" : ObjectId("6219c823ca5ebc1273fd6421"),
"name" : "手機",
"price" : "3000"
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6422"),
"name" : "充電器",
"price" : "50"
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6423"),
"name" : "耳機",
"price" : "900"
}
下面,我們來按照價格進行正序排序:
> db.product.find().sort({"price":1}).pretty(); # 按照價格進行正序排序
{
"_id" : ObjectId("6219c823ca5ebc1273fd6421"),
"name" : "手機",
"price" : "3000"
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6422"),
"name" : "充電器",
"price" : "50"
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6423"),
"name" : "耳機",
"price" : "900"
}
從上面運行的結果中,我們看出直接對價格進行排序是按照字符串的排序規則進行排序的(即從左到右,依次按照每位進行排序)。可是我們如果想要按照數字字符串表示的數字大小進行排序,該怎么辦呢?下面,我將介紹兩種解決方案!
三、解決方案
方案一:使用mongodb的collation功能
collation是mongodb3.4的新特性。這就意味着如果你想要使用collation,mongodb的版本必須要大於等於3.4。
> db.product.find().collation({"locale":"zh", "numericOrdering":true}).sort({"price":1}).pretty();
{
"_id" : ObjectId("6219c823ca5ebc1273fd6422"),
"name" : "充電器",
"price" : "50"
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6423"),
"name" : "耳機",
"price" : "900"
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6421"),
"name" : "手機",
"price" : "3000"
}
在使用了collation之后,我們發現現在的輸出結果是按照價格大小進行排序了
說明:
"locale":"zh"表示漢字按照拼音進行排序"numericOrdering":true表示將字符串類型數字按照數字大小進行排序,設置該屬性時,必須設置locale屬性,否則會報錯。
方案二: 使用聚合查詢
該方案的原理是將查詢出來的結果先轉換為數字,再按照轉換之后的結果進行排序!
> db.product.aggregate([
... {
... $addFields: {
... priceNum: {
... $toDecimal: "$price"
... }
... }
... },
... { $sort: { priceNum: 1 } }
... ]).pretty();
{
"_id" : ObjectId("6219c823ca5ebc1273fd6422"),
"name" : "充電器",
"price" : "50",
"priceNum" : NumberDecimal("50")
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6423"),
"name" : "耳機",
"price" : "900",
"priceNum" : NumberDecimal("900")
}
{
"_id" : ObjectId("6219c823ca5ebc1273fd6421"),
"name" : "手機",
"price" : "3000",
"priceNum" : NumberDecimal("3000")
}
我們發現,使用該種方法也能實現數字字符串的排序!
說明:
$addFields: {priceNum: {$toDecimal: "$price"}}表示將查詢出來的價格轉換為Decimal,並將這個轉換之后的字段命名為priceNum- sort排序一定要按照轉換之后的字段排序
四、解決方案代碼實現
1.項目創建
因為SpringBoot對各種數據源都進行了封裝,如果需要操作mongodb,只需要引入對應的啟動器,添加相關配置即可:
在pom.xml文件中引入相關依賴:
<!-- mongodb啟動器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
在配置文件application.yaml中添加如下配置:
spring:
data:
mongodb:
host: 127.0.0.1
port: 27017
database: test_db
logging:
level:
org.springframework.data.mongodb.core: DEBUG
2.創建相關pojo類
package com.xdw.mongodbtest.pojo;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Field;
@Data
public class Product {
@Id
private String id;
@Field("name")
private String name;
@Field("price")
private String price;
@Field("chainId")
private String chainId;
@Field("content")
private String content;
}
3.兩種方式的java代碼實現
方式一:使用collation
1.編寫查詢方法
public List<Product> getProductList() {
Query query = new Query();
// 設置collation,將字符串數字按照數值處理
Collation collation = Collation.of(Locale.CHINESE).numericOrdering(true);
query.collation(collation);
// 按照價格降序進行排序
query.with(Sort.by(Sort.Order.desc("price")));
return mongoTemplate.find(query, Product.class);
}
2.編寫測試方法
@Test
void testProductQuery() {
List<Product> productList = productService.getProductList();
if(!CollectionUtils.isEmpty(productList)) {
for (Product product : productList) {
System.out.println(product);
}
}
}
3.運行測試

最終的輸出結果果然是按照價格降序排列的。
方式二:使用聚合查詢
1.編寫查詢方法
public List<Product> getProdutByAggregation() {
ProjectionOperation project = Aggregation.project().and("_id").as("id")
.and("name").as("name")
.and(ConvertOperators.ToDecimal.toDecimal("$price")).as("price") // 將查詢出來的價格轉換為decimal
.and("chainId").as("chainId")
.and("content").as("content");
// 按照價格降序進行排序
SortOperation sort = Aggregation.sort(Sort.by(Sort.Order.desc("price")));
return mongoTemplate.aggregate(Aggregation.newAggregation(project, sort), "product", Product.class).getMappedResults();
}
2.編寫測試方法
@Test
void testGetProdutByAggregation() {
List<Product> productList = productService.getProdutByAggregation();
for (Product product : productList) {
System.out.println(product);
}
}
3.運行測試

五、小結
目前我只知道這兩種方式,如果還有其他方法,歡迎大家積極留言!
