1.认识mongdb
mongodb是一个开源,文档类型的非关系型数据库,是最像关系型数据库的非关系型数据库。
mongodb存储的是由字段和值组成的数据类型称为BSON,类似于JSON对象。包含字段的值,数组,文档数组。
与mysql的对比:
mysql | mongodb | 解释/说明 |
---|---|---|
column | field | 数据字段/域 |
database | database | 数据库 |
index | index | 索引 |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
row | document | 数据记录行/文档 |
table | collection | 数据库表/集合 |
table joins | 无 | 表连接/mongo只能嵌入文档 |
主要特性
- 高性能
- 对嵌入式数据模型的支持减少了数据库系统上的I / O操作
- 索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键。
- 高可用 -副本集
- 自动_故障转移
- 数据冗余
- 水平拓展
- 分片将数据分布在一个集群的集群上
- 3.4以后支持基于分片键创建数据区域
- 丰富的查询支持
- 支持CRUD 聚合,空间地理查询
应用场景
- 数据量较大;
- 读写频繁;
- 价值较低的数据,对事物要求不高。
集群模式-副本集
为了保证数据的冗余和可靠性和提高整个系统的负载 所以需要搭建mongo集群,mongo不是mysql那种主备模式,mongo是有自动故障恢复功能的主从集群
主从集群和副本集最大的区别就是副本集没有固定的“主节点”;整个集群会选出一个“主节点”,当其挂掉后,又在剩下的从节点中选中其他节点为“主节点”,副本集总有一个活跃点(主、primary)和一个或多个备份节点(从、econdary)。
搭建mongo副本集 至少需要三个节点 有两种模式可选择
- 主节点(Primary)类型:数据操作的主要连接点,可读写。
- 次要(辅助、从)节点(Secondaries)类型:数据冗余备份节点,可以读或选举。
主节点类型:当主节点挂点后,两个从节点会竞争产生一个主节点 ,再当其中一个节点挂掉号 整个集群不可用 ,当挂掉的主节点启动后会变成从节点
从节点类型:带有仲裁节点,仲裁节点不存储数据,可以节省成本 当主节点挂掉后 仲裁节点可以投票 从节点变成主节点
2.docker 安装 mongdb 单机与副本集
创建单机只需要执行一次,也不需要指定副本集名称
此次没有挂载日志和存储目录 采用一主一从一仲裁
docker run --name mongo3 -p 57017:27017 -id mongo --replSet "rs"
docker run --name mongo2 -p 47017:27017 -id mongo --replSet "rs"
docke run --name mongo1 -p 37017:27017 -id mongo --replSet "rs"
随便进入一个容器执行,如果是阿里云服务器,需指定外网ip
arbiterOnly:true 指定为仲裁节点
rs.initiate({"_id":"rs",members:[{_id:0,host:"106.14.72.13:37017"},{_id:1,host:"106.14.72.13:47017"},{_id:2,host:"106.14.72.13:57017",arbiterOnly:true}]})
3.可视化工具与基本操作命令
nosqlbooster 可视化
mongdb-compass-可视化
文档的操作
/********************数据库 操作*********/
// 选择或创建数据库 use 数据库名称
use articledb
//查看有权限查看的所有的数据库命令
show dbs
show databases
//查看当前正在使用的数据库
db
//删除数据库
db.dropDatabase()
/********************集合/表 操作*********/
//显示创建集合 /表 db.createCollection(name)
db.createCollection("mycollection")
//当前库中的表
show collections
show tables
//删除集合 db.collection.drop()
db.comment.drop()
//插入文档(单条) 不存在则会隐式创建
db.comment.insert({ "articleid": "100000", "content": "今天天气真好,阳光明媚", "userid": "1001", "nickname": "Rose", "createdatetime": new Date(), "likenum": NumberInt(10), "state": null })
//批量插入
db.comment.insertMany([{ "_id": "1", "articleid": NumberInt(100001), "content": "我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。", "userid": NumberInt(1002), "nickname": "相忘于江湖", "createdatetime": new Date("2019-08-05T22:08:15.522Z"), "likenum": NumberInt(1000), "state": NumberInt(1) }, { "_id": "2", "articleid": NumberInt(100001), "content": "我夏天空腹喝凉开水,冬天喝温开水", "userid": NumberInt(1005), "nickname": "伊人憔悴", "createdatetime": new Date("2019-08-05T23:58:51.485Z"), "likenum": NumberInt(888), "state": NumberInt(1) }, { "_id": "3", "articleid": NumberInt(100001), "content": "我一直喝凉开水,冬天夏天都喝。", "userid": NumberInt(1004), "nickname": "杰克船长", "createdatetime": new Date("2019-08-06T01:05:06.321Z"), "likenum": NumberInt(666), "state": NumberInt(1) }, { "_id": "4", "articleid": NumberInt(100001), "content": "专家说不能空腹吃饭,影响健康。", "userid": NumberInt(1003), "nickname": "凯撒", "createdatetime": new Date("2019-08-06T08:18:35.288Z"), "likenum": NumberInt(2000), "state": NumberInt(1) }, { "_id": "5", "articleid": NumberInt(100001), "content": "研究表明,刚烧开的水千万不能喝,因为烫嘴。", "userid": NumberInt(1003), "nickname": "凯撒", "createdatetime": new Date("2019-08-06T11:01:02.521Z"), "likenum": NumberInt(3000), "state": NumberInt(1) }]);
//批量插入 某行报错不会回滚可以加try catch
try {
db.comment.insertMany([{ "_id": "1", "articleid": NumberInt(100001), "content": "我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。", "userid": NumberInt(1002), "nickname": "相忘于江湖", "createdatetime": new Date("2019-08-05T22:08:15.522Z"), "likenum": NumberInt(1000), "state": NumberInt(1) }, { "_id": "2", "articleid": NumberInt(100001), "content": "我夏天空腹喝凉开水,冬天喝温开水", "userid": NumberInt(1005), "nickname": "伊人憔悴", "createdatetime": new Date("2019-08-05T23:58:51.485Z"), "likenum": NumberInt(888), "state": NumberInt(1) }, { "_id": "3", "articleid": NumberInt(100001), "content": "我一直喝凉开水,冬天夏天都喝。", "userid": NumberInt(1004), "nickname": "杰克船长", "createdatetime": new Date("2019-08-06T01:05:06.321Z"), "likenum": NumberInt(666), "state": NumberInt(1) }, { "_id": "4", "articleid": NumberInt(100001), "content": "专家说不能空腹吃饭,影响健康。", "userid": NumberInt(1003), "nickname": "凯撒", "createdatetime": new Date("2019-08-06T08:18:35.288Z"), "likenum": NumberInt(2000), "state": NumberInt(1) }, { "_id": "5", "articleid": NumberInt(100001), "content": "研究表明,刚烧开的水千万不能喝,因为烫嘴。", "userid": NumberInt(1003), "nickname": "凯撒", "createdatetime": new Date("2019-08-06T11:01:02.521Z"), "likenum": NumberInt(3000), "state": NumberInt(1) }]);
} catch (e) {
print(e);
}
/********************文档 操作*********/
//查询所有
db.comment.find()
db.comment.find({})
//条件查询
db.comment.find({userid:'1003'})
//查询单条
db.comment.findOne({userid:'1003'})
//投影查询
db.comment.find({userid:NumberInt(1003)},{userid:1,nickname:1})
//覆盖修改 其他字段全不见
db.comment.update({_id:"1"},{likenum:NumberInt(1001)})
//局部修改
db.comment.update({_id:"2"},{$set:{likenum:NumberInt(889)}})
//批量更新 增加标志 multi
db.comment.update({userid:"1003"},{$set:{nickname:"凯撒大帝"}},{multi:true})
//自增长
db.comment.update({_id:"3"},{$inc:{likenum:NumberInt(1)}})
//根据条件删除 没有条件删除全部
db.comment.remove({_id:"1"})
//统计记录数 没有参数统计全部
db.comment.count({userid:"1003"})
//分页操作 ->截取前三位
db.comment.find().limit(3)
//分页操作 跳过多少行,查询多少行 类似sql limit 4,6
db.comment.find().skip(3).limit(2)
//排序操作 -1 倒序 1升序
db.comment.find().sort({userid:-1,likenum:1})
//复杂查询 ->正则表达式匹配
db.comment.find({content:/^专家/})
//比较查询
db.comment.find({ "field" : { $gt: value }}) // 大于: field > value
db.comment.find({ "field" : { $lt: value }}) // 小于: field < value
db.comment.find({ "field" : { $gte: value }}) // 大于等于: field >= value
db.comment.find({ "field" : { $lte: value }}) // 小于等于: field <= value
db.comment.find({ "field" : { $ne: value }}) // 不等于: field != value
//包含查询
db.comment.find({userid:{$in:["1003","1004"]}})
//不包含查询
db.comment.find({userid:{$nin:["1003","1004"]}})
//连接查询 and
db.comment.find({$and:[{likenum:{$gte:NumberInt(700)}},{likenum:{$lt:NumberInt(2000)}}]})
//连接查询 or
db.comment.find({$or:[ {userid:"1003"} ,{likenum:{$lt:1000} }]})
4.索引
官网链接
索引支持在MongoDB中高效地执行查询。如果没有索引,MongoDB必须执行全集合扫描,这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。如果查询存在适当的索引,MongoDB可以使用该索引限制必须检查的文档数。
mongodb的数据结构是B-tree
4.1 单字段索引
在文档上的单个字段创建自定义的升序/降序索引,称为单字段索引
4.2 复合索引
复合索引中列出的字段顺序具有重要意义。例如,如果复合索引由{ userid: 1, score: -1 } 组成,则索引首先按userid正序排序,然后在每个userid的值内,再在按score倒序排序。
3.3 其他索引
地理空间索引(Geospatial Index)、文本索引(Text Indexes)、哈希索引(Hashed Indexes)。
3.4 关于索引的语法
//查看索引
db.collection.getIndexes()
//创建索引
db.collection.createIndex(keys, options)
//移除索引
db.collection.dropIndex(index)
5.分片集群
分片是一种将数据分配到多个机器上的方法。MongoDB通过分片技术来支持具有海量数据集和高吞吐量操作的部署方案
数据库系统的数据集或应用的吞吐量比较大的情况下,会给单台服务器的处理能力带来极大的挑战。例如,高查询率会耗尽服务器的CPU资源。工作的数据集大于系统的内存压力、磁盘驱动器的I/O容量。
分片集群包含组件
- 分片:每个shard(分片)包含被分片的数据集中的一个子集。每个分片可以被部署为副本集架构。
- mongs:mongos充当查询路由器,在客户端应用程序和分片集群之间提供接口。
- config服务器:mongos充当查询路由器,在客户端应用程序和分片集群之间提供接口。
分片的优势
- 读写负载
- 存储容量
- 高可用性
6.安全认证
1.启用访问控制
在实例启动时添加选项—auth 或指定启动配置文件中添加选项auth=true 容器启动配置-auth为true
2.角色
在MongoDB中通过角色对用户授予相应数据库资源的操作权限,每个角色当中的权限可以显式指定,也可以通过继承其他角色的权限,或者两都都存在的权限。
3.权限
权限由指定的数据库资源(resource)以及允许在指定资源上进行的操作(action)组成。
- 资源(resource)包括:数据库、集合、部分集合和集群;
- 操作(action)包括:对资源进行的增、删、改、查(CRUD)操作。
在角色定义时可以包含一个或多个已存在的角色,新创建的角色会继承包含的角色所有的权限。在同一个数据库中,新创建角色可以继承其他角色的权限,在admin 数据库中创建的角色可以继承在其它任意数据库中角色的权限。
// 查询所有角色权限(仅用户自定义角色)
db.runCommand({ rolesInfo: 1 })
// 查询所有角色权限(包含内置角色)
db.runCommand({ rolesInfo: 1, showBuiltinRoles: true })
// 查询当前数据库中的某角色的权限
db.runCommand({ rolesInfo: "<rolename>" })
// 查询其它数据库中指定的角色权限
db.runCommand({ rolesInfo: { role: "<rolename>", db: "<database>" } }
角色 | 权限描述 |
---|---|
read | 可以读取指定数据库中任何数据。 |
readWrite | 可以读写指定数据库中任何数据,包括创建、重命名、删除集合。 |
readAnyDatabase | 可以读取所有数据库中任何数据(除了数据库config和local之外)。 |
readWriteAnyDatabase | 可以读写所有数据库中任何数据(除了数据库config和local之外)。 |
userAdminAnyDatabase | 可以在指定数据库创建和修改用户(除了数据库config和local之外)。 |
dbAdminAnyDatabase | 可以读取任何数据库以及对数据库进行清理、修改、压缩、获取统计信息、执行检查等操作(除了数据库config和local之外)。 |
dbAdmin | 可以读取指定数据库以及对数据库进行清理、修改、压缩、获取统计信息、执行检查等操作。 |
userAdmin | 可以在指定数据库创建和修改用户。 |
clusterAdmin | 可以对整个集群或数据库系统进行管理操作。 |
backup | 备份MongoDB数据最小的权限。 |
restore | 从备份文件中还原恢复MongoDB数据(除了system.profile集合)的权限。 |
root | 超级账号,超级权限 |
7.springboot连接mongdb
加入jia包支持 自动跟随springboot版本
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
配置mongodb连接 权限认证如果不是在本库创建的用户 则需要在admin库进行权限认证。可以在/直接跟admin
然后在database 指定要连接的库
spring:
data:
mongodb:
#test 指需要连接的库 authSource=admin指权限在admin认证
uri: mongodb://rem:123456@106.14.72.13:37017,106.14.72.13:47017/test?replicaSet=rs&readPreference=secondaryPreferred&connectTimeoutMS=15000&slaveOk=true&write=1&authSource=admin
uuid-representation: standard
auto-index-creation: true
#database:
使用springboot连接mongodb 和使用其他springdata操作一样 继承MongoRepository
package com.rem.mongodb.dao;
import com.rem.mongodb.entity.Comment;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
/**
* 集成springDate 基本操作
* 评论的持久层接口
*
* @author Rem
* @date 2021/11/9
*/
@Repository
public interface CommentRepository extends MongoRepository<Comment, String> {
}
简单的CRUD以及分页操作,也可以直接使用mongoTemplate操作对象和dao层操作数据一样,如果不需要查询出对象再进行操作 比如加一操作,直接使用template操作更加简单。
springboot集成的事务对mongodb依然有效在方法上加上事务注解,在启动类加上开启事务或者单独配置事务类,当报错时不会对mongodb中数据进行操作。
package com.rem.mongodb.service;
import com.rem.mongodb.dao.CommentRepository;
import com.rem.mongodb.entity.Comment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 文章 服务层
*
* @author Rem
* @date 2021/11/9
*/
@Service
public class CommentService {
@Autowired
private CommentRepository commentRepository;
@Autowired
private MongoTemplate mongoTemplate;
/**
* 保存一个评论
*
* @param comment
*/
@Transactional
public void saveComment(Comment comment) {
//如果需要自定义主键,可以在这里指定主键;如果不指定主键,MongoDB会自动生成主键 设置一些默认初始值
commentRepository.save(comment);
}
/**
* 更新评论
*
* @param comment
*/
public void updateComment(Comment comment) {
commentRepository.save(comment);
}
/**
* 根据id删除评论
*
* @param id
*/
public void deleteCommentById(String id) {
commentRepository.deleteById(id);
}
/**
* 查询所有评论
* ,
*
* @return
*/
public List<Comment> findCommentList() {
return commentRepository.findAll();
}
/**
* 根据id查询评论
*
* @param id
* @return
*/
public Comment findCommentById(String id) {
return commentRepository.findById(id).get();
}
/**
* 根据多个id 批量查询评论
*
* @return
*/
public List<Comment> findCommentList(List<String> ids) {
return (List<Comment>) commentRepository.findAllById(ids);
}
/**
* 根据条件查询
*
* @param comment
* @return
*/
public List<Comment> findComment(Comment comment) {
return commentRepository.findAll(Example.of(comment));
}
/**
* 模糊查询
* StringMatcher 枚举中可选择各种匹配模式
* ExampleMatcher.StringMatcher.DEFAULT 默认
* ExampleMatcher.StringMatcher.EXACT 精确匹配
* ExampleMatcher.StringMatcher.STARTING 前缀匹配
* ExampleMatcher.StringMatcher.ENDING 后缀匹配
* ExampleMatcher.StringMatcher.CONTAINING 模糊查询
* ExampleMatcher.StringMatcher.REGEX 正则表达式
* matching 匹配
*
* @param comment
* @return
*/
public List<Comment> findCommentDim(Comment comment) {
ExampleMatcher matcher = ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING).withIgnoreCase(true);
return commentRepository.findAll(Example.of(comment, matcher));
}
/**
* 分页查询 并且按照 likenum 点赞数倒序
*
* @param comment
* @return
*/
public Page<Comment> findCommentByPage(Comment comment, Integer page, Integer size) {
Sort sort = Sort.by(Sort.Direction.DESC, "likenum");
//分页 0代表第一页
Pageable pageable = PageRequest.of(page, size, sort);
return commentRepository.findAll(Example.of(comment), pageable);
}
/**
* 使用mongoTemplate 简单操作
* 点赞数+1
*
* @param id
*/
public void updateCommentLikenum(String id) {
//查询对象
Query query = Query.query(Criteria.where("_id").is(id));
//更新对象
Update update = new Update();
//局部更新,相当于$set
// update.set(key,value)
//递增$inc
// update.inc("likenum",1);
update.inc("likenum");
//参数1:查询对象
//参数2:更新对象
//参数3:集合的名字或实体类的类型Comment.class
mongoTemplate.updateFirst(query, update, "comment");
}
}