一. MongoDB介绍和安装
1. Nosql的认识
1.1. 什么是NoSQL
NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,它泛指非关系型数据库。随着互联网2003年之后web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的交友类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库NoSQL则由于其本身的特点得到了非常迅速的发展。非关系型数据库弥补了传统关系型数据库在高并发下的不足。
非关系型数据库不一定遵循传统数据库的一些基本要求,比如说,不遵循sql标准,事务,表结构等等
1.2. 关系型数据库与非关系型数据库的区别
关系型数据库,泛指有表的数据库(Mysql,Oracle,SqlServer)。是指采用了关系模型或二维表来组织数据的数据库
NoSQL泛指非关系型数据库,非关系型数据库泛指没有表的数据库(redis、MongoDB、Memcached)
1.数据一致性的要求不同
关系型数据库强调的是数据强一致性,即在对数据进行更新、插入或删除操作时,它必须保证数据的完整性和一致性,以避免数据的冲突和错误。非关系型数据库强调最终一致性,系统会在一定时间内自动将数据同步,而不需要实时同步。因此如果业务对于数据的一致性要求很高,那么非关系型数据库并不一个很好的选择
2.数据存储方式不同-适用性不同
关系型数据库采用的是关系模型,这意味着它们以表结构的形式存储数据,并且通过表格之间的关系进行数据的连接和查询。相比之下,非关系型数据库采用的是其他的数据模型,如文档模型、键值对模型、图形模型等。这种不同的数据模型决定了它们在不同场景下的适用性。关系型数据库适合处理结构化数据,而非关系型数据库适合处理半结构化和非结构化数据
3.扩展性不同
关系型数据库的扩展性相对较差,通常只能通过升级硬件或者增加节点来提高系统的性能。而非关系型数据库则采用分布式架构,可以通过添加节点来水平扩展系统的性能
4.数据查询语言不同
关系型数据库通常采用结构化查询语言(SQL)进行数据查询,这种语言基于严格的语法规则,可以进行复杂的数据查询和分析。而非关系型数据库则通常使用简单的键值对查询语言,如MongoDB的查询语言,这种语言更加灵活,但是限制了查询的复杂性
5.对事务性的支持不同
关系型数据库通常采用ACID(原子性、一致性、隔离性、持久性)的事务模型来保证数据的安全性和稳定性,如果对数据进行高事务性操作或者复杂数据查询就要用关系型数据库。NoSQL数据库是最终一致性,不保证ACID的事务模型,重视系统的可用性和性能,而不是强一致性
适用场景:
关系型数据库的适用场景(为了维护一致性所付出的巨大代价就是其读写性能比较差):
1.对数据要求强一致性,要进行事务管理
2.多表关联查询
非关系型数据库的适用场景:
1.高并发场景
2.海量数据的读写
1.3. Nosql分类
http://www.nosql-database.org/
分类 | Examples举例 | 典型应用场景 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
键值(key-value) | Tokyo Cabinet/Tyrant, Redis, Memcached,Voldemort, Oracle BDB | 内容缓存,主要用于处理大量数据的高并发场景,也用于一些日志系统等等。 | Key 指向 Value 的键值对,通常用hash table来实现 | 查找速度快 | 数据无结构化,通常只被当作字符串或者二进制数据 |
列存储数据库 | Cassandra, HBase, Riak | 分布式的文件系统,大数据场景 | 以列簇式存储,将同一列数据存在一起 | 查找速度快,可扩展性强,更容易进行分布式扩展 | 功能相对局限 |
文档型数据库 | CouchDB, MongoDb | Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) | Key-Value对应的键值对,Value为结构化数据 | 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 | 查询性能不高,而且缺乏统一的查询语法。 |
图形(Graph)数据库 | Neo4J, InfoGrid, Infinite Graph | 社交网络,推荐系统等。专注于构建关系图谱 | 图结构 | 利用图结构相关算法。比如最短路径寻址,N度关系查找等 | 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群 |
1.4. MongoDB与Redis
数据模型
MongoDB 是一个面向文档的数据库,它存储的数据是以 BSON(Binary JSON)格式存储的文档。这种文档可以包含多个字段,并且可以使用嵌套来表示复杂的数据结构。相比之下,Redis 是一个键值对存储数据库,它将数据存储在键值对中,其中键是唯一的标识符,而值可以是字符串、哈希、列表、集合等数据类型。
数据库特性
MongoDB 提供了一些高级的数据库特性,比如复制、分片、事务、索引等等。这些特性可以帮助应用程序实现高可用性、扩展性和数据完整性。相比之下,Redis 虽然也提供了一些特性,比如持久化、事务、发布/订阅等,但它并不支持分片和复制。
性能
MongoDB 的读写性能通常比 Redis 低。MongoDB 支持大量的查询语言,例如 SQL 或类似 SQL 的查询语言,这些语言提供了更高级的查询功能,因此读写速度相对较慢。相反,Redis 的读写速度通常很快,因为它是内存数据库,而内存的读写速度通常比磁盘要快得多。
数据库大小
MongoDB 是一个分布式数据库,它可以容纳大量数据。MongoDB 支持分片,使得它可以横向扩展,处理大量数据。相比之下,Redis 的数据库大小通常受到内存的限制。因为 Redis 是一个内存数据库,它的存储空间大小受到服务器内存的限制。
应用场景
MongoDB 更适合存储大量的非结构化数据,比如文档、图片、音频、视频等等。它适用于需要高可用性、高扩展性、事务支持和丰富查询功能的应用程序。相比之下,Redis 更适合存储小型的键值对、缓存数据、消息队列等等。它适用于需要快速读写、高并发、轻量级存储的应用程序
2. MongoDB的认识
2.1. 什么是MongoDB
2.1.1. 基本概念
下面引用百度百科的解释来介绍MongoDB
MongoDB是一个基于分布式文件存储 [1] 的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式(MongoDB存储的是BSON文档),因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
上面说到,MongoDB是介于关系型数据库和非关系型数据库之间的产品,其实更多的人把MongoDB直接当成关系数据库,其实这都不重要,重要的是我们需要搞清楚关系型数据库如:Mysql。非关系型数据库如:Redis和MongDB他们的区别以及使用场景
高性能: 数据写入内存即完成,持久化到硬盘的操作在后台异步完成。同时副本集的读写分离,分片模式的读写可以更大程度的提高吞吐量
高可用: 副本集模式会将数据同步在一到多个副本服务器,并支持故障自动转移,同时提供了数据的冗余备份,提高了数据的可用性, 保证了数据的安全性
2.1.2. 数据的存储
对于数据管理来说,Mysql关系型数据库的数据是存储到磁盘的,每次进行数据的读写都涉及到IO操作,它以二维表的方式来展现数据,可以很方便的描述对象以及对象之间的关系。它的性能瓶颈就是IO造成的高并发读写能力较弱。
而对于MongoDB而言,它的数据其实是存储到磁盘上的,所有要操作的数据都是通过mmap(内存映射文件)的方式映射到内存中,数据的操作就基于这片内存进行处理的,避免了磁盘的零碎操作,性能也是非常高的。当然mmap中的数据会flush到磁盘上。
Mysql通过二维表的方式来存储数据,支持索引,事务等,MongoDB 的文件存储是BSON格式【BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON】类似JSON,相对于JSON有更快的遍历速度,而且数据类型更多。查询功能比较强大,能存储海量数据,但是不支持事务
mmap 是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用 read、write 等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享
2.2. MongoDB使用场景
MongoDB 更适合存储大量的非结构化数据,比如文档、图片、音频、视频等等
注意:高度事务性的系统,例如银行或会计系统,还是要用传统的关系型数据库
社交场景:存储用户信息,朋友圈信息,通过地理位置索引实现附近的人等功能
游戏场景:存储游戏用户信息,用户的装备,积分等信息。高效的存储和访问
物流场景:存储订单,订单在运送过程中会不断更新。用MongoDB内嵌数组的形式来存储,一次查询就可以将订单所有的更新记录读取出来
视频直播:使用MongoDB存储用户信息,点赞信息和互动信息等
2.3. MongoDB的概念
在mongodb中有几个比较核心的概念:文档【mysql一行数据】、集合【mysql的表】、数据库【mysql数据库】,
下面是和关系型数据库的一个比照图:
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
2.3.1. 数据库database
一个mongodb中可以建立多个数据库,默认数据库为"db",数据库存储在data目录中,不同的数据库也放置在不同的文件中,数据库也通过名字来标识。
MongoDB默认保留了几个数据库:
- admin : admin数据库则主要存储MongoDB的用户、角色等信息。
- local : local数据库主要存储副本集的元数据,它只会在本地存储数据
- config : 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
2.3.2. 文档document
通常一个对象可以映射成一个文档,文档是一组键值(key-value)对(即 BSON,类似于JSON)。MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点。比如在数据库中的2行数据:
id | username | age |
---|---|---|
1 | zs | 19 |
2 | ls | 19 |
在MongoDB中的体现
{
"_id" : ObjectId("605d9a0b4d99251c4e70bd6d"),
"username" : "zs",
"id" : "1",
"age" : 19,
"title" : "ls"
}
{
"_id" : ObjectId("605d9a0b4d99251c4e70bd6e"),
"username" : "ls",
"id" : "2",
"age" : 19
}
2.3.3. 集合collections
一个集合包含一堆 MongoDB 文档,如果把Mysql中的一行数据当做一个文档,一张表格就相当于是MongoDB中的集合
集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。
2.3.4. MongoDB数据类型
下表为MongoDB中常用的几种数据类型:
数据类型 | 描述 |
---|---|
String | 字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。 |
Integer | 整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。 |
Boolean | 布尔值。用于存储布尔值(真/假)。 |
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. MongDB的安装
3.1. MongoDB安装
MongoDB 提供了可用于 32 位和 64 位系统的预编译二进制包,你可以从MongoDB官网下载安装,MongoDB 预编译二进制包下载地址:https://www.mongodb.com/download-center/community
下载后点击安装,选择自定义,如下(我这里使用的是4.4.版本):
选择“browse…”更换安装路径
这里选择“run service as network service user”
取消 “install MongoDB compass”,没有必要安装可视化界面,直接用navicat连接
配置环境变量
通过 cmd 执行 “mongo” 如下:
3.2. 命令操作数据库和集合
创建数据库:use 数据库名 - 如果数据库不存在,则创建并切换到该数据库,存在则切换到该数据库
删除数据库:先切换到数据库,然后使用db.dropDatabase()删除
创建集合:db.createCollection(集合名/表名)
查看所有集合:show collections
删除集合:db.集合名.drop()
添加数据:db.集合名.insert({bson格式}) - 其实可以直接添加数据,自动创建集合
修改数据:使用update()和save()方法来更新集合中的文档
save()如果有_id就是修改,没有就是添加
删除数据:
db.集合名.remove({bson格式})
查询数据:db.集合名.find(query)
如果不传query就是查询所有
如果传递了query就是按条件查询,格式也是bson格式
3.3. Navicat操作MongoDB
3.3.1. Navicat连接MongoDB
网络上有很多的MongoDB的可视化工具,随便百度一款就可以,我这里使用Navicat 15
输入连接名后(任意指定),这里直接默认确定即可,如果开启了账号认证那么需要在验证一栏选择password进行认证 ,
3.3.2. 创建数据库
3.3.3. 创建集合
3.3.4. 其他操作
二. MongoDB的权限管理
1. 权限认识
1.1. 角色认识
MongoDB安装完成后,默认是没有权限验证的,默认是不需要输入用户名密码即可登录的,但是往往数据库方面我们会出于安全性的考虑而设置用户名密码
在MongDB中,用户只能在用户所在数据库登录,包括管理员账号。mongo的用户是以数据库为单位来建立的,每个数据库有自己的用户
注:帐号是跟着库走的,所以在指定库里授权,必须也在指定库里验证
1.2. 内置角色
在MongDB中内置了一些用户角色roles:
编号 | 说明 | 角色 |
---|---|---|
1 | 数据库用户角色 | read,readWrite |
2 | 数据库管理角色 | dbAdmin,dbOwner,userAdmin |
3 | 集群管理角色 | clusterAdmin,clusterManager,clusterMonitor,hostManager |
4 | 备份恢复角色 | backup,restore |
5 | 所有数据库角色 | readAnyDatabase,readWriteAnyDatabase,userAdminAnyDatabase,dbAdminAnyDatabase |
6 | 超级用户角色 | root |
下面是每一种角色的解释
编号 | 角色 | 解释 |
---|---|---|
1 | read | 允许用户读取指定数据库 |
2 | readWrite | 允许用户读写指定数据库 |
3 | dbAdmin | 允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile |
4 | userAdmin | 允许用户向system.users集合写入,可以向指定数据库里创建、删除和管理用户 |
5 | clusterAdmin | 只在admin数据库中可用,具有所有分片和复制集相关函数的管理权限。 |
6 | readAnyDatabase | 只在admin数据库中可用,具有所有数据库的读权限 |
7 | readWriteAnyDatabase | 只在admin数据库中可用,具有所有数据库的读写权限 |
8 | userAdminAnyDatabase | 只在admin数据库中可用,具有所有数据库的userAdmin权限 |
9 | dbAdminAnyDatabase | 只在admin数据库中可用,具有所有数据库的dbAdmin权限。 |
10 | root | 只在admin数据库中可用。超级账号,超级权限 |
2. 使用命令管理用户
mongodb是没有默认管理员账号,所以要先添加管理员账号,再开启权限认证。添加账号需要使用use admin
切换到admin数据库,在admin数据库添加的账号才是管理员账号
管理员可以管理所有数据库,但是不能直接管理其他数据库,要先在admin数据库认证后才可以管理
对普通用户账号的很多操作需要用到管理员账号,而且Java代码操作也需要用到管理员账号,所以我们需要创建管理员账号
2.1. 命令创建管理员账号
windows中,cmd命令窗口,输入mongo切换到mongodb的命令窗口。查看数据库
> show dbs
# 只显示有集合的数据库
切换admin数据库
> use admin
查看用户
> show users
# 只显示当前数据库的用户
创建超级用户
db.createUser(
{
user: "root",
pwd: "123456",
roles: [ { role: "root", db: "admin" } ]
} )
# user : 用户名
# pwd : 密码
# roles : 角色
# db : 数据库
开启认证,修改配置文件 bin/mongod.cfg
,增加如下内容
security:
authorization: enabled
修改之后,重启mongdb服务 ,重新连接mongdb
1. 同时按住win + r键
2. 输出services.msc,回车
3. 重启MongoDB服务
登录mongdb
> use admin
> db.auth("root","123456")
2.2. 命令创建普通用户账号
创建普通数据库, 先切到admin数据库,然后db.auth(“用户”,“密码”) 登录
> use admin #切到amdin数据库
> db.auth("root","123456") #登录
通过 use 创建数据库 test,如果已经有该数据就会进行切换。如果数据库没有数据执行 show dbs 数据库不会被显示
> use blog
在 test 数据库中 ,创建用户 ,可以通过 db 命令查看当前是哪个数据库
注意:如果不切换到admin并登陆的话,执行下面的创建用户是不会成功的
> db.createUser({
user: "blog",
pwd: "123456",
roles: [{
role: "readWrite",
db: "blog" #注意这里是当前用户操作哪一个数据库,要写当前的数据库
}]
})
> show users
登录blog数据库
> use blog #切到blog数据库
> db.auth("blog","123456") #验证账号密码
2.3. 修改用户权限
先进入数据库,然后使用db.updateUser修改 , 比如下面就给blog用户新增了角色
> use admin
> db.auth("root","123456") #登录
> use blog
> db.updateUser("blog", {
roles: [{
role: "dbAdmin",
db: "teblogst"
}, {
role: "readWrite",
db: "blog"
}]
})
最终的权限以修改的权限为主
2.4. 修改用户密码
下面案例是更新密码
> use admin
> db.auth("root","123456") #登录
> use blog
> db.updateUser("blog",{"pwd":"654321"}) //注意: 第一个参数是用户名
或者使用下面命令
> db.changeUserPassword("blog","654321")
2.5. 删除用户
删除用户需要切换到对应的数据库,然后使用db.dropUser进行删除,需要具备:userAdmin ,userAdminAnyDatabase或者root权限才可以删除用户。
> use admin
> db.auth("root","123456")
> use blog
> db.dropUser("blog");
更多命令操作请查阅文档:https://blog.youkuaiyun.com/weixin_48964486/article/details/123526224
2.6. 开启认证之后客户端连接
注意:命令可以操作的,基本客户端都可以操作
开启认证之后用客户端连接mongodb时:
如果使用管理员账号连接:会显示所有数据库 - 除了自带的三个数据库
如果使用普通用户账号连接:只会显示当前数据库
三. MongoDB入门
在项目中我们更多的是使用Spring整合MongoDB进行开发,直接面向对象操作MongoDB的API即可,这里使用spring-boot-starter-data-mongodb
注意:SpringBoot项目中使用Mongodb有两种,一种是在Service注入MongoTemplate直接调用API,一种是继承MongoRepository
1. 集成Mongodb
第一步:创建项目导入基础依赖,SpringBoot和MongoDB
<parent>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
第二步:创建启动类
@SpringBootApplication
public class MongoDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MongoDemoApplication.class, args);
}
}
第三步:yml配置mongodb
spring:
data:
mongodb:
uri: mongodb://root:123456@127.0.0.1:27017/blog?authSource=admin&authMechanism=SCRAM-SHA-1
#uri: mongodb://用户名:密码@127.0.0.1:27017/数据库?authSource=admin&authMechanism=SCRAM-SHA-1 #注意这里用户名密码要写管理员账号,写用户账号不能操作数据
第四步:编写实体类,SpringData提供了@Document注解来标记文档对象 ,如下
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType;
@Data
@Document(collection = "article")//实体类对应mongodb的表/文档article
public class BlogArticle {
@Id //对应mongodb的表的_id,添加时不设置_id会自动增长
private String _id;
private Integer id;
//文章名称
@Field(name = "name",targetType = FieldType.STRING)
private String username;
//文章标签
@Field(name = "age",targetType = FieldType.INT32)
private Integer age;
//文章简介
private String address;
private String tel;
}
第五步:注入MongoTemplate直接调用其API
@Autowired //只能用指定的API
private MongoTemplate template;
2. MongDB的CRUD
2.1. collection集合操作
可以通过mongoTemplate.createCollectionl来创建collection,它会自动读取对象上的@Document(collection = “blog_article”)中的collection 作为集合的名字。也可以通过直接指定名字来创建
@Test
public void testCreate(){
System.out.println(template);
// 会根据类型上的@Document(collection="sys_user")生成集合
// template.createCollection(SysUser.class);
template.createCollection("sys_user");
}
注意:其实添加数据的时候可以自动创建集合,添加只有可以通过navicat看结果
2.2. 添加或修改
insert: 若新增数据的主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常提示主键重复,不保存当前数据。
save: 若新增数据的主键已经存在,则会对当前已经存在的数据进行修改操作
insert: 可以一次性插入一整个列表,而不用进行遍历操作,效率相对较高
save: 只能一个个的插入,如果是一个列表需要遍历
- 代码参考一:
@Test
public void testAddOrUpdate(){
BlogArticle blog = new BlogArticle();
blog.setArticleName("java基本数据类型有哪些");
blog.setArticleContent("整数byte,short,int,long。小数float,double,字符char,布尔boolean");
blog.setArticleState(1);
template.save(blog);
//BlogArticle(_id=63a90c7eac3d25228c2ae77d, id=null, articleName=java基本数据类型有哪些, articleState=1, articleContent=整数byte,short,int,long。小数float,double,字符char,布尔boolean, updateTime=Mon Dec 26 10:52:46 CST 2022)
System.out.println(blog);//添加之后返回主键
//由于主键一样,所以第二次save就是修改操作。collectionName可写可不写,不写会根据blog的类型检索@Document(collection = "blog_article")
template.save(blog,"blog_article");
}
- 代码参考二:
@Test
public void testAdd(){
BlogArticle blog1 = new BlogArticle();
blog1.setArticleName("测试数据1-博客名");
blog1.setArticleContent("测试数据1-博客内容");
blog1.setArticleState(1);
BlogArticle blog2 = new BlogArticle();
blog2.setArticleName("测试数据2-博客名");
blog2.setArticleContent("测试数据2-博客内容");
blog2.setArticleState(0);
List<BlogArticle> list = new ArrayList<>();
list.add(blog1);
list.add(blog2);
//template.insert(BlogArticle.class);//数据没有添加
//template.insert(blog1);//添加时返回自增主键
//System.out.println(blog1); //BlogArticle(_id=63a919dc8b290e088ddb9758, id=null
//再次添加报错org.springframework.dao.DuplicateKeyException
//template.insert(blog1);
//添加集合
template.insert(list,BlogArticle.class);
}
2.3. 删除
@Test
public void testDel(){
//BlogArticle blog1 = new BlogArticle();
//blog1.set_id("63a91ae906e123410f57468f");
//template.remove(blog1);//删除成功
//BlogArticle blog2 = new BlogArticle();
//blog2.setArticleName("测试数据1-博客名");
//该方式通过名称去删报错
//template.remove(blog2); //java.lang.IllegalStateException: Could not obtain identifier from BlogArticle
//删除成功:相当于delete from blog_article where articleName = "mybatis中#与$区别"
Query query = new Query(Criteria.where("articleName").is("mybatis中#与$区别"));
template.remove(query,BlogArticle.class);
}
2.4. 查询
- 使用PageRequest.of(0, 10);来构建分页
- 使用ExampleMatcher.matching().withMatcher构建条件匹配器
- Example.of(student,matcher);来构建一个查询对象
- repository.findAll(example, pageRequest);执行查询
@Test
public void testFind(){
//1.查询所有
//template.findAll(BlogArticle.class).forEach(System.out::println);
//2.通过id查询单个
//System.out.println(template.findById("63a92c4b2682f13e9b06472a", BlogArticle.class));
//3.通过条件查询单个:如果有多条数据只返回第一条
//Query query = new Query(Criteria.where("articleName").is("测试数据1-博客名"));
//System.out.println(template.findOne(query, BlogArticle.class));
//4.通过条件查询所有符合条件的数据:返回List
//Query query = new Query(Criteria.where("articleName").is("测试数据1-博客名"));
//template.find(query, BlogArticle.class).forEach(System.out::println);
//5.查询满足条件的总条数:查询articleName为"测试数据1-博客名"并且articleState为1的文档数量
//Query query = new Query(Criteria.where("articleName").is("测试数据1-博客名").and("articleState").is(1));
//System.out.println(template.count(query, BlogArticle.class));//2
//6.分页查询:查询第二页的数据
//Integer currentPage = 2;Integer pageSize = 3;
//Query query = new Query();
//template.find(query.skip((currentPage - 1) * pageSize).limit(pageSize),BlogArticle.class)
//.forEach(System.out::println);
//7.模糊查询
Criteria criteria = new Criteria();
//以*任意字符开始,以*任意字符结尾,忽略大小写
Pattern compile = Pattern.compile("^.*" + "博客" + ".*$", Pattern.CASE_INSENSITIVE);
//文章名正则匹配
criteria.and("articleName").regex(compile);
Query query = new Query(criteria);
template.find(query,BlogArticle.class).forEach(System.out::println);
}
2.5. 修改
@Test
public void testUpdate(){
Query query = new Query(Criteria.where("articleName").is("测试数据2-文章名"));
Update update = new Update();
update.set("articleContent","测试数据2-文章内容-editAll");
update.set("articleState",3);
//修改单条数据
//template.updateFirst(query,update,BlogArticle.class);
//修改所有数据
template.updateMulti(query,update,BlogArticle.class);
}
四. 评论系统开发
需求说明:由于文章的查询较为频繁,为了提高查询速度,减轻数据库的压力,要求文章列表从MongoDB中查询,单篇文章详情从数据库获取数据展示。另外文章的评论也存储到MongoDB中。
1. 整合MongoDB
1.1. 基本集成
导包 -> 配置 -> 注入使用
1.2. 编写Mongo的文章对象
可以直接使用domain,但是mongodb的_id是字符串类型,与实体domain不相符,导致添加报错。所以我们只需要在domain添加一个字段
@Data
@Document(collection = "t_article") //当前类对应mongodb的文档t_article
public class Article{
/**
* mongodb上的主键ID
*/
@Id
private String _id;
//其他字段没有变化
//......
}
2. 文章增删改操作
- 增删改同步到MongoDB。记得要先处理数据库,再处理MongoDB
@Override
public void add(BlogArticle blogArticle) {
//添加文章到Mysql
blogArticleMapper.add(blogArticle);
//注意:在添加的时候要设置返回自增id,这样在修改和删除的时候就可以通过自增id查询文章对象
//添加文章到Mongodb
template.insert(blogArticle);
}
@Override
public void update(BlogArticle blogArticle) {
//在mysql数据库中进行修改
blogArticleMapper.update(blogArticle);
//通过id值从mongodb中查询出以前的文章对象
Query query = new Query(Criteria.where("id").is(blogArticle.getId()));
BlogArticle oldBlogArticle = template.findOne(query, BlogArticle.class);
//将mongodb的_id设置到新对象中
blogArticle.set_id(oldBlogArticle.get_id());
//使用save修改:_id一样就是修改
template.save(blogArticle);
}
@Override
public void del(Long id) {
blogArticleMapper.del(id);
//通过id值从mongodb中删除
Query query = new Query(Criteria.where("id").is(id));
template.remove(query,BlogArticle.class);
}
3. 文章评论操作
说明:这里为了简化开发,只做了一级评论,没有做回复和点赞,也没有做分页
3.1. 文章评论实体
package cn.itsource.blog.domain;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import java.util.Date;
@Data
//对应集合/表t_comment ,如果集合名为comment与类名首字母一致,那么可以只写@Document,()可以省略
@Document(collection = "t_comment")
public class Comment {
/**
* 主键标识,映射的是mongodb的_id,名称一样,所以这里@Id是可以不写的
*/
@Id
private String id;
/**
* 文章id - 评论的是哪一篇文章
*/
@Field(name = "articleId") //字段名称不一致,可以使用@Field指定,这里实际上是可以省略的
private Long articleId;
/**
* 评论内容
*/
private String content;
/**
* 那个用户评论的
*/
private Long userId;
/**
* 评论人的昵称
*/
private String nickname;
/**
* 评论的时间
*/
private Date createTime = new Date();
/**
* 点赞数
*/
private Integer likenum = 0;
/**
* 回复数
*/
private Integer replynum = 0;
/**
* 状态:1可见,0不可见
*/
private Integer state;
/**
* 父级id
*/
private String parentId;
}
3.2. 文章评论repository
注意:SpringBoot项目中使用Mongodb有两种,一种是在Service注入MongoTemplate直接调用API,一种是实现MongoRepository
//String - 是主键id的数据类型
@Repository
public interface CommentRepository extends MongoRepository<Comment,String> {
}
这里还可以增加了一个根据文章ID查询评论列表的方法:
package cn.itsource.blog.repository;
/**
* 持久化接口: 评论Comment
*/
@Repository //将当前接口的实例交给Spring管理 - service注入使用
public interface CommentRepository extends MongoRepository<Comment,String> {
/**
* 根据文章id查询文章的所有评论
* findBy是一种语法格式,ArticleId不能写错,.必须要与Comment实体的字段对应
* @param articleId
* @return
*/
List<Comment> findByArticleId(Long articleId);
}
3.3. 编写评论的业务
@Service
public class CommentServiceImpl implements ICommentService {
@Autowired
private CommentRepository repository;
@Override
public List<Comment> findByArticleId(Long articleId) {
return repository.findByArticleId(articleId);
}
@Override
public void delete(Long id) {
}
@Override
public void add(Comment comment) {
repository.save(comment);
}
@Override
public void update(Comment comment) {
repository.save(comment);
}
}
3.4. 文章评论后端接口
@RestController
@RequestMapping("/comment")
@Api(tags = "评论接口类")
public class CommentController {
@Autowired
private ICommentService commentService;
/**
* 接口: 根据文章id查询当前文章的所有评论
* @comment articleId
* @return
*/
@GetMapping("/{articleId}")
public List<Comment> getComment(@PathVariable("articleId")Long articleId){
return commentService.findByArticleId(articleId);
}
/**
* 接口: 添加和修改
* @param comment
* @return
*/
@PutMapping
public JsonResult addOrUpdate(@RequestBody Comment comment){
try {
if(comment.getId() == null){//添加
commentService.add(comment);
}else{
commentService.update(comment);
}
return JsonResult.success();
} catch (Exception e) {
e.printStackTrace();
return JsonResult.error();
}
}
}
3.5. 文章评论前端操作
- html代码
<div style="background-color: #fafbfc;">
<div class="d-flex">
<img src="../../assets/images/p28.jpg" alt="" width="40px" class="rounded-circle m-2" />
<input class="form-control h-75 mt-3" v-model="content" placeholder="请输入评论..." />
</div>
<button type="button" class="btn btn-primary" @click="submitComment" style="width: 100px;margin-left: 55px;">发表评论</button>
</div>
<hr />
<div>
<div v-for="c in comments">
<div class="delBtn">
<i class="bi bi-trash3-fill"></i>
</div>
<div class="d-flex">
<img src="../../assets/images/p28.jpg" alt="" width="40px" class="rounded-circle m-2" />
<div class="mt-1">
<span class="text-dark fw-bolder h6">游客:
<span> {{c.nickname}}</span>
</span>
<br />
<span>{{c.createTime}}</span>
</div>
</div>
<span style="margin-left: 55px;">{{c.content}}</span>
<hr class="my-2" />
</div>
</div>
- js代码-模型数据
data() {
return {
articleId: null, //接收当前文章的id
//......
//当前文章所有评论
comments: [],
//评论内容
content: ''
}
},
- js代码-方法
getComment() {
this.$http.get("/comment/" + this.articleId).then(res => {
this.comments = res.data;
})
},
createUuid() {
var s = [];
var hexDigits = "0123456789abcdefghi";
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = "-";
var uuid = s.join("");
return uuid;
},
submitComment(){
let params = {
content: this.content,
articleId: this.articleId,
nickname: this.createUuid()
}
this.$http.put("/comment",params).then(res=>{
if(res.data.success){//成功
this.getComment();
this.content='';
}else{
this.$message.error("评论失败");
}
})
}
最终实现效果: