一、MongoDB简介

1.1、什么是MongoDB?

MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,且与关系数据库的最为相像的。它是一个文档数据库,它的数据以文档方式进行存储,将数据存储在类似 JSON 的 BSON 文档中。支持的数据结构非常松散,因此可以存储比较复杂的数据类型。

1.2、MongoDB有什么特点?

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

二、MongoDB基本概念

2.1、文档(Document)

文档是MongoDB中最基本的数据单元,由键值对组成,类似于 JSON 格式,可以存储不同字段,字段的值可以包括其他文档,数组和文档数组。

2.2、集合(Collection)

集合指的是文档组(类似于MySql中表的概念),里面可以存储许多文档。

2.3、数据库(Database)

MongoDB中可以存在多个数据库,每个数据库中可以设置不同的集合与用户权限。

2.4、数据类型

数据类型

说明

String

字符串,存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的

Integer

整型数值,用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位

Boolean

布尔值,用于存储布尔值(true/false)

Double

双精度浮点值,用于存储浮点值

Min/Max keys

将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比

Array

用于将数组或列表或多个值存储为一个键

Timestamp

时间戳。记录文档修改或添加的具体时间

Object

用于内嵌文档

Null

用于创建空值

Symbol

符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言

Date

日期时间,用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息

Object ID

对象 ID,用于创建文档的 ID

Binary Data

二进制数据,用于存储二进制数据

Code

代码类型,用于在文档中存储 JavaScript 代码

Regular expression

正则表达式类型,用于存储正则表达式

三、安装运行

3.1、镜像拉取

3.2、服务运行

  • 跑服务: java docker run -itd --name mongo -p 27017:27017 mongo --auth
  • 进入容器:java docker exec -it 542d12efed26 mongo

注意事项:–auth:需要密码才能访问容器服务。进入容器命令后方固定为“mongo”,类似mysqld那种。

四、Shell操作步骤

4.1、进入mongo命令端

java docker exec -it xxxxx mongo

进入容器后,默认数据库是test。可通过use命令进行数据库切换。若数据库存在则切换,不存在则创建。

4.2、选择数据库

java use admin

4.3、创建用户、设置权限

查看系统所有用户:java db.system.users.find().pretty()

如下所示:

"_id" : "admin.root",
        "userId" : UUID("704ba779-237a-43f3-b6fc-3626d7b9b5df"),
        "user" : "root",
        "db" : "admin",
        "credentials" : {
                "SCRAM-SHA-1" : {
                        "iterationCount" : 10000,
                        "salt" : "44bA6kZd0Yd53zfgUeW03Q==",
                        "storedKey" : "hsvwvWX9Dr/4tUf3WvJACE6SZx0=",
                        "serverKey" : "gPirvqO4QV2aaRZjmdf0fDV9s5s="
                },
                "SCRAM-SHA-256" : {
                        "iterationCount" : 15000,
                        "salt" : "th+fxRfSohujGXDuK/pGhXlxABRmYpqXLsNSTg==",
                        "storedKey" : "jPcM5bBFJGT+6s32CWag7lokhKaVJ+9nb1ITWKDXqVc=",
                        "serverKey" : "yS1qMIY2LcXtuak6pR8y8evFyhmYMDs2PCq9ZBEmifA="
                }
        },
        "roles" : [
                {
                        "role" : "userAdminAnyDatabase",
                        "db" : "admin"
                }
        ]
}
{
        "_id" : "admin.wss",
        "userId" : UUID("77829777-c420-4522-857c-99fc448108a0"),
        "user" : "wss",
        "db" : "admin",
        "credentials" : {
                "SCRAM-SHA-1" : {
                        "iterationCount" : 10000,
                        "salt" : "2Wnr6DI1b/Sjgy5+LZ7NxQ==",
                        "storedKey" : "fsBO/wNIlBncdT3FL6B221bwZg0=",
                        "serverKey" : "9hBHcJtcs2bHqYbsG8Ew8u5dQfM="
                },
                "SCRAM-SHA-256" : {
                        "iterationCount" : 15000,
                        "salt" : "darr3ESorPMuvEU24SCg/5YseiMZVK4qFrQm/A==",
                        "storedKey" : "zRGMLhJOgWwwdSeE41GLC0WjYoUqYtSp12enBvGCbJM=",
                        "serverKey" : "wWFnAY4azewtiUAUw1UpfIZPWR8OVHodTU4Run38PQo="
                }
        },
        "roles" : [
                {
                        "role" : "readWrite",
                        "db" : "mydb"
                }
        ]
}
{
        "_id" : "admin.wshanshi",
        "userId" : UUID("6711d8a1-506e-42cf-ae51-4c69d86a8b4b"),
        "user" : "wshanshi",
        "db" : "admin",
        "credentials" : {
                "SCRAM-SHA-1" : {
                        "iterationCount" : 10000,
                        "salt" : "ew/kf1zjKSIaI2+72D4X8A==",
                        "storedKey" : "t4WfIMbMUXRuzodhvAU07riBag8=",
                        "serverKey" : "TqtrkmU8mGbOGzD6QI1X2WAR7gE="
                },
                "SCRAM-SHA-256" : {
                        "iterationCount" : 15000,
                        "salt" : "g0ibGWeNHRAYybgfZOepxiJfOd4hGfkdaF5/tw==",
                        "storedKey" : "N5/1Bq8oEZC7JHIT+Df92IwNu0n0Pf19w2iSNLk5DwE=",
                        "serverKey" : "o2uFVEc1ZS4Dr5OkGbF2kd51PLq96CBFgHeanJ1Yr4Y="
                }
        },
        "roles" : [
                {
                        "role" : "root",
                        "db" : "admin"
                }
        ]
}
{
        "_id" : "testtwst.admin",
        "userId" : UUID("55fa4589-ebff-4d7d-8968-9e5be0547423"),
        "user" : "admin",
        "db" : "testtwst",
        "credentials" : {
                "SCRAM-SHA-1" : {
                        "iterationCount" : 10000,
                        "salt" : "+YyskVWzqq9VPrEDvWp29Q==",
                        "storedKey" : "tPo3ELQgP+NzYElbA5ZqVhZFa8Y=",
                        "serverKey" : "XzArOMmOA0pxtocf+8pFbND7x/s="
                },
                "SCRAM-SHA-256" : {
                        "iterationCount" : 15000,
                        "salt" : "hGnjSaUEyUtHOK6JKfjGw/O1Gon7egL3nY+oMQ==",
                        "storedKey" : "KtD4D/44mORZT83OFiwNZ/3TdYIJHtzG90ZU+HmOJOM=",
                        "serverKey" : "XEI2anD0NYcomPGwwUZxKcMvbI8FN48gLnmgPprJiHI="
                }
        },
        "roles" : [
                {
                        "role" : "root",
                        "db" : "admin"
                }
        ]
}

设置用户账户权限:

java db.createUser({ user: 'wss', pwd: 'wss', roles: [ { role: "root", db: "admin" } ] });

以上操作就相当于创建了一个用户。上面创建的用户角色是超级账号,是超级用户,role后面的参数可以参考下面:

角色

说明

read

允许用户读取指定数据库

readWrite

允许用户读写指定数据库

dbAdmin

允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile

userAdmin

允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户

clusterAdmin

只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限

readAnyDatabase

只在admin数据库中可用,赋予用户所有数据库的读权限

readWriteAnyDatabase

只在admin数据库中可用,赋予用户所有数据库的读写权限

userAdminAnyDatabase

只在admin数据库中可用,赋予用户所有数据库的userAdmin权限

root

只在admin数据库中可用。超级账号,超级权限

角色分类如下:

角色分类

角色罗列

数据库用户角色

read、readWrite

数据库管理角色

dbAdmin、dbOwner、userAdmin

集群管理角色

clusterAdmin、clusterManager、clusterMonitor、hostManager

备份恢复角色

backup、restore

所有数据库角色

readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase

超级用户角色

root

4.4、退出后用密码登录

java docker exec -it 542d12efed26 mongo -u wshanshi -p wshanshi --authenticationDatabase admin

4.5、切换用户

java db.auth("root","admin")

4.6、增删改查

具体可参考:MongoDB 操作

五、SpringBoot集成MongoDB

5.1、maven依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

5.2、application配置

spring:
    data:
    mongodb:
      host: 127.0.0.1
      port: 27017
      username: wshanshi
      password: wshanshi
      database: admin
      #authentication-database: root
      connections-per-host: 20
      min-connections-per-host: 20

5.3、config配置类

import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import java.util.List;

@Component
@Validated
@Getter
@Setter
public class MongoSettingsProperties {
    @Value("${spring.data.mongodb.database}")
    private String database;

    @Value("${spring.data.mongodb.host}")
    private String host;

    @Value("${spring.data.mongodb.port}")
    private String port;

    @Value("${spring.data.mongodb.username}")
    private String username;

    @Value("${spring.data.mongodb.password}")
    private String password;

    private String replicaSet;
    private String authenticationDatabase;
    private Integer minConnectionsPerHost = 10;
    private Integer connectionsPerHost = 2;
}

六、Java操作示例

6.1、创建集合

public Object createCollection() {
    // 设置集合名称
    String collectionName = "users";
    // 创建集合并返回集合信息
    mongoTemplate.createCollection(collectionName);
    // 检测新的集合是否存在,返回创建结果
    return mongoTemplate.collectionExists(collectionName) ? "创建集合成功" : "创建集合失败";
}

6.2、查询集合

/**
 * 获取【集合名称】列表
 *
 * @return 集合名称列表
 */
public Object getCollectionNames() {
    // 执行获取集合名称列表
    return mongoTemplate.getCollectionNames();
}

6.3、删除集合

/**
 * 删除【集合】
 *
 * @return 创建集合结果
 */
public Object dropCollection() {
    // 设置集合名称
    String collectionName = "users1";
    // 执行删除集合
    mongoTemplate.getCollection(collectionName).drop();
    // 检测新的集合是否存在,返回删除结果
    return !mongoTemplate.collectionExists(collectionName) ? "删除集合成功" : "删除集合失败";
}

6.4、创建文档

/**
     * 插入【一条】文档数据,如果文档信息已经【存在就抛出异常】
     *
     * @return 插入的文档信息
     */
    public Object insert() {
        // 设置用户信息
        User user = new User()
//                .setId("10")
                .setAge(22)
                .setSex("男")
                .setRemake("无")
                .setSalary(2800)
                .setName("张三")
                .setBirthday(new Date())
                .setStatus(new Status().setHeight(180).setWeight(150));
        // 插入一条用户数据,如果文档信息已经存在就抛出异常
        User newUser = mongoTemplate.insert(user, COLLECTION_NAME);
        // 输出存储结果
        log.info("存储的用户信息为:{}", newUser);
        return newUser;
    }

    /**
     * 插入【多条】文档数据,如果文档信息已经【存在就抛出异常】
     *
     * @return 插入的多个文档信息
     */
    public Object insertMany() {
        // 设置两个用户信息
        User user1 = new User()
//                .setId("11")
                .setAge(22)
                .setSex("男")
                .setRemake("无")
                .setSalary(1500)
                .setName("王五")
                .setBirthday(new Date())
                .setStatus(new Status().setHeight(180).setWeight(150));
        User user2 = new User()
//                .setId("12")
                .setAge(22)
                .setSex("男")
                .setRemake("无")
                .setSalary(1500)
                .setName("赵柳")
                .setBirthday(new Date())
                .setStatus(new Status().setHeight(180).setWeight(150));
        // 使用户信息加入结合
        List<User> userList = new ArrayList<>();
        userList.add(user1);
        userList.add(user2);
        // 插入一条用户数据,如果某个文档信息已经存在就抛出异常
        Collection<User> newUserList = mongoTemplate.insert(userList, COLLECTION_NAME);
        // 输出存储结果
        for (User user : newUserList) {
            log.info("存储的用户信息为:{}", user);
        }
        return newUserList;
    }

如下图所示:

sringboot整合mongodb和mybatis_数据库

6.5、存储文档

/**
     * 存储【一条】用户信息,如果文档信息已经【存在就执行更新】
     *
     * @return 存储的文档信息
     */
    public Object save() {
        // 设置用户信息
        User user = new User()
//                .setId("11")
                .setAge(37)
                .setSex("女")
                .setRemake("无")
                .setSalary(2800)
                .setName("测试")
                .setBirthday(new Date())
                .setStatus(new Status().setHeight(169).setWeight(150));
        // 存储用户信息,如果文档信息已经存在就执行更新
        User newUser = mongoTemplate.save(user, COLLECTION_NAME);
        // 输出存储结果
        log.info("存储的用户信息为:{}", newUser);
        return newUser;
    }

6.6、查询全部文档

/**
 * 查询集合中的【全部】文档数据
 *
 * @return 全部文档列表
 */
public Object findAll() {
    // 执行查询集合中全部文档信息
    List<User> documentList = mongoTemplate.findAll(User.class, COLLECTION_NAME);
    // 输出结果
    for (User user : documentList) {
        log.info("用户信息:{}", user);
    }
    return documentList;
}

6.7、根据编号查询文档

/**
 * 根据【文档ID】查询集合中文档数据
 *
 * @return 文档信息
 */
public Object findById(String id) {
    // 根据文档ID查询集合中文档数据,并转换为对应 Java 对象
    User user = mongoTemplate.findById(id, User.class, COLLECTION_NAME);
    // 输出结果
    log.info("用户信息:{}", user);
    return user;
}

注意:在 MongoDB 中可以手动指定文档主键 ID,如果未手动指定则 MongoDB 会生成 12 位的 ObjectID。

若主键列设置为String类型,且未手动设置主键。后期根据id查询时会返回null。可根据具体业务场景设置合理的取值。

ObjectId类型数据如下图所示:

sringboot整合mongodb和mybatis_数据库_02

查询出来如下图所示:

sringboot整合mongodb和mybatis_mongodb_03

6.8、分页查询、排序

/**
     * 根据【条件】查询集合中【符合条件】的文档,获取其【文档列表】【分页】并【排序】
     *
     * @return 符合条件的文档列表
     */
    public Object findByConditionAndSort(String sex, String sort, Integer page, Integer size) {
        // 创建条件对象
        Criteria criteria = Criteria.where("sex").is(sex);
        // 创建查询对象,然后将条件对象添加到其中,然后根据指定字段进行排序
        Query query = new Query(criteria).with(Sort.by(sort));
        query.with(Sort.by(
//                Sort.Order.asc("readOrNot"),
                Sort.Order.desc("age")
        ));
        // 执行查询
        Pageable pageable = PageRequest.of(page, size);
        // 查询记录总数
        long totalCount = mongoTemplate.count(query, COLLECTION_NAME);
        //查询分页后的记录
        List<User> documentList = mongoTemplate.find(query.with(pageable), User.class, COLLECTION_NAME);
        // 计算总页数
        int totalPage = (int) (totalCount % size == 0 ? totalCount / size : totalCount / size + 1);
        log.info("totalPage:{}", totalPage);
        // 输出结果
        for (User user : documentList) {
            log.info("用户信息:{}", user);
        }
        return documentList;
    }

6.9、聚合分组(group+count)

/**
 * 使用管道操作符 $group 结合 $count 方法进行聚合统计
 *
 * @return 聚合结果
 */
public Object aggregationGroupCount() {
    // 使用管道操作符 $group 进行分组,然后统计各个组的文档数量
    AggregationOperation group = Aggregation.group("age").count().as("numCount");
    // 将操作加入到聚合对象中
    Aggregation aggregation = Aggregation.newAggregation(group);
    // 执行聚合查询
    AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, COLLECTION_NAME, Map.class);
    for (Map result : results.getMappedResults()) {
        log.info("{}", result);
    }
    return results.getMappedResults();
}

响应形式:

[
    {
        "_id": 34,
        "numCount": 1
    },
    {
        "_id": 55,
        "numCount": 1
    },
    {
        "_id": 22,
        "numCount": 2
    }
]

6.10、聚合分组(group+push)

/**
 * 使用管道操作符 $group 结合表达式操作符 $push 获取某字段列表
 *
 * @return 聚合结果
 */
public Object aggregationGroupPush() {
    // 先对数据进行排序,然后使用管道操作符 $group 进行分组,然后以数组形式列出某字段的全部值
    AggregationOperation push = Aggregation.group("age").push("salary").as("salaryFirst");
    // 将操作加入到聚合对象中
    Aggregation aggregation = Aggregation.newAggregation(push);
    // 执行聚合查询
    AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, COLLECTION_NAME, Map.class);
    for (Map result : results.getMappedResults()) {
        log.info("{}", result);
    }
    return results.getMappedResults();
}

响应形式:

[
    {
        "_id": 55,
        "salaryFirst": [
            1500
        ]
    },
    {
        "_id": 22,
        "salaryFirst": [
            1500,
            1500
        ]
    },
    {
        "_id": 34,
        "salaryFirst": [
            1500
        ]
    }
]

6.11、创建索引

/**
 * 创建升序索引
 *
 * @return 索引信息
 */
public Object createAscendingIndex() {
    // 设置字段名称
    String field = "age";
    // 创建索引
    return mongoTemplate.getCollection(COLLECTION_NAME).createIndex(Indexes.ascending(field));
}

/**
 * 创建降序索引
 *
 * @return 索引信息
 */
public Object createDescendingIndex() {
    // 设置字段名称
    String field = "age";
    // 创建索引
    return mongoTemplate.getCollection(COLLECTION_NAME).createIndex(Indexes.descending(field));
}

/**
 * 创建升序复合索引
 *
 * @return 索引信息
 */
public Object createCompositeIndex() {
    // 设置字段名称
    String field1 = "name";
    String field2 = "age";
    // 创建索引
    return mongoTemplate.getCollection(COLLECTION_NAME).createIndex(Indexes.ascending(field1, field2));
}

/**
 * 创建文字索引
 *
 * @return 索引信息
 */
public Object createTextIndex() {
    // 设置字段名称
    String field = "name";
    // 创建索引
    return mongoTemplate.getCollection(COLLECTION_NAME).createIndex(Indexes.text(field));
}

/**
 * 创建哈希索引
 *
 * @return 索引信息
 */
public Object createHashIndex() {
    // 设置字段名称
    String field = "name";
    // 创建索引
    return mongoTemplate.getCollection(COLLECTION_NAME).createIndex(Indexes.hashed(field));
}

/**
 * 创建升序唯一索引
 *
 * @return 索引信息
 */
public Object createUniqueIndex() {
    // 设置字段名称
    String indexName = "name";
    // 配置索引选项
    IndexOptions options = new IndexOptions();
    // 设置为唯一索引
    options.unique(true);
    // 创建索引
    return mongoTemplate.getCollection(COLLECTION_NAME).createIndex(Indexes.ascending(indexName), options);
}

/**
 * 创建局部索引
 *
 * @return 索引信息
 */
public Object createPartialIndex() {
    // 设置字段名称
    String field = "name";
    // 配置索引选项
    IndexOptions options = new IndexOptions();
    // 设置过滤条件
    options.partialFilterExpression(Filters.exists("name", true));
    // 创建索引
    return mongoTemplate.getCollection(COLLECTION_NAME).createIndex(Indexes.ascending(field), options);
}

6.12、查询索引

/**
 * 获取当前【集合】对应的【所有索引】的【名称列表】
 *
 * @return 当前【集合】所有【索引名称列表】
 */
public Object getIndexAll() {
    // 获取集合中所有列表
    ListIndexesIterable<Document> indexList = mongoTemplate.getCollection(COLLECTION_NAME).listIndexes();
    // 创建字符串集合
    List<Document> list = new ArrayList<>();
    // 获取集合中全部索引信息
    for (Document document : indexList) {
        log.info("索引列表:{}",document);
        list.add(document);
    }
    return list;
}

6.13、删除索引

/**
 * 根据索引名称移除索引
 */
public void removeIndex() {
    // 设置索引名称
    String indexName = "age_-1";
    // 删除集合中某个索引
    mongoTemplate.getCollection(COLLECTION_NAME).dropIndex(indexName);
}

/**
 * 移除全部索引
 */
public void removeIndexAll() {
    // 删除集合中全部索引
    mongoTemplate.getCollection(COLLECTION_NAME).dropIndexes();
}

七、注意事项(踩坑)

7.1、ObjectId主键

说明:ObjectId 是一个12字节 BSON 类型数据,有以下格式:前4个字节表示时间戳,接下来的3个字节是机器标识码,紧接的两个字节由进程id组成(PID),最后三个字节是随机数。

MongoDB中存储的文档必须有一个"_id"键。这个键的值可以是任何类型的,默认是个ObjectId对象。

在一个集合里面,每个文档都有唯一的"_id"值,来确保集合里面每个文档都能被唯一标识。

MongoDB采用ObjectId,而不是其他比较常规的做法(比如自动增加的主键)的主要原因,因为在多个 服务器上同步自动增加主键值既费力还费时。

八、参考文档

8.1、Docker安装Mongodb

https://www.runoob.com/docker/docker-install-mongodb.html

8.2、Sheel命令操作文档

https://www.runoob.com/mongodb/mongodb-delete-collection.html

8.3、Java示例参考

http://www.mydlq.club/article/85/#1maven-%E5%BC%95%E5%85%A5%E7%9B%B8%E5%85%B3%E4%BE%9D%E8%B5%96

8.4、Github示例参考

https://github.com/my-dlq/blog-example/tree/master/springboot/springboot-mongodb-example