MongoDB入门到进阶



安装MongoDB

1. 下载:下载地址

2. 配置环境变量,注意到bin目录

3. 创建数据库所在目录,默认在C盘下的data文件夹中的db文件里

自定义数据库路径启动

1
mongod --dbpath  数据库路径

自定义端口号启动

1
mongod --port 端口号

自定义数据库路径 和 端口号 启动

1
mongod --dbpath 数据库路径 --port 端口号

4. cmd输入 mongod 启动服务

5. 通过 localhost:27017 访问

6. cmd输入 mongo 启动客户端

7. 开机自启动mongod服务

在自定义的数据库路径data中新建log文件夹,然后在根目录新建mongod.cfg配置文件(mongodb4后,说是自带服务了,但是我是免安装版的,还是自己配置。)

配置文件内容如下:

1
2
3
4
5
6
<!--配置内容-->
systemLog:
destination: file
path: mongod的目录\mongod.cfg
storage:
dbPath: mongod的目录\bin

8. 开机自启动命令:使用管理员权限进入 cmd 窗口

1
sc.exe create MongoDB binPath= "\"D:\mongod的bin目录\mongod.exe\" --service --config=\"D:\mongod的目录\mongod.cfg\"" DisplayName= "MongoDB" start= "auto"

9. 然后可在cmd中输入 services.msc 查看

10. 启动、关闭服务命令

1
2
3
4
// 关闭服务
service mongod stop
// 开启服务
service mongod start

MongoDB权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 数据库用户角色
Read:允许用户读取指定数据库
readWrite:允许用户读写指定数据库
// 数据库管理角色
dbOwner:在对应的数据库下具有最高权限
dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
// 集群管理角色
clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
// 所有数据库角色
readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
// 超级用户角色
root:只在admin数据库中可用。超级账号,超级权限

MongoDB创建管理员

  1. 创建管理员账户:创建 admin用户,可访问管理数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    db.createUser({ user: "admin", pwd: "admin", roles: [{ 
    role: "userAdminAnyDatabase",
    db: "admin" }
    ] })


    // 或者 db.getUsers()
    > show users
    {
    "_id" : "admin.admin",
    "userId" : UUID("7c16bb13-2c6d-49a1-83b0-6074f148d439"),
    "user" : "admin",
    "db" : "admin",
    "roles" : [
    {
    "role" : "userAdminAnyDatabase",
    "db" : "admin"
    }
    ],
    "mechanisms" : [
    "SCRAM-SHA-1",
    "SCRAM-SHA-256"
    ]
    }
  2. 创建超级管理员 root:只在admin数据库中可用,超级账号,超级权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
db.createUser({user: "root",pwd: "root", roles: [ { role: "root", db: "admin" } ]})
======================================
{
"_id" : "admin.root",
"userId" : UUID("469c1e49-6e7a-40ed-888e-8b4a2bc327b0"),
"user" : "root",
"db" : "admin",
"roles" : [
{
"role" : "root",
"db" : "admin"
}
],
"mechanisms" : [
"SCRAM-SHA-1",
"SCRAM-SHA-256"
]
}
  1. 创建用户自己的数据库的管理角色
1
2
3
4
5
6
7
use yourdatabase
// roles:dbOwner -》 对应此数据库下最高权限

db.createUser({user: "用户名",pwd: "用户密码",roles: [ {
role: "用户角色",
db: "建立在哪个数据库下的用户"
} ]})
  1. 验证用户添加是否成功
1
2
3
4
5
mongo admin -u 用户名 -p 密码

db.auth('root', 'root')
db.auth("admin", "admin")
// 返回值为 1,则添加成功。
  1. 创建完管理员权限用户后,我们就来设置 mongodb的安全性登录,不然就是 默认的无需密码就可以操作数据库。首先,我们需要 卸载原来安装的服务
1
sc delete MongoDB
  1. 以auth的方式启动服务,开启用户权限认证
1
sc create MongoDB binpath= "D:\mongodb\bin\mongod.exe --dbpath D:\mongodb\data\db --logpath D:\mongodb\data\log\MongoDB.log  --logappend --auth --service"
  1. 重启mongodb
1
net start mongodb
  1. 修改权限:也可以授权 root 用户具有操作访问 Java数据库的权限
1
2
3
4
db.grantRolesToUser("root", [ {
"role" : "dbOwner",
"db" : "Java"
}])

image-20201119160911393

  1. 删除权限
1
2
// 删除 root 用户 在数据库 admin 中 角色是 root 的权限
db.revokeRolesFromUser("root",[{role:"root", db:"admin"}])
  1. 删除 user 为 root 的用户
1
db.dropUser("root")
  1. 用户名不能跟数据库名重合

image-20201119163437784

注意:
以不同用户身份进行多次身份验证不会删除先前已身份验证的用户的凭据。这可能导致连接具有比用户预期更多的权限,并导致逻辑会话内的操作 引发错误
~
解决:最直白就是重新登录mongo,然后直接授权访问这个数据库进行操作

image-20201119163921359

MongoDB使用

1. 官方文档

1
2
3
4
5
<!-- 中文文档 -->
https://mongodb.net.cn/manual/crud/

<!-- 英文文档 -->
https://docs.mongodb.com/crud/

2. 基本的CRUD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//展示所有数据库
show dbs;

//使用数据库:switched to db test
use test;

//======= 插入 =====
//向test集合插入一行数据
db.inset.insertOne(
{ item: "canvas27", qty: 1220, tags: ["cot1ton"], size: { h: 28, w: 35.5, uom: "cm" } }
);
//向集合插入多个文档
db.inset.insertMany([{ item: "canvas1", qty: 12020, tags: ["cot1ton"], size: { h: 28, w: 35.5, uom: "cm" } },
{name: "job", age: 18}])
//向集合插入一个或者多个文档
db.inset.insert([{name: "job", age: 18}])

//返回当操作数据库的名称
db.getName()

//当前所在的数据库
db

查找

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
db.inventory.insertMany([
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A" },
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" }
]);

// 查找集合总记录数
db.inventory.count()

// 查集合的所有,输入.find 查函数:function (query, projection) { return this.forwardToCustomFunction("collectionFind", query, projection);}
db.inventory.find( {} )

// 查找status等于D的,类似:SELECT * FROM inventory
db.inventory.find( { status: "D" } )

// 查找等于 A 或者 D 的
db.inventory.find( { status: { $in: [ "A", "D" ] } } )

// 查找status等于A 且 qty 小于30的:$lt:
// SELECT * FROM inventory WHERE status = "A" AND qty < 30
db.inventory.find( { status: "A", qty: { $lt: 30 } } )

// 查找等于A 且 要不 qty 小于30 就 item包含p:SELECT * FROM inventory WHERE status = "A" AND ( qty < 30 OR item LIKE "p%")
db.inventory.find( {
status: "A",
$or: [ { qty: { $lt: 30 } }, { item: /^p/ } ]
} )

修改

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
db.inventory.insertMany( [
{ item: "canvas", qty: 100, size: { h: 28, w: 35.5, uom: "cm" }, status: "A" },
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
{ item: "mat", qty: 85, size: { h: 27.9, w: 35.5, uom: "cm" }, status: "A" },
{ item: "mousepad", qty: 25, size: { h: 19, w: 22.85, uom: "cm" }, status: "P" },
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "P" },
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D" },
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D" },
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A" },
{ item: "sketchbook", qty: 80, size: { h: 14, w: 21, uom: "cm" }, status: "A" },
{ item: "sketch pad", qty: 95, size: { h: 22.85, w: 30.5, uom: "cm" }, status: "A" }
] );

// 符合条件 item为"paper"的 第一个,将 status为 "P"的值 set 成 "size.uom",且判断lastModified字段有否,没有就创建字段,值为当前时间。
db.inventory.updateOne(
{ item: "paper" },
{
$set: { "coderblue.cn": "cm", status: "P" },
$currentDate: { lastModified: true }
}
)

// 修改所有符合条件的 qty小于50
db.inventory.updateMany(
{ "qty": { $gt: 50 } },
{
$set: { "coderblue.cn": "in", status: "M" },
$currentDate: { lastModified: true }
}
)

// 符合条件 item为"paper"的第一个,将会被后面的给替代,有赋值的字段会被修改值,没有的字段会被新建赋值,没赋值的字段值将会被清空
db.inventory.replaceOne(
{ item: "journal" },
{ item: "papers", qty: 80, instock: [ { warehouse: "A", qty: 60 }, { warehouse: "B", qty: 40 } ] }
)

删除

1
2
3
4
5
6
7
8
// 清空集合
db.inventory.deleteMany({})

// 批量删除集合下符合条件的
db.inventory.deleteMany({ status : "A" })

// The following example deletes the first document where status is "D"
db.inventory.deleteOne( { status: "D" } )

MongoDB GridFS

1.基本介绍

  • GridFS 用于存储和恢复那些超过 16M(BSON文件限制)的文件(如:图片、音频、视频等)。

  • GridFS 也是文件存储的一种方式,但是它是存储在MonoDB的集合中。

  • GridFS 可以更好的存储大于16M的文件。

  • GridFS 会将 大文件对象分割成多个小的chunk(文件片段),一般为256k/个,每个chunk将作为MongoDB的一个文档(document)被存储在chunks集合中。

2.存储原理

  • GridFS 用 两个集合来存储一个文件fs.filesfs.chunks

  • 每个文件的实际内容被存在chunks(二进制数据)中,和文件有关的meta数据(filenamecontent_type,还有用户自定义的属性)将会被存在files集合中。

以下是简单的 fs.files 集合文档:

1
2
3
4
5
6
7
{
"filename": "test.txt",
"chunkSize": NumberInt(261120),
"uploadDate": ISODate("2014-04-13T11:32:33.557Z"),
"md5": "7b762939321e146569b07f72c62cca4f",
"length": NumberInt(646)
}

以下是简单的 fs.chunks 集合文档:

1
2
3
4
5
{
"files_id": ObjectId("534a75d19f54bfec8a2fe44b"),
"n": NumberInt(0),
"data": "Mongo Binary Data"
}

Spring Boot中使用MongoDB上传文件

以下使用了Spring的cache缓存+swagger3测试MongoDB文件上传。基本的工程搭建我就不一一阐述了,直抒要点。

1. CURD

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
85
86
87
88
89
90
91
92
/**
* @author coderblue
*/
@RestController
@RequestMapping("/mongo")
@Slf4j
public class MongodbController {

@Autowired
private MongoTemplate mongoTemplate;

@GetMapping
@Cacheable(value = "UserInfo", key = "'selectUserInfo'")
@ApiOperation(value = "查询", notes = "mongo查询")
public Result find() {
List<UserInfo> all = mongoTemplate.findAll(UserInfo.class);
log.info("用户信息:" + all);
return Result.success().data("findAll", all);
}

@GetMapping("/findCondition/{page}/{size}")
@ApiOperation(value = "分页查询", notes = "mongo分页查询")
public Result findCondition(UserInfo userInfo, @PathVariable Integer page, @PathVariable Integer size) {
// 条件:字段区分大小写
Criteria criteria = Criteria.where("gender").is(userInfo.getGender());
Query query = new Query();
query.addCriteria(criteria);
// 数量
long total = mongoTemplate.count(query, UserInfo.class);
// 分页
query.skip((page - 1) * size).limit(size);
List<UserInfo> condition = mongoTemplate.find(query, UserInfo.class);
log.info("用户信息-分页:" + condition);
return Result.success().data("findCondition", condition).data("total", total);
}

@PostMapping
@ApiOperation(value = "保存", notes = "mongo保存")
public Result save(UserInfo userInfo) {
Map<String, Object> map = new HashMap<>();
map.put("age", 18);
map.put("gender", "M");
map.put("user", userInfo);
// userInfo.setId(System.currentTimeMillis()); 不设置id,默认会生成
mongoTemplate.save(userInfo);
return Result.success().data(map);
}

/**
* 根据用户名查询对象
*
* @return
*/
@ApiOperation("根据用户名查询对象")
@GetMapping("/findByName")
public UserInfo findByName(String name) {
Query query = new Query(Criteria.where("name").is(name));
return mongoTemplate.findOne(query, UserInfo.class);
}

/**
* 更新对象:将修改后的返回值作为value
* CachePut:先修改,然后更新缓存数据
*/
@PutMapping("/update")
@ApiOperation("更新对象")
@CachePut(value = "UserInfo", key = "'selectUserInfo'")
public Result update(UserInfo userInfo) {
Query query = new Query(Criteria.where("id").is(userInfo.getId()));
Update update = new Update().set("age", userInfo.getAge()).set("name", userInfo.getName());
//更新查询返回结果集的第一条
UpdateResult result = mongoTemplate.updateFirst(query, update, UserInfo.class);
// 将修改后的返回值作为value
UserInfo info = this.findByName(userInfo.getName());
return Result.success().data("data", info);
// 更新查询返回结果集的所有
// mongoTemplate.updateMulti(query,update,UserInfo.class);
}

/**
* 根据id删除对象
*
* @param id
*/
@ApiOperation("根据id删除对象")
@DeleteMapping("deleteById")
public void deleteById(Integer id) {
Query query = new Query(Criteria.where("id").is(id));
mongoTemplate.remove(query, UserInfo.class);
}

}

存储如图所示,对应Java实体类中的 UserInfo

  • 如果 UserInfo 类上 没有注明 @Document(collection = “UserInfo”),那么存储的集合名就是 userInfo

  • 如果 UserInfo 类上 注明 @Document(collection = “UserInfo”),那么存储的集合名就是 UserInfo

2.通用的文件上传功能

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/**
* 文件上传
* @author coderblue
*/
@RequestMapping("/upload")
@RestController
@Slf4j
public class UploadController {

/**
* 默认20MB
*/
@Value("${project.upload.sizeLimit}")
private Long maxPostSize;
/**
* 支持的文件类型
*/
private List<String> fileTypes;
/**
* 获得SpringBoot提供的mongodb的GridFS对象
*/
@Autowired
private GridFsTemplate gridFsTemplate;
/**
* 版本太高就会提示方法是被弃用的了
*/
@Autowired
private MongoDbFactory mongoDbFactory;

/**
* 单文件上传统一方法
*
* @param request
* @param response
* @return
*/
@ApiOperation("单文件上传统一方法")
@PostMapping("/image")
public Result upload(HttpServletRequest request, HttpServletResponse response) throws Exception {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
// 获取上传文件对象
MultipartFile file = multipartRequest.getFile("file");
//初始化mongoFile对象
MongoFile mongoFile = new MongoFile(file.getOriginalFilename(), file.getContentType(), file.getSize());
this.uploadCommon(file, mongoFile);
return Result.success().data("uploadFile", mongoFile);
}

/**
* 批量文件上传
* @param request
* @param response
* @return
* @throws Exception
*/
@ApiOperation("批量文件上传")
@PostMapping(value = "/batch")
@ResponseBody
protected Result uploadBatch(HttpServletRequest request, HttpServletResponse response) {
List<MongoFile> mongoFiles = new ArrayList<>();

if (request instanceof MultipartHttpServletRequest) {
MultipartHttpServletRequest mr = (MultipartHttpServletRequest) request;
List<MultipartFile> multipartFile = mr.getFiles("file");

if (null != multipartFile && !multipartFile.isEmpty()) {
for (MultipartFile file : multipartFile) {
// 文件类型和大小检验
String sourceFileSuffix = file.getContentType();// 源文件类型
if (file.getSize() > maxPostSize) {
return Result.error().data("message", "上传文件超出" + formatWithUnit(maxPostSize) + "限制");
} else if (null == sourceFileSuffix || !fileTypes.contains(sourceFileSuffix)) {
return Result.error().data("message", "不允许上传的文件类型!");
}
MongoFile mongoFile = null;
try {
mongoFile = new MongoFile(file.getOriginalFilename(), file.getContentType(), file.getSize());
this.uploadCommon(file, mongoFile);
} catch (Exception e) {
mongoFile.setOperateMsg("文件上传失败!");
e.printStackTrace();
} finally {
//无论文件单文件上传失败成功与否皆保存文件对象至集合
mongoFiles.add(mongoFile);
}
}
}
}
return Result.success().data("uploadFile", mongoFiles);
}

@GetMapping(value = "/download/{_id}")
@ApiOperation("下载文件")
public void download(@PathVariable("_id") String _id, HttpServletRequest request, HttpServletResponse response) throws Exception {
String fileId = _id;
//通过文件对象id查询 GridFS类型文件
GridFSFile image = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
//获取文件资源
GridFsResource gridFsResource = new GridFsResource(image, GridFSBuckets.create(mongoDbFactory.getDb()).openDownloadStream(image.getObjectId()));

//获取文件名字
String fileName = image.getFilename();
//处理中文文件名乱码
if (request.getHeader("User-Agent").toUpperCase().contains("MSIE") ||
request.getHeader("User-Agent").toUpperCase().contains("TRIDENT")
|| request.getHeader("User-Agent").toUpperCase().contains("EDGE")) {
fileName = java.net.URLEncoder.encode(fileName, "UTF-8");
} else {
//非IE浏览器的处理:
fileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
}
//设置浏览器传输格式
response.setContentType("multipart/form-data");
//设置返回文件名
response.setHeader("Content-Disposition", "attachment;filename=\"" + fileName + "\"");
log.info("gridFsResource:" + gridFsResource);
// 获取文件输入流至浏览器输出流
IOUtils.copy(gridFsResource.getInputStream(), response.getOutputStream());
}

/**
* 文件上传的通用抽离方法
* @param file
* @param mongoFile
* @throws Exception
*/
private void uploadCommon(MultipartFile file, MongoFile mongoFile) throws Exception {
// 获得文件输入流
InputStream is = file.getInputStream();
//获取文件流的md5值
String md5 = MD5.getMd5(is);
//通过文件MD5值去mongo数据库查询文件
GridFSFile fileByMd5 = gridFsTemplate.findOne(query(Criteria.where("md5").is(md5)));
//如果文件存在返回文件 对象id(_id)
if (fileByMd5 != null) {
mongoFile.set_id(fileByMd5.getObjectId().toString());
} else {
//文件不存在,保存文件,返回文件 对象id(_id)
ObjectId store = gridFsTemplate.store(file.getInputStream(), file.getOriginalFilename(), file.getContentType());
mongoFile.set_id(store.toString());
}
mongoFile.setOperateStatus(true);
}

/**
* 数据转为带单位字符串
* Byte -> KB -> MB -> GB
*
* @returns {string}
*/
public static String formatWithUnit(Long value) {
if (value / 1024 < 1024) {
return value * 100 / 1024 / 100 + "KB";
} else if (value / Math.pow(1024, 2) < 1024) {
return value * 100 / Math.pow(1024, 2) / 100 + "MB";
} else {
return value * 100 / Math.pow(1024, 3) / 100 + "GB";
}
}


@Value("${project.upload.fileType}")
public void setFileTypes(String fileTypes) {
this.fileTypes = new ArrayList<>();
if (!StringUtils.isEmpty(fileTypes)) {
String[] types = fileTypes.split(",");
for (String type : types) {
if (!StringUtils.isEmpty(type)) {
this.fileTypes.add(type);
}
}
}
}
}

上传的文件将会存储在MongoDB的 GridFS 存储桶里。

此项目Demo完整的代码见此Github链接:

打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  1. © 2020-2021 coderblue    湘ICP备20003709号

请我喝杯咖啡吧~

支付宝
微信