MongoDB 学习记录

MongoDB 基础

菜鸟教程 | MongoDB 教程

mongodb-mongotemplate进行地理坐标操作

MongoDB官方文档:索引与计算

什么是MongoDB ?

MongoDB:是一个数据库 ,高性能、无模式、文档性,目前nosql中最热门的数据库,开源产品,基于c++开发。是nosql数据库中功能最丰富,最像关系数据库的nosql db。

特性:

1 面向集合文档的存储:适合存储Bson(json的扩展)形式的数据;
2 格式自由,数据格式不固定,生产环境下修改结构都可以不影响程序运行;
3 强大的查询语句,面向对象的查询语言,基本覆盖sql语言所有能力;
4 完整的索引支持,支持查询计划;
5 支持复制和自动故障转移;
6 支持二进制数据及大型对象(文件)的高效存储;
7 使用分片集群提升系统扩展性;
8 使用内存映射存储引擎,把磁盘的IO操作转换成为内存的操作;

MongoDB基本概念

在这里插入图片描述
MongoDB概念与RDMS概念对比

在这里插入图片描述

MongoDB 应用场景

在这里插入图片描述
MongoDB 的应用已经渗透到各个领域,比如游戏、物流、电商、内容管理、社交、物联网、视频直播等,
以下是几个实际的应用案例:

1 游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新
2 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。
3 社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能
4 物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析
5 视频直播,使用 MongoDB 存储用户信息、礼物信息
6 存储一些监控数据,No schema 对开发人员来说,真的很方便,增加字段不用改表结构,而且学习成本极低。

什么场景不能用MongoDB?

1 高度事务性系统:例如银行、财务等系统。MongoDB对事物的支持较弱;
2 传统的商业智能应用:特定问题的数据分析,多数据实体关联,涉及到复杂的、高度优化的查询方式;
3 使用sql方便的时候;数据结构相对固定,使用sql进行查询统计更加便利的时候;

MongoDB 特性及优势

在这里插入图片描述

MongoDB 安装

MongoDB官网下载地址
MongoDB 安装教程

在这里插入图片描述

双击打开安装:mongodb-windows-x86_64-5.0.9-signed.msi
选择custom,自定义安装路径

在这里插入图片描述

自定义安装路径

在这里插入图片描述

next

在这里插入图片描述

next

在这里插入图片描述

接下来就next–>install–>finish就安装成功了,mongodb会被安装为系统服务,会开机自启。

重启后点击访问 27017 是否安装成功,出现如下页面则表示启动成功

在这里插入图片描述

添加环境变量

在这里插入图片描述

命令查看版本信息

在这里插入图片描述

cmd命令增删改查

用CMD命令行使用 mongodb 增删改查 基本操作

# 查看mongodb信息 添加环境变量后可以任意位置cmd查看
mongo

###################### 查看所有数据库
> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB

# 新增数据库 (新增数据库后要插入一条数据才能看见)
> use test_db
switched to db test_db
> db
test_db
> db.test_db.insert({"name":"菜鸟"})
WriteResult({ "nInserted" : 1 })
> show dbs
admin    0.000GB
config   0.000GB
local    0.000GB
test_db  0.000GB


# 切换到admin数据库
use admin


###################### 查看某个库下所有集合(表)
> show collections
system.version
user

# 查看admin数据库下 user集合(表)所有数据
> db.user.find()
{ "_id" : ObjectId("62d51dfc7ae8c42a739f69b9"), "name" : "zsh", "age" : "22" }
{ "_id" : "1" }

# user表插入一条数据
> db.user.insert({"name":"zsh","age":"22"})
WriteResult({ "nInserted" : 1 })
> db.user.insert({"_id":"1"})
WriteResult({ "nInserted" : 1 })

# 删除user集合中某条数据
> db.user.remove({"_id":"1"})
WriteResult({ "nRemoved" : 1 })
> db.user.find()
{ "_id" : ObjectId("62d51dfc7ae8c42a739f69b9"), "name" : "zsh", "age" : "22" }
> db.user.remove({"_id":ObjectId("62d51dfc7ae8c42a739f69b9")})
WriteResult({ "nRemoved" : 1 })
> db.user.insert({"name":"xm","age":"30"})
> db.user.insert({"name":"cc","age":"50"})
> db.user.insert({"name":"cc","age":70})

> db.user.find()
{ "_id" : ObjectId("62d522037ae8c42a739f69ba"), "name" : "xm", "age" : "30" }
{ "_id" : ObjectId("62d522237ae8c42a739f69bb"), "name" : "cc", "age" : "50" }
{ "_id" : ObjectId("62d522037ae8c42a739f69ba"), "name" : "cc", "age" : 70 }

# 条件查询  格式: db.表(集合).find({}{},...)
> db.user.find({"age":"30"})
{ "_id" : ObjectId("62d522037ae8c42a739f69ba"), "name" : "xm", "age" : "30" }

> db.user.find({age:{$gt:20}})
{ "_id" : ObjectId("62d522857ae8c42a739f69bc"), "name" : "cc", "age" : 70 }

# 搜索查询 模糊查询 select * from user where name like '%c%'
> db.user.find({name:/c/})
{ "_id" : ObjectId("62d522237ae8c42a739f69bb"), "name" : "cc", "age" : "50" }
{ "_id" : ObjectId("62d522857ae8c42a739f69bc"), "name" : "cc", "age" : 70 }

索引

MongoDB官方文档:索引与计算
MongoDB 索引详解

索引命令

创建索引语法格式

db.user.createIndex(keys,options)

1、key值为你要创建的索引字段,1按照升序创建索引,-1 按降序创建索引

2、可选参数列表如下

在这里插入图片描述

#创建索引后台执行
db.values.createIndex({open: 1,close: 1},{backgroud: true})
#创建唯一索引
db.values.createIndex({title:1},{unique:true})

// 创建2dsphere索引  CityLocation:集合(表)   geoJson:GeoJson格式坐标对象
db.cityLocation.createIndex({ "geoJson": "2dsphere" })

查看集合的所有索引

db.user.getIndexes()

查看集合索引大小

db.user.totalIndexSize()

删除索引

db.user.dropIndex("name")

删除全部索引

db.user.dropIndexes()

单键索引

1按照升序创建索引,-1 按降序创建索引

db.book.createIndex({"title": 1})

对内嵌文档字段创建索引:

db.book.createIndex({"author.name": 1})

复合索引

复合索引是多个字段组合而成的索引,其性质和单字段索引类似。但不同的是,复合索引中字段的顺序、字段的升降序对查询性能有直接的影响,因此在设计复合索引时则需要考虑不同的查询场景。

数字 1 表示 username 键的索引按升序存储,-1 表示 age 键的索引按照降序方式存储。

db.user.createIndex({"username":1, "age":-1})

该索引被创建后,基于 username 和 age 的查询将会用到该索引,或者是基于 username 的查询也会用到该索引,但是只是基于 age 的查询将不会用到该复合索引。因此可以说, 如果想用到复合索引,必须在查询条件中包含复合索引中的前 N 个索引列。然而如果查询条件中的键值顺序和复合索引中的创建顺序不一致的话,MongoDB 可以智能的帮助我们调整该顺序,以便使复合索引可以为查询所用。

db.user.find({"age": 30, "username": "stephen"})

对于上面示例中的查询条件,MongoDB 在检索之前将会动态的调整查询条件文档的顺 序,以使该查询可以用到刚刚创建的复合索引。

对于上面创建的索引,MongoDB 都会根据索引的 keyname 和索引方向为新创建的索引自动分配一个索引名,下面的命令可以在创建索引时为其指定索引名,如:

db.user.createIndex({"username":1},{"name":"userindex"})

随着集合的增长,需要针对查询中大量的排序做索引。如果没有对索引的键调用 sort, MongoDB 需要将所有数据提取到内存并排序。因此在做无索引排序时,如果数据量过大以 致无法在内存中进行排序,此时 MongoDB 将会报错。

唯一索引

db.user.createIndex({"userid":1},{"unique":true})

地址位置索引

MongoDB为地理空间检索提供了非常方便的功能。地理空间索引(2dsphereindex)就是专门用于实现位置索引的一种特殊索引。

创建 2dsphere 索引

db.collection.createIndex( { <location field> : "2dsphere" } )
// CityLocation:集合(表)   geoJson:GeoJson格式坐标对象
db.cityLocation.createIndex({ "geoJson": "2dsphere" })

查询

> db.cityLocation.getIndexes()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_"
        },
        {
                "v" : 2,
                "key" : {
                        "geoJson" : "2dsphere"
                },
                "name" : "geoJson_2dsphere",
                "2dsphereIndexVersion" : 3
        }
]

删除

db.user.dropIndex("geoJson_2dsphere")

创建 2d 索引

db.cityLocation.createIndex({ "location": "2d" })
// geoJson格式创建2d索引
db.cityLocation.createIndex( { "geoJson.coordinates": "2d" } )

全文检索(Text Indexes)

MongoDB支持全文检索功能,可通过建立文本索引来实现简易的分词检索。

db.reviews.createIndex({"字段":"text"})

t e x t 操作符可以在有 t e x t i n d e x 的集合上执行文本检索。 text操作符可以在有text index 的集合上执行文本检索。 text操作符可以在有textindex的集合上执行文本检索。text将会使用空格和标点符号作为分隔符对检索字符串进行分词,并且对检索字符串中所有的分词结果进行一个逻辑上的OR操作。全文检索能解决快速文本查找的需求,比如有一个博客文章集合,需要根据博客的内容来快速查询,则可以针对博客内容建立索引。

案例:

数据准备:

db.stores.insert(
    [ 
        { _id: 1, name: "Java Hut", description: "Coffee and cakes" },
        { _id: 2, name: "Burger Buns", description: "Gourmet hamburgers" },
        { _id: 3, name: "Coffee Shop", description: "Just coffee" },
        { _id: 4, name: "Clothes Clothes Clothes", description: "Discount clothing" },
        { _id: 5, name: "Java Shopping", description: "Indonesian goods" }
    ]
)

创建name和description的全文索引

db.stores.createIndex({name:"text",description:"text"})

MongoDB的文本索引功能存在诸多限制,而官方并未提供中文分词的功能,这使得该功能的应用场景十分受限。

MongoDB 实战

MongoDB 最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

传统的关系数据库一般由数据库(database)、表(table)、记录(record)三个层次概念组成,
MongoDB 是由数据库(database)、集合(collection)、文档对象(document)三个层次组成。MongoDB 对于关系型数据库里的表,但是集合中没有列、行和关系概念,这体现了模式自由的特点。

MongoDB 的适合对大量或者无固定格式的数据进行存储,比如:日志、缓存等。对事物支持较弱,不适用复杂的多文档(多表)的级联查询。

spring boot整合Mongodb

整合

依赖
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
yml配置
spring:
  data:
    mongodb:
      host: localhost
      port: 27017
      # 如果指定的数据库不存在,mongo会自动创建数据库
      database: test_db
    #uri: mongodb://localhost/test_db
定义实体类
import lombok.Data;
import org.springframework.data.annotation.Id;

import java.util.Date;

/**
 * <p>
 * 文章实体类,可以随意添加删除字段,旧数据会仍然保留字段信息
 * </p>
 */
@Data
public class Article {

    /** 文章id */
    @Id
    private Long id;

    /** 文章标题 */
    private String title;

    /** 文章内容 */
    private String content;

    /** 创建时间 */
    private Date createTime;

    /** 点赞数量 */
    private Long thumbUp;

    /** 访客数量 */
    private Long num;

    private String add;

    public Article(Long id, String title, String content, Date createTime, Long thumbUp, Long num) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.createTime = createTime;
        this.thumbUp = thumbUp;
        this.num = num;
    }
}
数据操作dao层
import com.tuling.mongodb.model.Article;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.List;

/**
 * <p>
 * 文章 Dao
 * </p>
 */
public interface ArticleRepository extends MongoRepository<Article, Long> {
    /**
     * 根据标题模糊查询,自己写,像Java JPA
     *
     * @param title 标题
     * @return 满足条件的文章列表
     */
    List<Article> findByTitleLike(String title);
}

测试

    @Autowired
    private MongoTemplate mongoTemplate;
新增、模糊查询
    @Test
    public void testSaveAndFind() {
        Article article = new Article("zsh添加并查询1", new Date(), 120L);
        article.setId(6L); //有id注解,不添加会报错
        articleRepo.save(article);
        //criteria:条件构造类、  regex:模糊查询
        Criteria criteria = Criteria.where("title").regex("zsh").and("num").gt(110);
        Article find = mongoTemplate.findOne(Query.query(criteria), Article.class);
        log.info("【article】= {}", JSONUtil.toJsonStr(find));
    }
按条件更新数据
    @Test
    public void testThumbUp2() {
        //更新条件
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").is(1L));
        //要改动的数据
        Update update = new Update();
        //num自增
        update.inc("num", 1L);
        //更新title字段值
        update.set("title","zsh22222");
        mongoTemplate.updateFirst(query, update, "article");
    }
分页、排序查询
    @Test
    public void testQuery() {
        //descending:降序
        Sort sort = Sort.by("thumbUp", "updateTime").descending();
        PageRequest pageRequest = PageRequest.of(0, 5, sort);
        Page<Article> all = articleRepo.findAll(pageRequest);
        log.info("【总页数】= {}", all.getTotalPages());
        log.info("【总条数】= {}", all.getTotalElements());
        log.info("【当前页数据】= {}", JSONUtil.toJsonStr(all.getContent().stream().map(article -> "文章标题:" + article.getTitle() + "点赞数:" + article.getThumbUp()).collect(Collectors.toList())));
    }
flash-waimai查询指定坐标附近距离的商家
    /**
     * 查询指定位置附近的商家
     * @param x 坐标
     * @param y 坐标
     * @param collectionName 表名称
     * @param params 表查询字段参数
     * @param miles 指定公里数附近
     * @return
     */
    public GeoResults<Map> near(double x, double y, String collectionName, Map<String, Object> params,Integer miles) {
        Point location = new Point(x, y);
        NearQuery nearQuery = NearQuery.near(location).maxDistance(new Distance(miles, Metrics.MILES));
        if (params != null && !params.isEmpty()) {
            Query query = Query.query(criteria(params));
            nearQuery.query(query);
        }
        try {
            return mongoTemplate.geoNear(nearQuery, Map.class, collectionName);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
        return null;
    }

    private Criteria criteria(Map<String, Object> map) {
        Criteria criteria = null;
        if (map != null) {
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                if (criteria == null) {
                    criteria = Criteria.where(entry.getKey()).is(entry.getValue());
                } else {
                    criteria.and(entry.getKey()).is(entry.getValue());
                }
            }
        }
        return criteria;
    }

测试

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import com.tuling.mongodb.model.Article;
import com.tuling.mongodb.repository.ArticleRepository;
import lombok.extern.slf4j.Slf4j;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
class MongodbApplicationTests {
    @Autowired
    private ArticleRepository articleRepo;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private Snowflake snowflake;

    /**
     * 测试新增
     */
    @Test
    public void testSave() {
        Article article = new Article(1L, RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil.date(), 0L, 0L);
        articleRepo.save(article);
        log.info("【article】= {}", JSONUtil.toJsonStr(article));
        System.out.println("MongodbApplicationTests.testSave");
    }

    /**
     * 测试新增列表
     */
    @Test
    public void testSaveList() {
        List<Article> articles = Lists.newArrayList();
        for (int i = 0; i < 10; i++) {
            articles.add(new Article(snowflake.nextId(), RandomUtil.randomString(20), RandomUtil.randomString(150), DateUtil.date(), 0L, 0L));
        }
        articleRepo.saveAll(articles);

        log.info("【articles】= {}", JSONUtil.toJsonStr(articles.stream().map(Article::getId).collect(Collectors.toList())));
    }

    /**
     * 测试更新
     */
    @Test
    public void testUpdate() {
        articleRepo.findById(1L).ifPresent(article -> {
            article.setTitle(article.getTitle() + "更新之后的标题");
            articleRepo.save(article);
            log.info("【article】= {}", JSONUtil.toJsonStr(article));
        });
    }

    /**
     * 测试删除
     */
    @Test
    public void testDelete() {
        // 根据主键删除
        articleRepo.deleteById(1L);

        // 全部删除
        articleRepo.deleteAll();
    }

    /**
     * 测试点赞数、访客数,使用save方式更新点赞、访客
     */
    @Test
    public void testThumbUp() {
        articleRepo.findById(1L).ifPresent(article -> {
            article.setThumbUp(article.getThumbUp() + 1);
            articleRepo.save(article);
            log.info("【标题】= {}【点赞数】= {}", article.getTitle(), article.getThumbUp());
        });
    }

    /**
     * 测试点赞数、访客数,使用更优雅/高效的方式更新点赞、访客
     */
    @Test
    public void testThumbUp2() {
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").is(1L));
        Update update = new Update();
        update.inc("thumbUp", 1L);
        update.inc("visits", 1L);
        mongoTemplate.updateFirst(query, update, "article");

        articleRepo.findById(1L).ifPresent(article -> log.info("【标题】= {}【点赞数】= {}", article.getTitle(), article.getThumbUp()));
    }

    /**
     * 测试分页排序查询
     */
    @Test
    public void testQuery() {
        Sort sort = Sort.by("thumbUp", "updateTime").descending();
        PageRequest pageRequest = PageRequest.of(0, 5, sort);
        Page<Article> all = articleRepo.findAll(pageRequest);
        log.info("【总页数】= {}", all.getTotalPages());
        log.info("【总条数】= {}", all.getTotalElements());
        log.info("【当前页数据】= {}", JSONUtil.toJsonStr(all.getContent().stream().map(article -> "文章标题:" + article.getTitle() + "点赞数:" + article.getThumbUp()).collect(Collectors.toList())));
    }

    /**
     * 测试根据标题模糊查询
     */
    @Test
    public void testFindByTitleLike() {
        List<Article> articles = articleRepo.findByTitleLike("更新");
        log.info("【articles】= {}", JSONUtil.toJsonStr(articles));
    }

    @Test
    public void findAll() {
        List<Article> list = articleRepo.findAll();
        list.forEach(System.out::println);
    }

}

MongoDB地理空间索引

MongoDB 对于坐标数据的文档提供了地理空间索引。使用该索引,可以通过坐标高效地查询到对应文档。下面介绍地理空间索引的两种类型:

  • 2dsphere :此索引类型支持查询地球球体上的位置,支持"GEOJSON"和专统坐标类型的数据。
    GeoJSON数据:需要使用嵌入式文档。在其中可以通过“coordinates”字段来指定坐标位置,以及可以通过“type”字段来指定坐标的类型。
    type可以分3种形式:
    点(Point):若“type”为“Point”,则“coordinates”字段只有一个坐标
    线(LineString):若“type”为“LineString”,则“coordinates”字段会有两个坐标
    多边形(Polygon):若“type”为“Polygon”,则“coordinates”字段会有两个以上的坐标
  • 2d:此索引类型支持查询二维平面上的位置,仅支持传统坐标类型的数据。

2dsphere实现

定义表

创建GeoJSON格式对象

/**
 * GeoJSON 格式的对象,如:{type:“point”,coordinates:[105,41]}
 */
@Data
public class GeoJson {
    /** 坐标的类型 */
    private String type;

    /** 坐标位置 */
    private Double[] coordinates;
}

地点名称对象

/**
 * 地球状球体计算几何的查询应使用 2dsphere 索引
 * 命令给表字段坐标对象 geoJson 设置 2dsphere 索引:db.CityLocation.createIndex({ "geoJson": "2dsphere" })
 */
@Data
public class CityLocation implements Serializable {

    private static final long serialVersionUID = 4508868382007529970L;

    @Id
    private Long id;

    /**
     * 地点名称
     */
    private String name;

    /**
     * 位置描述
     */
    private String address;

    /**
     * 位置类型:1-景点,2-加油站,3-酒店
     **/
    private Integer type;

    /**
     * 距离,单位m:查询时的返回数据
     **/
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private Double distance;

    /**
     * 自定义坐标对象:x:经度 y:纬度:使用2dsphere,集合必须含有GeoJSON格式的坐标对象
     **/
    private GeoJson geoJson;
}

查询参数类

public class CityLocationQuery {

    /**
     * 当前经纬度[xxx,xxx]
     **/
    private Double[] coordinates;
    /**
     * 最近距离:10米
     **/
    private Double minDistance;

    /**
     * 最远距离:500米
     */
    private Double maxDistance = 500D;
    /**
     * 距离单位,如:m,km
     **/
    private String unit;
    /**
     * 位置类型:1-景点,2-加油站,3-酒店
     **/
    private Integer type;

    /**
     * 查询条数
     */
    private Integer num = 1;
}

接口继承MongoRepository

public interface CityLocationRepository extends MongoRepository<CityLocation, Long> {
    
}

设置2dsphere索引

命令的方式创建( 建议使用 )

在mongo执行下面的命令:

// CityLocation:集合(表)   geoJson:GeoJson格式坐标对象
db.cityLocation.createIndex({ "geoJson": "2dsphere" })
通过代码的形式,系统启动成功后,加载配置
@Component
public class MongodbIndexConfig implements CommandLineRunner {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    public void run(String... args) throws Exception {
        MongoCollection<Document> cityLocation = mongoTemplate.getCollection("CityLocation");
        //给表字段设置 2dsphere 索引
        cityLocation.createIndex(new BasicDBObject("geoJson","2dsphere"));
        System.out.println("CityLocation.geoJson 成功创建 2dsphere 索引");
    }
}

注意: 索引要建立正确,不然会查询不到数据

批量添加数据:saveBatch

批量添加数据

[
  {
    "id": 20220118001001,
    "name": "天安门",
	"address": "北京市-天安门",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.4041602659,
        39.9096215780
      ]
    }
  },
  {
    "id": 20220118001002,
    "name": "东单",
	"address": "北京市-东单",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.4244857287,
        39.9144951360
      ]
    }
  },
  {
    "id": 20220118001003,
    "name": "王府井",
	"address": "北京市-王府井",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.4177807251,
        39.9175129885
      ]
    }
  },
  {
    "id": 20220118001004,
    "name": "西单",
	"address": "北京市-西单",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.3834863095,
        39.9133467579
      ]
    }
  },
  {
    "id": 20220118001005,
    "name": "复兴门",
	"address": "北京市-复兴门",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.3631701881,
        39.9129554253
      ]
    }
  },
  {
    "id": 20220118001006,
    "name": "西四",
	"address": "北京市-西四",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.3799838526,
        39.9299098531
      ]
    }
  },
  {
    "id": 20220118001007,
    "name": "菜市口",
	"address": "北京市-菜市口",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.3809950293,
        39.8952009239
      ]
    }
  },
  {
    "id": 20220118001008,
    "name": "东四",
	"address": "北京市-东四",
    "type": 1,
    "geoJson": {
      "type": "Point",
      "coordinates": [
        116.4239387439,
        39.9306126797
      ]
    }
  }
]

查询

MongoDB官方文档:索引与计算

指定地理空间查询从最近到最远返回文档的点。 MongoDB 使用球面几何计算$nearSphere的距离。
near、nearSphere是Mongodb从指定地理空间查询从最近到最远返回文档的点、并且由近至远排好序的操作,不返回距离。他们都支持对2d索引和2dsphere索引的操作,但有如下需要注意的点:

  • near检索时,若标识坐标的字段为2d索引,它传入的基准点的格式必须是Double数组型的;若标识坐标的字段为2dsphere索引,传入的基准点的格式则必须为GeoJSON型。
  • nearSphere检索时,若作用在2d索引上,则要求该2d索引必须是建立在集合中GeoJSON型数据的coordinates字段上,且传入的基准点的格式必须是Double数组型的;若作用在2dsphere索引上,则对基准点的格式无要求。
  • 传入的基准点格式为Double数组时,minDistance、maxDistance的计算都是以弧度为单位的;传入的基准点格式为GeoJSON时,计算是以距离:米为单位的。
  • near、nearSphere虽然能够检索出邻近点,并且由近至远排好序,但是这两个操作是不返回距离的。

返回:distance字段表示当前距离

near查询
db.restaurant.find({
    location:{
        $near : {
            $geometry:{
                type:"Point",
                coordinates:[-73.88,40.78]
            },
            $maxDistance:10000
        }
    }
})
  • $near 查询操作符,用于实现附近商家的检索,返回数据库结果会按距离排序。
  • geometry 操作符用于指定一个GeoJSON格式的地理空间对象,type=Point表示地理坐标点,coordinates则是用户当前所在的经纬度位置,$maxDistance限定了最大距离,单位是米。
nearSphere
db.places.find(
   {
     location: {
        $nearSphere: {
           $geometry: {
              type : "Point",
              coordinates : [ -73.9667, 40.78 ]
           },
           $minDistance: 1000,
           $maxDistance: 5000
        }
     }
   }
)
  /**
   * 指定一个点,返回该点附近的坐标点且是由近到远,$nearSphere 需要建立索引2dsphere 或者2d,并且支持GeoJSON和一般坐标对 注意:
   * $nearSphere在分片的集群中无效,使用geoNear
   *
   * @param collection 集合名称
   * @param locationField 坐标字段
   * @param center 中心点坐标[经度,纬度]
   * @param minDistance 最近距离
   * @param maxDistance 最远距离
   * @param query 查询条件
   * @param fields 查询字段
   * @param limit 返回记录限制数量
   * @return 非NULL的list
   */
  public List<DBObject> nearSphere(String collection, String locationField, Point center,
      long minDistance, long maxDistance, DBObject query, DBObject fields, int limit) {
      if (query == null) {
          query = new BasicDBObject();
      }
    query.put(locationField,
        new BasicDBObject("$nearSphere",
            new BasicDBObject("$geometry",
                new BasicDBObject("type", "Point").append("coordinates",
                    new double[]{center.getX(), center.getY()}))
                .append("$minDistance", minDistance)
                .append("$maxDistance", maxDistance)));
    return mongoTemplate.getCollection(collection).find(query, fields).limit(limit).toArray();
  }
geoNear查询带距离

geoNear(聚合)官方文档
返回距离需要用到聚合检索中的$geoNear,其使用方法为:

db.集合名.aggregate([{
    $geoNear: {
       near: { type: "Point", coordinates: [ -73.99279 , 40.719296 ] },
       distanceField: "dist.calculated",
       maxDistance: 2,
       query: { type: "public" },
       includeLocs: "dist.location",
       spherical: true
    }
}])

从版本 4.2 开始,MongoDB 将删除 num,分页用 $limit

db.places.aggregate([
   {
     $geoNear: {
        near: { type: "Point", coordinates: [ -73.98142 , 40.71782 ] },
        key: "geoJson", //索引
        distanceField: "distance", //返回距离字段别名
        query: { "type": 1 } //查询条件
     }
   },
   { $limit: 5 }
])
2dsphere索引geoNear复杂查询(返回距离米)
    /**
     * 查询存储的位置,距离指定位置的距离,排序为:由近到远
     **/
    @PostMapping(value = "geoNear")
    public List<CityLocation> geoNear(@RequestBody CityLocationQuery params) {
        JSONObject query = new JSONObject();
        query.put("type", params.getType());

        List<BasicDBObject> bsonList = new ArrayList<>();
        BasicDBObject geoNear = new BasicDBObject("$geoNear", //返回距离需要用到聚合检索中的$geoNear
                new BasicDBObject("near"
                        , new BasicDBObject("type", "Point") //GeoJson对象坐标类型
                        .append("coordinates", params.getCoordinates())) //用户的坐标位置经纬度
                        .append("key", "geoJson") //指定在计算距离时要使用的地理空间索引字段
                        .append("query", query) //查询条件,必须是object类型数据
                        .append("minDistance",params.getMinDistance()) //最小距离
                        .append("maxDistance", params.getMaxDistance()) //最大范围:500米内
                        .append("distanceField", "distance") //计算的距离的输出字段
                        .append("spherical", true) //计算球面距离
        );
        BasicDBObject limit = new BasicDBObject("$limit", params.getNum());
        bsonList.add(geoNear);
        bsonList.add(limit);
        AggregateIterable<Document> iterable = mongoTemplate.getCollection("cityLocation").aggregate(bsonList);
        MongoCursor<Document> cursor = iterable.iterator();

        //将查询的结果,封装成对象返回出去
        List<CityLocation> voList = new ArrayList<>();
        while (cursor.hasNext()) {
            Document document = cursor.next();
            CityLocation node = JSONObject.parseObject(JSONObject.toJSONString(document),CityLocation.class);
            voList.add(node);
        }
        return voList;
    }

查询参数

{
    "type": 1,
    "num": 2,
    "minDistance": 10,
    "maxDistance": 5000,
    "coordinates": [
        116.4177807251,
        39.9175129885
    ]
}

返回结果

[
	{
		"id": 20220118001004,
		"name": "西单",
		"address": "北京市-西单",
		"type": 1,
		"distance": 2964.5830248374164, //自动计算的距离
		"geoJson": {
			"type": "Point",
			"coordinates": [
				116.3834863095,
				39.9133467579
			]
		}
	},
	{
		"id": 20220118001006,
		"name": "西四",
		"address": "北京市-西四",
		"type": 1,
		"distance": 3509.447386518843,
		"geoJson": {
			"type": "Point",
			"coordinates": [
				116.3799838526,
				39.9299098531
			]
		}
	}
]
2dsphere索引geoNear复杂查询(返回距离默认千米)

逻辑实现

	/**
     * 2dsphere索引查询
     * @param params 距离范围单位千米
     * @return 输出距离单位千米,返回米可实体类:distance * 1000
     */
    @PostMapping(value = "aggregate")
    public List<CityLocation> aggregate(@RequestBody CityLocationQuery params) {
        Query query = new Query();
        //封装查询条件 query.addCriteria(Criteria.where("type").is(1));
        if (ObjectUtil.isNotEmpty(params.getType())){
            Criteria type = new Criteria("type").is(params.getType());
            query.addCriteria(type);
        }
        if (StringUtils.isNotEmpty(params.getName())){
            Criteria name = new Criteria("name").regex(params.getName());
            query.addCriteria(name);
        }
        //分页排序 ok
        if (ObjectUtil.isNotEmpty( params.getLimit() )){
            query.skip(params.getStartPage()).limit(params.getLimit());
        }
        //Sort 排序 ok: geoNear排序无效,它是根据地理位置近到远排序
        if (ObjectUtil.isNotEmpty( params.getOrderColumn() )){
            if (StringUtils.equals(params.getOrderType(), PageLimit.ASC)){
                query.with( Sort.by( Sort.Order.asc(params.getOrderColumn()) ) );
            }
            if (StringUtils.equals(params.getOrderType(), PageLimit.DESC)){
                query.with( Sort.by( Sort.Order.desc(params.getOrderColumn()) ) );
            }
        }

        //根据距离筛选附近(条件)
        Point point = new Point(params.getCoordinates()[0], params.getCoordinates()[1]);
        NearQuery nearQuery = NearQuery
                .near(point)
                .minDistance(new Distance(params.getMinDistance(), Metrics.KILOMETERS)) //距离范围:KM
                .maxDistance(new Distance(params.getMaxDistance(), Metrics.KILOMETERS)) //距离范围:KM
                .spherical(true) //计算球面距离
                //.skip(params.getStartPage()).limit(params.getLimit()) //分页 skip未生效
                .query( query );

        //聚合条件
        List<AggregationOperation> list = new ArrayList<>();
        //指定计算的距离的输出字段
        list.add( Aggregation.geoNear(nearQuery, "distance").useIndex("geoJson"));
        //根据积分排序?
        //Sort sort = Sort.by( Sort.Direction.DESC, "totalScore" );
        //aggregationOperations.add(Aggregation.sort(sort));
        Aggregation aggregation = Aggregation.newAggregation(list);
        AggregationResults<CityLocation> results = mongoTemplate.aggregate(aggregation, "cityLocation", CityLocation.class);
        List<CityLocation> cityLocationList = results.getMappedResults();
        return cityLocationList;
    }

查询参数

{
    "type": 2,
    "page": 1,
    "limit": 5,
    "minDistance": 0,
    "maxDistance": 5,
    "coordinates": [
        116.4177807251,
        39.9175129885
    ]
}

返回实体类

@Data
@Document(collection = "cityLocation")
public class CityLocation implements Serializable {

    private static final long serialVersionUID = 4508868382007529970L;

    @Id
    private Long id;

    /**
     * 地点名称
     */
    private String name;

    /**
     * 位置描述
     */
    private String address;

    /**
     * 位置类型:1-景点,2-加油站,3-酒店
     **/
    private Integer type;

    /**
     * 输出目标距离,单位m
     **/
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private Double distance;

    /**
     * 自定义坐标对象:x:经度 y:纬度:使用2dsphere,集合必须含有GeoJSON格式的坐标对象
     **/
    private GeoJson geoJson;

    public CityLocation(Long id, String name, String address, Integer type, Double distance, GeoJson geoJson) {
        this.id = id;
        this.name = name;
        this.address = address;
        this.type = type;
        this.distance = distance * 1000; //根据输出距离单位改动
        this.geoJson = geoJson;
    }
}

数据返回

[
	{
		"id": 20220118001002,
		"name": "东单",
		"address": "北京市-东单22222",
		"type": 2,
		"distance": 663.7686961399672,
		"geoJson": {
			"type": "Point",
			"coordinates": [
				116.4244857287,
				39.914495136
			]
		}
	},
	{
		"id": 20220118001008,
		"name": "东四",
		"address": "北京市-东四2222",
		"type": 2,
		"distance": 1550.1192689098514,
		"geoJson": {
			"type": "Point",
			"coordinates": [
				116.4239387439,
				39.9306126797
			]
		}
	}
]

sort排序无效,是distance距离排序的

数据库经纬度查询距离

参数

public class StoreNearRequest implements Serializable {

    private static final long serialVersionUID=1L;

    @ApiModelProperty(value = "经度")
    private String latitude;

    @ApiModelProperty(value = "纬度")
    private String longitude;
}

查询sql

    <select id="getNearList" resultType="com.zbkj.common.vo.SystemStoreNearVo" parameterType="com.zbkj.common.request.StoreNearRequest">
        SELECT *, (round(6367000 * 2 * asin(sqrt(pow(sin(((latitude * pi()) / 180 - (${latitude} * pi()) / 180) / 2), 2) + cos((${latitude} * pi()) / 180) * cos((latitude * pi()) / 180) * pow(sin(((longitude * pi()) / 180 - (${longitude} * pi()) / 180) / 2), 2))))) AS distance
        FROM eb_system_store WHERE is_show = 1 and is_del = 0
         ORDER BY distance asc
    </select>

2d实现

在这里插入代码片

MongoDB 相关面试题

21个MongoDB经典面试题

mongo地理坐标计算距离

mongotemplate进行地理坐标操作

mongo地理坐标计算距离

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值