一、关于MongoDB

1、MongoDB是什么?

MongoDB是一款为web应用程序和互联网基础设施设计的数据库管理系统。没错MongoDB就是数据库,是NoSQL类型的数据库

2、为什么要用MongoDB

  • MongoDB提出的是文档、集合的概念,使用BSON(类JSON)作为其数据模型结构,其结构是面向对象的而不是二维表,存储一个用户在MongoDB中是这样子的。

    1
    2
    3
    4
    {
    username: "123",
    password: "123"
    }

    使用这样的数据模型,使得MongoDB能在生产环境中提供高读写的能力,吞吐量较于mysql等SQL数据库大大增强。

  • 易伸缩,自动故障转移。

    易伸缩指的是提供了分片能力,能对数据集进行分片,数据的存储压力分摊给多台服务器。自动故障转移是副本集的概念,MongoDB能检测主节点是否存活,当失活时能自动提升从节点为主节点,达到故障转移。

  • 数据模型因为是面向对象的,

    所以可以表示丰富的、有层级的数据结构,比如博客系统中能把“评论”直接怼到“文章“的文档中,而不必像myqsl一样创建三张表来描述这样的关系。

3、主要特性

  1. 文档数据类型

    SQL类型的数据库是正规化的,可以通过主键或者外键的约束保证数据的完整性与唯一性,所以SQL类型的数据库常用于对数据完整性较高的系统。MongoDB在这一方面是不如SQL类型的数据库,且MongoDB没有固定的Schema,正因为MongoDB少了一些这样的约束条件,可以让数据的存储数据结构更灵活,存储速度更加快。

  2. 即时查询能力

    MongoDB保留了关系型数据库即时查询的能力,保留了索引(底层是基于B tree)的能力。这一点汲取了关系型数据库的优点,相比于同类型的NoSQL redis 并没有上述的能力。

  3. 复制能力

    MongoDB自身提供了副本集能将数据分布在多台机器上实现冗余,目的是可以提供自动故障转移、扩展读能力。

  4. 速度与持久性

    MongoDB的驱动实现一个写入语义 fire and forget ,即通过驱动调用写入时,可以立即得到返回得到成功的结果(即使是报错),这样让写入的速度更加快,当然会有一定的不安全性,完全依赖网络。

    MongoDB提供了Journaling日志的概念,实际上像mysql的bin-log日志,当需要插入的时候会先往日志里面写入记录,再完成实际的数据操作,这样如果出现停电,进程突然中断的情况,可以保障数据不会错误,可以通过修复功能读取Journaling日志进行修复。

  5. 数据扩展
    MongoDB使用分片技术对数据进行扩展,MongoDB能自动分片、自动转移分片里面的数据块,让每一个服务器里面存储的数据都是一样大小。

二、安装MongoDB

1、下载MongoDB安装包

进入下载页面:https://www.mongodb.com/try/download/community-kubernetes-operator

根据自己的平台来下载MongoDB,我的是Windows所以我就选Windows x64

点击 Download 按钮,下载文件

下载好,点击安装文件

选择 Custom 按钮 自定义安装路径

选择 Browse 按钮选择安装路径

点击next,进入到下一页面

注意:一定要把 左下角的 Install MongoDB Compass 的勾选给取消掉,这个按钮的意思是下载compass客户端可视化界面,如果勾选了,下载会很慢。而compass客户端可视化界面随时都可以下载,所以取消勾选
Compass图形化下载地址: https://www.mongodb.com/try/download/compass

MongoDB下载后里面的文件

2、配置 MongoDB 数据库的环境

安装好后配置 MongoDB 数据库的环境:

按 win + i 打开 windows设置界面

点击系统-关于-高级系统设置-环境变量

找到 path,并打开路径,将自己的 MongoDB 的 bin 文件地址添加上去

3、运行 MongoDB服务

在data文件夹中创建 db 文件夹,因为启动 MongoDB服务之前需要必须创建数据库文件的存放文件夹,否则命令不会自动创建,而且不能启动成功

启动MongoDB服务

其一、打开 cmd 命令;

即:用Win + R键打开,输入cmd;

其二、找到安装 MongoDb的 db文件夹

其三、输入命令,来启动 MongoDb 服务

1
mongod --dbpath "D:\Program Files\MongoDB\Server\7.0.7\data\db"

注意:此时的地址就是你建的 db 目录,然后再回车,此时的服务就启动了

仔细找一下,就会找到端口号一般为:‘27017’;

其四、然后在浏览器中输入地址和端口号为:

1
http://localhost:27017

若显示结果如下,就说明安装成功并结束

其五、如何结束服务

按’Ctrl + C’,就结束了该次进程;

4、配置本地 Windows MongoDB服务

这样可设置为 开机自启动,可直接手动启动关闭,可通过命令行net start MongoDB 启动。该配置会大大方便;也不要在进入bin的目录下启动了;

  1. 在 data 文件下创建新文件夹log; (用来存放日志文件)

  2. 在 MongoDB 中新建配置文件 mongo.config; (与bin目录同级)

  3. 用记事本打开 mongo.config文件,并输入下面两个命令,然后保存;

    (注意:以自己的实际安装的文件地址为准)

    1
    2
    dbpath=D:\Program Files\MongoDB\Server\7.0.7\data\db
    logpath=D:\Program Files\MongoDB\Server\7.0.7\data\mongo.log

  4. 用管理员身份打开 cmd,然后找到 bin 文件地址为: “D:\Program Files\MongoDB\Server\7.0.7\bin”,并输入代码为:

    1
    mongod -dbpath "D:\Program Files\MongoDB\Server\7.0.7\data\db" -logpath "D:\Program Files\MongoDB\Server\7.0.7\data\mongo.log" -install -serviceName "MongoDB"

    而命令中的”MongoDB”就是之后启动 MongoDB服务的名字

    注意:这个一定在管理员身份的cmd中运行,否则执行下面命令会一直报错

    • 如何才能找到管理员命令下的cmd
      在搜索框中搜索cmd,然后选择以管理员身份运行即可
    • 输入相关的命令,然后本地 Windows MongoDB 服务的配置就完成了
  5. 在cmd管理员中启动和关闭 MongoDB服务

    • 启动 MongoDB 命令为:net start MongoDB

      然后在浏览器中输入地址和端口号为:

      1
      http://localhost:27017

      显示结果如下,就说明 MongoDB 服务已启动;

    • 在 MongoDB 服务启动后,会在设置中的服务看到正在运行的MongoDB服务
      打开设置中服务的过程:在 运行 中输入命令:services.msc

    • 关闭 MongoDB 命令为:net stop MongoDb

      然后在浏览器中输入地址和端口号为:

      1
      http://localhost:27017

      显示结果如下,就说明 MongoDB 服务已结束;

三、MongoDB的操作

1、体系结构

SQL术语/概念 MongoDB术语/概念 解释/说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
table joins 表连接,MongoDB不支持
嵌入文档 MongoDB通过嵌入式文档来代替多表连接
primary key primart key 主键,MongoDB自动将_id字段设置为主键

2、数据类型

MongoDB的最小存储单位就是文档document对象。文档document对象对应于关系型数据库的行。数据在MongoDB中以BSON(Binary-JSON)文档的格式存储在磁盘上。

BSON(Binary Serialized Document Format)是一种类json的一种二进制形式的存储格式,简称 Binary JSON;BSON和JSON一样,支持内嵌的文档对象和数组对象,但是BSON有JSON没有的一些数据类型,如Date和Bin Data类型。

BSON采用了类似于C语言结构体的名称、对表示方法,支持内嵌的文档对象和数组对象,具有轻量性、可遍历性、高效性的三个特点,可以有效描述非结构化数据和结构化数据。这种格式的优点是灵活性高,但它的缺点是空间利用率不是很理想。

BSON中,除了基本JSON类型: string,integer,boolean,double,null,array和object,mongo还使用了特殊的数据类型。这些类型包括 date, object id, binary data, regular expression和code。每一个驱动都以特定语言的方式实现了这些类型,查看你的驱动的文档来获取详细信息

BSON数据类型参考列表:

数据类型 描述 举例
字符串 UTF-8字符串都可表示为字符串类型的数据 {“x”: “foobar”}
对象id 对象id是文档的12字节的唯一ID {“x”: Objectid{} }
布尔值 真或者假:true或者flase {“x”: true}+
数组 值的集合或者列表可以表示成数组 {“x”: [“a”,”b”,”c”]}
32位整数 类型不可用。JavaScript仅支持64位浮点数,所以32位整数会被自动转换 shell是不支持该类型的,shell中默认会转换成64位浮点数
64位整数 不支持这个类型。shell会使用一个特殊的内嵌文件来显示64位整数 shell是不支持该类型的,shell中默认会转换成64位浮点数
64位浮点数 shell中的数字就是这一种类型 {“x”: 3.1415926, “y”: 3}
null 表示空值或者未定义的对象 {“x”: null}
undefined 文档中也可以使用未定义类型 {“x”: undefined}
符号 shell不支持,shell会将数据库中的符号类型的数据自动转换成字符串
正则表达式 文档中可以包含正则表达式,采用JavaScript的正则表达式语法 {“x”: /foobar/i}
代码 文档中还可以包含JavaScript代码 {“x”: function() { /*……*/ } }
二进制数据 二进制数据可以由任意字节的串组成,不过shell中无法使用
最大值/最小值 BSON包括一个特殊类型,表示可能的最大值。shell中没有这个类型

提示:

shell默认使用64位浮点型数值。{“x”:3.14或{“x”:3}。对于整型值,可以使用NumberInt(4字节符号整数)或 NumberLong(8字节符号整数),{“x”:NumberInt(“3” ){“x”:NumberLong(“3”)}

3、基本常用命令

  1. 数据库相关

    查看所有数据库

    1
    show databases

    选择数据库(如果数据库不存在,不会报错;会隐式创建:当后期该数据库有数据时自动创建)

    1
    use 数据库名

    删除数据库(先选中数据库)

    1
    db.dropDatabase()
  2. 集合相关

    查看所有集合

    1
    show collections

    创建集合(插入数据会隐式创建)

    1
    db.createCollection('集合名')

    删除集合

    1
    db.集合名.drop()

    CRUD

    我们可以在可视化界面方便的查看和操作MongoDB

    可以在Navicat Premium连接 也可以用 Compass连接

    Navicat Premium连接

    Compass连接

    注意:如果你的电脑名称是中文连接的时候就会报错

增 Create

插入文档

官方插入方法文档

1
db.集合名.insert(json数据)

集合存在则直接插入数据,不存在则隐式创建集合并插入数据

json数据格式要求key得加”“,但这里为了方便查看,对象的key统一不加”“;查看集合数据时系统会自动给key加””

mongodb会自动给每条数据创建全球唯一的_id键(我们也可以自定义_id的值,只要给插入的json数据增加_id键即可覆盖,但是不推荐这样做)

示例:

1
2
3
4
5
6
db.book.insert({
"name": "书名",
"type": "玄幻",
"description": "简介",
"readingVolume": 0
})

MongoDB并不需要创建字段,在添加的时候会自动创建,但是需要注意的是,在后续的添加中如果集合中并不存在相应的key会再次自动创建字段

删 Delete

官方删除方法文档

1
db.集合名.remove(条件[,是否删除一条])

是否删除一条

  • false删除多条,即全部删除(默认)
  • true删除一条

示例:

1
db.book.remove({name: "书名"})

改 Update

官方修改方法文档

1
db.集合名.update(条件,新数据[,是否新增,是否修改多条])

新数据

  • 默认是对原数据进行替换
  • 若要进行修改,格式为 {修改器:{key:value}}

是否新增

  • 条件匹配不到数据时是否插入: true插入,false不插入(默认)

是否修改多条

  • 条件匹配成功的数据是否都修改: true都修改,false只修改一条(默认)

修改器

  • $inc 递增
  • $rename 重命名列
  • $set 修改列值
  • $unset 删除列

示例:

为name为书名的阅读量增加1

1
db.book.updata({name: "书名"},{$inc: {readingVolume:1}})

查 Read

官方查看方法文档

1
2
db.集合名.find(条件[,查询的列])
db.集合名.find(条件[,查询的列]).pretty()#格式化查看

条件

  • 查询所有数据{}或不写
  • 查询指定要求数据 {key:value} 或 {key:{运算符:value}}

查询的列(可选参数)

  • 不写则查询全部列
  • {key:1}只显示key列
  • {key:0}除了key列都显示
  • 注意:_id列都会存在

运算符

  • $gt 大于
  • $gte 大于等于
  • $lt 小于
  • $lte 小于等于
  • $ne 不等于
  • $in in 匹配数组中指定的任何值。
  • $nin not in 不匹配数组中指定的任何值

示例:

查看所有数据并只显示name列

1
db.book.find({},{name: 1})

排序&分页

排序

官方排序方法文档

1
db.集合名.find().sort(json数据)

json数据(key:value)

  • key就是要排序的字段
  • value为1表示升序,-1表示降序

示例:

按照阅读量升序排序

1
db.book.find().sort({readingVolume: 1})

分页

官方游标方法文档

1
db.集合名.find().sort().skip(数字).limit(数字)[.conunt()]

skip(数字)

  • 指定跳过的数量(可选)

limit(数字)

  • 限制查询的数量

conunt()

  • 统计数量

示例:

例如每页有5条,你要查第2页

1
db.book.find().skip(5).limit(5)

索引

  1. 简介

    索引是一种排序好的便于快速查询数据的数据结构,用于帮助数据库高效的查询数据

    优点:

    • 提高数据查询的效率,降低数据库的IO成本

    • 通过索引对数据进行排序,降低数据排序的成本,降低CPU的消耗

    缺点:

    • 占用磁盘空间

      大量索引影响SQL语句的执行效率,因为每次插入和修改都要更新索引

  2. 语法

    创建索引语法:

    创建索引

    1
    db.集合名.createIndex(待创建索引的列:方式 [,额外选项])

    创建复合索引

    1
    db.集合名.createIndex({key1:方式,key2:方式} [,额外选项])

    参数说明:

    • 待创建索引的列:方式:{key: 1}/{key: -1}
      1表示升序,-1表示降序;例如{age: 1}表示创建age索引并按照升序方法排列

    • 额外选项:设置索引的名称或者唯一索引等
      设置名称:{name: 索引名}
      唯一索引:{unique: 列名}

    删除索引语法:

    删除全部索引

    1
    db.集合名.dropIndexes()

    删除指定索引

    1
    db.集合名.dropIndex(索引名)

    查看索引语法:

    查看索引

    1
    db.集合名.getIndexes()

权限机制

安装完MongoDB后,在命令行输入命令即可登录数据库,这肯定是不安全的,我们需要使用权限机制,开启验证模式

创建账号

1
2
3
4
5
6
7
8
db.createUser({
"user":"账号",
"pwd":"密码",
"roles":[{
role:"角色",
db:"所属数据库"
}]
})

角色种类

超级用户角色:root
数据库用户角色:read、readWrite
数据库管理角色:dbAdmin、userAdmin
集群管理角色: clusterAdmin、clusterManager、clusterMonitor、hostManager
备份恢复角色: backup、restore
所有数据库角色: readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase

角色说明

root:只在admin数据库中可用。超级账号,超级权限;
read:允许用户读取指定数据库;
readWrite:允许用户读写指定数据库

开启验证模式

验证模式:指用户需要输入账号密码才能登录使用

四、SpringBoot 集成 MongoDB

基本增删改查

  1. 首先在 pom.xml 导入MongoDB 的依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
  2. 在 application.yaml 中配置 mongodb 的属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    spring:
    # 数据源配置
    data:
    mongodb:
    # 主机地址
    host: localhost
    # 数据库
    database: test
    # 端口号
    port: 27017
    # 也可以使用uri
    # uri: mongodb://localhost:27017/test
  3. 创建 MongoConfig 配置 MongoTemplate 的 Bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Configuration
    public class MongoConfig {

    @Bean(name = "mongoTemplate")
    public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory, MongoMappingContext mongoMappingContext) {
    DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDatabaseFactory);
    MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
    //去掉_class字段,如果不去除在进行insert操作后,会自动增加_class字段,值为实体类的地址
    mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
    return new MongoTemplate(mongoDatabaseFactory,mappingConverter);
    }
    }
  4. 创建对应集合的实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    //对应集合名
    @Document(collection = "book")
    public class Book {
    /**
    * 编号
    */
    @Id
    private String id;

    /**
    * 图书名称
    * Indexed 索引
    */
    @Indexed(unique = true)
    private String name;

    /**
    * 图书类型
    */
    private String type;

    /**
    * 图书简介
    */
    private String description;

    /**
    * 阅读量 默认为0
    */
    @Field("readingVolume")
    private Integer readingVolume = 0;

    @Override
    public String toString() {
    return "Book{" +
    "id='" + id + '\'' +
    ", name='" + name + '\'' +
    ", type='" + type + '\'' +
    ", description='" + description + '\'' +
    '}';
    }
    }
  5. 创建 Repository 相当于 Mapper

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Repository
    public interface BookRepository extends MongoRepository<Book,String> {
    /**
    * 分页并根据书名模糊查询
    * @param name 书名
    * @param pageable 分页参数
    * @return 图书信息
    */
    Page<Book> findByNameLike(String name, Pageable pageable);
    }

    继承 MongoRepository 格式 MongoRepository<实体类名, 主键字段类型>

    Repository 中可以自定义复杂方法,只要符合命名规范,就会自动生成查询语句

  6. 创建Service定义基本的增删改查和我在 Repository 中定义的分页模糊查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    public interface BookService {
    /**
    * 新增图书
    * @param book 图书信息
    */
    public void insertBook(Book book);

    /**
    * 分页模糊查询图书信息
    * @param name 图书名称
    * @param page 当前页
    * @param size 每页数量
    * @return 图书信息
    */
    public Page<Book> findBookListByName(String name, int page, int size) ;

    /**
    * 根据图书id查询一条数据,并增加阅读量
    * @param id 图书id
    * @return 图书信息
    */
    public Book findBookById(String id);

    /**
    * 查询全部图书信息
    * @return 图书信息
    */
    public List<Book> findAll();

    /**
    * 更新图书
    * @param book 图书信息
    */
    public void updateBook(Book book);

    /**
    * 根据id删除图书信息
    * @param id 删除图书信息
    */
    public void deleteBookById(String id);

    }
  7. 创建 ServiceImpl 实现方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    @Service
    @AllArgsConstructor
    @Slf4j
    public class BookServiceImpl implements BookService{

    //Book 持久层
    private final BookRepository bookRepository;
    //mongodb 操作模版
    private final MongoTemplate mongoTemplate;

    private final GridFsTemplate gridFsTemplate;

    private final GridFSBucket gridFSBucket;

    /**
    * 增加图书
    * @param book 图书信息
    */
    @Override
    public void insertBook(Book book) {
    bookRepository.insert(book);
    }

    /**
    * 批量增加图书
    * @param books 图书信息集合
    */
    @Override
    public void insertBookBatch(List<Book> books) {
    bookRepository.insert(books);
    }

    /**
    * 分页并根据书名模糊查询图书信息
    * @param name 图书名称
    * @param page 当前页
    * @param size 每页数量
    * @return 图书信息
    */
    @Override
    public Page<Book> findBookListByName(String name, int page, int size) {
    return bookRepository.findByNameLike(name, PageRequest.of(page - 1, size));
    }

    /**
    * 根据id查询图书信息,并增加该图书的阅读量
    * @param id 图书id
    * @return 图书信息
    */
    @Override
    public Book findBookById(String id) {
    Book book = bookRepository.findById(id).get();
    book.setReadingVolume(book.getReadingVolume() + 1);
    bookRepository.save(book);
    return book;
    }

    /**
    * 查询全部图书信息
    * @return 图书信息
    */
    @Override
    public List<Book> findAll() {
    return bookRepository.findAll();
    }

    /**
    * @param book 图书信息
    */
    @Override
    public void updateBook(Book book) {
    bookRepository.save(book);
    }

    /**
    * 根据id删除图书
    * @param id 删除图书信息
    */
    @Override
    public void deleteBookById(String id) {
    bookRepository.deleteById(id);
    }

    }
  8. 创建 Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    @RestController
    @RequestMapping("/book")
    @AllArgsConstructor
    public class BookController {
    // 图书 业务层
    private BookService bookService;

    /**
    * 分页并根据书名模糊查询
    * @param name 书名
    * @param page 当前页
    * @param size 每页数量
    * @return 图书信息
    */
    @GetMapping("/find")
    public R<Page<Book>> findBookListByName(String name, @DefaultValue("1") int page,@DefaultValue("10") int size){
    Page<Book> bookListByName = bookService.findBookListByName(name, page, size);
    return R.success(bookListByName);
    }

    /**
    * 根据id查询一条图书,并增加这个图书的阅读量
    * @param id 图书id
    * @return 图书信息
    */
    @GetMapping("/findById")
    public R<Book> findBookById(@Valid String id){
    Book bookById = bookService.findBookById(id);
    return R.success(bookById);
    }

    /**
    * 新增图书
    * @param book 图书信息
    * @return 操作结果
    */
    @PostMapping("/insert")
    public R<Void> insertBook(@RequestBody Book book){
    bookService.insertBook(book);
    return R.ok("新增成功");
    }

    /**
    * 更细图书信息
    * @param book 图书信息
    * @return 操作结果
    */
    @PutMapping("/update")
    public R<Void> updateBook(@RequestBody Book book){
    bookService.updateBook(book);
    return R.ok("修改图书信息成功");
    }

    /**
    * 根据id删除指定图书
    * @param id id
    * @return 操作结果
    */
    @DeleteMapping("/delete")
    public R<Void> deleteById(@Valid String id){
    bookService.deleteBookById(id);
    return R.ok("删除成功");
    }
    }

    可以看到也是可以正常使用的

MongoDB上传文件和下载文件

首先我们要知道 MongoDB 存储文件的地方在哪 可以看到是在一个叫GridFS 存储桶的地方

那么 GridFS 是什么

GridFS是用于存储和检索超过 16 MB大小限制的BSON文档文件的规范

注意
GridFS不支持多文档事务

相较于将一个文件存储在单条文档中,GridFS将文件分为多个部分或块[1],并将每个块存储为单独的文档。默认情况下,GridFS使用的块默认大小为255kB;也就是说,除最后一个块,GridFS会将文件划分为255 kB的块。最后一个块只有必要的大小。同样,最后的那个块也不会大于默认的块大小,仅使用所需的空间以及一些其他元数据。

GridFS使用两个集合来存储文件。一个集合存储文件块,另一个集合存储文件元数据。 GridFS集合一节详细介绍了每个集合。

当你从GridFS查询文件时,驱动程序将根据需要重新组装该文件所有的块。你可以对GridFS存储的文件进行范围查询。你还可以从文件的任意部分访问其信息,例如“跳到”视频或音频文件的中间。

GridFS不仅可用于存储超过16 MB的文件,而且还可用于存储您要访问的任何文件而不必将整个文件加载到内存中。另请参阅何时使用GridFS

什么时候使用 GridFS

在MongoDB中,使用GridFS存储大于16 MB的文件。

在某些情况下,在MongoDB数据库中存储大型文件可能比在系统级文件系统上存储效率更高。

  • 如果文件系统限制了目录中文件的数量,则可以使用GridFS来存储所需数量的文件。
  • 当你要访问大文件部分的信息而不必将整个文件加载到内存中时,可以使用GridFS来调用文件的某些部分,而无需将整个文件读入内存。
  • 当你希望保持文件和元数据在多个系统和设施之间自动同步和部署时,可以使用GridFS。使用地理分布的复制集时,MongoDB可以自动将文件及其元数据分发到多个mongod实例和设施。

如果您需要对整个文件的内容进行原子更新,请不要使用GridFS。或者,您可以存储每个文件的多个版本,并在元数据中指定文件的当前版本。上传文件的新版本后,您可以原子更新元数据中指示为“最新”状态的字段,然后在需要时删除以前的版本。

此外,如果文件均小于16 MB BSON文档大小限制,请考虑将每个文件存储在单个文档中,而不是使用GridFS。您可以使用BinData数据类型存储二进制数据。有关使用BinData的详细信息,请参见驱动程序文档。

GridFS Collections

GridFS将文件存储在两个集合中:

  • 块存储二进制块。有关详细信息,请参见chunks集合
  • 文件存储文件的元数据。有关详细信息,请参见文件集合

GridFS通过使用存储桶名称为每个集合添加前缀,将集合放置在一个公共存储桶中。默认情况下,GridFS使用两个集合以及一个名为fs的存储桶:

  • fs.files
  • fs.chunks

您可以选择其他存储桶名称,也可以在一个数据库中创建多个存储桶。完整集合名称(包括存储桶名称)受命名空间长度限制

块集合

块集合中的每个文档都代表了GridFS中表示的文件的不同的块。此集合中的文档具有以下格式:

1
2
3
4
5
6
{
"_id": <ObjectId>,
"files_id": <ObjectId>,
"n": <num>,
"data": <binary>
}

chunks 集合中的文档包含以下字段:

  • chunks._id
    块的唯一ObjectId
  • chunks.files_id
    files集合中指定的“父”文档的_id
  • chunk.n
    块的序列号。GridFS从0开始对所有块进行编号
  • chunks.data
    块 BSON 二进制类型的荷载

文件集合

文件集合中的每个文档代表 GridFS 中的一个文件

1
2
3
4
5
6
7
8
9
10
11
{
"_id": <ObjectId>,
"length": <num>,
"chunkSize": <num>,
"uploadDate": <timestamp>,
"md5": <hash>,
"filename": <string>,
"contentType": <string>,
"aliases": <string array>,
"metadata": <any>
}

files集合中的文档包含以下或全部字段:

  • files._id
    该文档的唯一标识符。_id是您为原始文档选择的数据类型。MongoDB文档的默认类型是 BSON ObjectId

  • files.length
    文档的大小(以字节为单位)

  • files.chunkSize
    每个块的大小(以字节为单位)。GridFS将文档分为大小为chunkSize的块,最后一个除外,后者仅根据需要而变大。默认大小为255 KB。

  • files.uploadDate
    GridFS首次存储这个文档的日期。此值为有日期类型。

  • files.md5

    过期
    FIPS 140-2禁止使用MD5算法。MongoDB驱动程序已弃用MD5支持,并将在未来版本中删除MD5的生成。需要文件摘要的应用程序应在GridFS外部实现它,并将其存储在files.metadata中。
    filemd5命令返回的完整文件的MD5哈希。此值为字符串类型。

  • files.filename
    可选的。GridFS文件的可读名称

  • files.contentType

    过期
    可选的。GridFS文件的有效MIME类型。仅应用程序用。
    使用files.metadata来存储与GridFS文件的MIME类型有关的信息。

  • files.aliases
    过期

    可选的。别名字符串数组。仅用于应用程序
    使用files.metadata来存储与GridFS文件的MIME类型有关的信息。

  • files.metadata
    可选的。元数据字段可以是任何数据类型,并且可以保存您要存储的任何其他信息。如果希望将其他任意字段添加到文件集合中的文档,请将其添加到元数据字段中的对象。

application.yaml 修改 Spring 上传文件大小的限制

1
2
3
4
5
spring:
servlet:
multipart:
max-file-size: 500MB
max-request-size: 500MB

MongoConfig 注册 GridFSBucket 的Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class MongoConfig {
@Value("${spring.data.mongodb.database}")
private String db;

@Bean(name = "mongoTemplate")
public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory, MongoMappingContext mongoMappingContext) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDatabaseFactory);
MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
//去掉_class字段
mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
return new MongoTemplate(mongoDatabaseFactory,mappingConverter);
}

@Bean
public GridFSBucket getGridFSBucket(MongoClient mongoClient){
MongoDatabase mongoDatabase = mongoClient.getDatabase(db);
return GridFSBuckets.create(mongoDatabase);
}
}

Service 创建上传文件和下载文件的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 上传文件
* @param multipartFile 文件
* @throws Exception 可能转换流失败
*/
public ObjectId uploadFile(MultipartFile multipartFile) throws Exception;

/**
* 下载文件
* @param fileId 文件编号
* @param response 响应
* @throws IOException 可能读取失败
*/
public void downloadFile(String fileId, HttpServletResponse response) throws IOException;

ServiceImpl 实现方法

  1. 注入 操作 GridFs 的 Bean

    1
    2
    3
    private final GridFsTemplate gridFsTemplate;

    private final GridFSBucket gridFSBucket;
    • GridFsTemplate
      GridFsTemplate是Spring Data MongoDB库中用于操作GridFS的一个类。它提供了一组方法,用于执行与GridFS相关的操作,例如存储文件、检索文件、删除文件等。
    • GridFSBucket
      它是MongoDB驱动程序中用于处理GridFS的一个类。 GridFS(Grid File System)是MongoDB用于存储和检索大型文件的规范。 GridFSBucket类提供了一组方法,用于执行与GridFS相关的常见操作,如上传文件、下载文件、删除文件等
  2. 上传文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * 上传文件
    * @param multipartFile 文件
    * @throws Exception 获取流可能失败
    */
    @Override
    public ObjectId uploadFile(MultipartFile multipartFile) throws Exception {
    try {
    //获取文件名
    String originalFilename = multipartFile.getOriginalFilename();
    //获取类型
    String contentType = multipartFile.getContentType();
    //获取流
    InputStream inputStream = multipartFile.getInputStream();
    //上传
    ObjectId objectId = gridFsTemplate.store(inputStream, originalFilename, contentType);
    return objectId;
    } catch (IOException e) {
    log.error("上传失败",e);
    throw new Exception("上传失败:{" + e + "}");
    }
    }
  3. 下载文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    /**
    * 下载我文件
    * @param fileId 文件编号
    * @param response 响应
    * @throws IOException 可能读取失败
    */
    @Override
    public void downloadFile(String fileId, HttpServletResponse response) throws IOException {
    log.info("准备下载文件....");
    //查询条件
    Query query = Query.query(Criteria.where("_id").is(fileId));
    //查询单个文件
    GridFSFile gridFSFile = gridFsTemplate.findOne(query);
    if(gridFSFile == null){
    return;
    }
    //获取文件名和后缀
    String fileName = gridFSFile.getFilename().replace(",", "");
    String contentType = gridFSFile.getMetadata().get("_contentType").toString();
    //通知浏览器进行文件下载
    response.setContentType(contentType);
    response.setHeader("Content-Disposition","attachment;filename=\""+ URLEncoder.encode(fileName,"UTF-8") + "\"");
    GridFSDownloadStream gridFSDownloadStream = gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
    GridFsResource resource = new GridFsResource(gridFSFile, gridFSDownloadStream);

    ServletOutputStream outputStream = response.getOutputStream();
    InputStream inputStream = resource.getInputStream();
    IOUtils.copy(inputStream,outputStream);
    outputStream.flush();
    outputStream.close();
    inputStream.close();
    }

Controller 定义接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 上传文件
* @param multipartFile 文件
* @return 上传结果
*/
@PostMapping("/uploadFile")
public R<String> uploadFile(@RequestParam("file") MultipartFile multipartFile){
try {
ObjectId objectId = bookService.uploadFile(multipartFile);
return R.success(String.valueOf(objectId));
} catch (Exception e) {
return R.fail(ReturnCode.RC500.getCode(),e.getMessage());
}
}

/**
* 下载文件
* @param fileId 文件编号
* @param response 响应
*/
@GetMapping("/downloadFile")
public void downloadFile(String fileId, HttpServletResponse response){
try {
bookService.downloadFile(fileId,response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

测试:

可以看到上传成功了

下载文件也没有问题