MongoDB 如今是最流行的 NoSQL 数据库之一,被广泛应用于各行各业中,很多创业公司数据库选型就直接使用了 MongoDB。MongoDB一经推出就受到了广大社区的热爱,可以说是对程序员最友好的一种数据库之一,下面主要是笔者在平常的学习中对这个数据库学习的一点小总结,包括基础知识以及它同Mysql一样作为数据库在SpringBoot项目中的技术整合流程梳理。
MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。它可以应用于各种规模的企业、各个行业以及各类应用程序的开源的非关系型数据库。
MongoDB的数据结构非常灵活,它可以随着应用程序的发展而灵活地更新。与此同时,它也为开发人员提供了许多传统数据库的功能:二级索引、完整的查询系统及数据一致性等。可以说是最像关系型数据库的非关系型数据库。MongoDB能够使企业更加具有灵活性和可扩展性,无论是创业公司、互联网企业或者是传统企业都可以通过MongoDB 来创建新的应用。
MongoDB具备高可扩展性、高性能和高可用性等非关系型数据库的特性,可以从单服务器部署扩展到大型、复杂的多数据中心架构。利用内存计算的优势, MongoDB 能够提供高性能的数据读写操作。 MongoDB的本地复制和自动故障转移功能使应用程序具有企业级的可靠性和操作灵活性。
MongoDB 是一个面向文档存储的数据库,操作起来比较简单和容易。相比其它的数据库,MongoDB具有如下特点:
1、易扩展性,MongoDB使用分片技术对数据进行扩展,MongoDB能自动分片、自动转移分片里面的数据块,去掉了关系型数据库的关系型特性,数据之间没有关系。让每一个服务器里面存储的数据都是一样大小。这样就非常容易扩展。
2、高性能,Mongo非常适合实时的插入,保留了关系型数据库即时查询的能力,并具备网站实时数据存储所需的复制及高度伸缩性。
3、高伸缩性,Mongo非常适合由数十或数百台服务器组成的数据库,Mongo的路线图中已经包含对MapReduce引擎的内置支持。
4、存储动态性,相较于传统的数据库当要增加一个属性值的时,对表的改动比较大,mongodb的面向文档的形式可以使其属性值轻意的增加和删除。而原来的关系型数据库要实现这个需要有很多的属性表来支持。
5、速度与持久性,MongoDB通过驱动调用写入时,可以立即得到返回得到成功的结果(即使是报错),这样让写入的速度更加快,当然会有一定的不安全性,完全依赖网络。
MongoDB与传统SQL的按结构对比
SQL 术语 | MongoDB 术语 | 说明 |
---|---|---|
DataBase | DataBase | 数据库 |
Table | Collection | 数据库表/集合 |
Row | Document | 数据记录行/文档 |
Column | Field | 数据字段/域 |
index | index | 索引 |
Table joins | MongoDB 不支持 | |
primary key | primary key | 主键,MongoDB自动将 _id字段设置为主键 |
如上表所示:MongoDB 和关系数据库一样有库的概念,一个MongoDB 可以有多个数据库, MongoDB 中的集合就相当于我们关系数据库中的表,文档就相当于关系数据库中的数据行,域就相当于关系数据库中的列, MongoDB也支持各种索引有唯一主键,但不支持表连接查询。
MonggoDB支持以下Windows,Linux等平台。同时也提供了C、C++、C# / .NET、Erlang、Java、Ruby、Go等语言的驱动客户端。
直接找到MongDB官网进行下载即可,可以选择自己对应的平台的压缩包zip或者msi文件进行下载即可。
Windows这里不详述,用手下就行了。这里仅以Centos 7系统为例,演示MongoDB的安装。
另外需要注意的是:在安装前需要安装libcurl、openssl等依赖包。命令如下:sudo yum install libcurl openssl
。
Step1:下载
这里使用wget在线下载安装,也可以通过上面的下载地址,手动下载安装包。
# 1.下载
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-6.0.5.tgz
# 2.解压
tar -zxvf mongodb-linux-x86_64-rhel70-6.0.5.tgz
# 3.将解压包拷贝到指定目录
mv mongodb-linux-x86_64-rhel70-6.0.5.tgz /usr/local/mongodb
Step2:创建数据库目录
默认情况下 MongoDB 启动后会初始化以下两个目录:
数据存储目录:/var/lib/mongodb
日志文件目录:/var/log/mongodb
所以,在启动前先创建这两个目录,命令如下:
sudo mkdir -p /var/lib/mongo
sudo mkdir -p /var/log/mongodbsudo
chown 777 /var/lib/mongo # 设置权限
sudo chown 777 /var/log/mongodb # 设置权限
Step3:创建配置文件
MongoDB的bin目录下创建一个mongodb.conf 文件,增加如下配置:
#touch mongodb.conf
port=27017 #端口
bind_ip=0.0.0.0 #默认是127.0.0.1dbpath=/var/lib/mongo/ #数据库存放logpath=/var/log/mongodb/mongod.log #日志文件
fork=true #设置后台运行#auth=true #开启认证
MongoDB默认没有配置文件,需要我们手动创建配置文件。建议使用自定义配置文件,而不是默认配置。bind_ip 设置为0.0.0.0,否则Mongo服务只能本地连接,远程服务器会连接不上。
Step4:启动MongDB服务
进入MongoDB安装目录下的bin目录通过命令启动:
mongod --config mongodb.conf
如果没有配置这个mongodb.conf文件也可以通过mongo本地启动。
打开 /var/log/mongodb/mongod.log 文件看到以下信息,说明启动成功。
# tail -10f /var/log/mongodb/mongod.log
2023-04-14T08:31:09.925+0800 I NETWORK [listener] Listening on /tmp/mongodb-27017.sock
2023-04-14T08:31:09.925+0800 I NETWORK [listener] Listening on 127.0.0.1
2023-04-14T08:31:09.925+0800 I NETWORK [listener] waiting for connections on port 27017
如果要停止 mongodb 可以使用以下命令:
mongod --config mongodb.conf --shutdown
MongoDB Shell 是 MongoDB 自带的交互式 Javascript shell,用来对 MongoDB 进行操作和管理的交互式环境。因此只要我们服务开启了,直接通过mongo就能进入数据库客户端操作了。
[root@VM-12-4-centos ~]# mongo
MongoDB shell version v6.0.5
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("dd2d1fd6-2fcf-4f35-a871-b75697e93434") }
MongoDB server version: 6.0.5
>
MongoDB可以说是最像关系数据库的非关系数据库。一些命令和Mysql 比较类似。比如show databases查看数据库,use database 切换数据库等。
# 查询数据库
show databases
# 切换数据库,
use test
# 查询当前数据库下面的集合
show collections
# 创建集合
db.createCollection("集合名称")
# 删除集合
db.集合名称.drop()
# 删除数据库
db.dropDatabase() //首先要通过use切换到当前的数据库
MongoDB没有创建数据库的命令,提供了use 命令切换数据库,如果数据库不存在,则切换后,创建完机会后会自动创建数据库。如果你要创建一个新的数据库,使用use 命令切换到新数据库,然后创建collection 即可。
有了集合数据后,就可以对MongoDB 的集合中数据进行增删改查等操作。MongoDB的数据结构和 JSON 基本一样。所有存储在集合中的数据都是 BSON 格式存储(一种类似 JSON 的二进制形式的存储格式,是 Binary JSON 的简称)。
插入数据之前,需要创建collocation,这里使用db.createCollection("``userinfo``")
命令创建了userinfo集合。
#新增一条数据
db.userinfo.insert({name:"三",age:25,gander:"男",address:'湖北'})
#新增多条数据
db.userinfo.insert([{name:"李四",age:25,gander:"男",address:'湖南'},{name:"王五",age:16,gander:"女",address:'浙江'}])
在 MongoDB 中,remove 和 deleteOne 以及 deleteMany 都用于删除文档记录。但是,remove 函数返回的删除的结果的 WriteResult,而 delete 函数返回的是 bson 格式。
其中 remove 是根据参数 justOne 来判断是删除所有匹配的文档记录还是仅仅删除一条匹配的文档记录,默认是删除所有的匹配的记录。
deleteOne 函数仅仅删除一条匹配的文档记录,而 deleteMany 函数是删除所有的匹配的文档记录。
# 全部删除
db.userinfo.deleteMany({})
# 删除age为25的一条数据
db.inventory.deleteOne( { age:25} )
# 删除年龄为16岁的全部数据
db.userinfo.deleteMany({age:16})
# remove 移除
db.userinfo.remove({'name':'王五'})
根据MongoDB的官方说明,remove() 方法已经过时了,现在官方推荐使用 deleteOne() 和 deleteMany() 方法。
MongoDB提供了 update() 方法来更新集合中的数据。update() 方法使用比较复杂,语法格式如下所示:
db.collection.update( , , { upsert: , multi: , writeConcern: })
参数说明:
query : update的查询条件,类似sql update语句后where查询条件。
update : update的对象和一些更新的操作(如$,$inc...)等,也可以理解为sql update查询内set 部分。
upsert : 可选,这个参数的意思是,如果不存在update的记录是否插入,true为插入,默认是false 不插入。
multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
writeConcern :可选,抛出异常的级别。
下面,我们通过 update() 方法来更新一条数据:
db.userinfo.update({'name':'王五'},{$set:{'address':'汉阳'}})
上面的示例中,通过update()方法将姓名(name)为‘王五’的用户的住址信息(address)改为‘汉阳’。
MongoDB 使用 find() 方法查询显示文档数据。语法格式如下:db.collection.find(query, projection)
。
query 指定查询条件,类似sql select语句后的where条件,
projection 为指定返回的键。默认返回文档中所有键值。
# 查询全部
db.userinfo.find()
# pretty() 方法以Json格式化显示所有文档。db.userinfo.find().pretty()
# 查询一条数据
db.userinfo.findOne()
# 限制返回条数
db.userinfo.find().limit(1)
我们在查询数据的时候,经常会在查询条件中遇到条件判断的情况。如:查询年龄大于18岁的所有人员。同样,MongoDB中也提供类类似的条件运算符,具体有如下几个:
(>) 大于 - $gt
(<) 小于 - $lt
(>=) 大于等于 - $gte
(<= ) 小于等于 - $lte
# 查询年龄大于20的全部人员
db.userinfo.find({age:{$gt:20}})
MongoDB同样也有运算符$in,查询是否在某个集合中,类似sql 中的in关键字。使用方式如下:
db.userinfo.find({age:{$in:[16,20]}})
MongoDB提供了sort() 方法对数据进行排序,通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。语法格式为:db.collection.find().sort({key:1})
。
# 按年龄升序
db.userinfo.find({}).sort({age:1})
# 按年龄降序
db.userinfo.find({}).sort({age:-1})
MongoDB提供了skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。语法格式为:db.collection.find().limit(NUMBER).skip(NUMBER)
。
#分页查询,跳过20条查询10条
db.userinfo.find({}).sort({age:1}).skip(20).limit(10)
如上所示,通过skip() 和limit() 方法,即可实现数据分页查询的功能。
Spring Boot提供了MongoDB的组件:spring-boot-starter-data-mongodb ,它是 Spring Data 的一个子模块。熟悉Spring Boot的朋友应该知道,Redis、Elasticsearch、JPA等数据操作组件都在Spring Data下。所以,在Spring Boot中操作mongodb和操作其他的数据库基本是一样的。
spring-boot-starter-data-mongodb 核心功能是映射 POJO 到 Mongo的DBCollection 中的文档,并且提供 Repository 风格数据访问层。spring-bootstarter-data-mongodb 除了继承 Spring Data 的通用功能外,针对 MongoDB 的特性开发了很多定制的功能,让我们使用 Spring Boot 操作 MongoDB 更加简便。
Spring Boot 操作 MongoDB 有两种比较流行的使用方法,一种是将 MongoTemplate 直接注入使用,一种是继承 MongoRepository, MongoRepository 内置了很多方法可直接使用。下面分别来介绍它们的使用。
MongoTemplate 提供了非常多的操作 MongoDB 方法,MongoTemplate 实现了MongoOperations 接口,此接口定义了众多的操作方法如 find、 findAndModify、findOne、 insert、 remove、 save、 update and updateMulti 等。并提供了Query、 Criteria and Update 等流式 API。
添加依赖
首先创建Spring Boot项目spring-boot-starter-mongodb,在 pom 包里面添加 spring-boot-starter-data-mongodb 包引用,示例代码如下:
org.springframework.boot spring-boot-starter-data-mongodb
添加MongoDB连接配置
修改application.yml配置文件,添加Mongo连接配置,具体如下:
spring:
data:
mongodb:
uri: mongodb://localhost:27017/news
mongodb默认没有账号密码,IP+端口+数据库就可以连接成功。如果mongodb配置了有账号密码,那连接字符串则需要增加相应的账号密码:
spring:
data:
mongodb:
uri: mongodb://username:[email protected]:27017/news
此外,如果MongoDB采用集群部署的方式,可以使用集群的配置方式,具体如下:
spring:
data:
mongodb:
uri: mongodb://user:pwd@ip1:port1,ip2:port2/database
如果需要打印数据库执行日志的话,可以加上下面的日志打印配置:
#打印mongoDB执行日志
logging:
level:
org.springframework.data.mongodb.core: DEBUG
然后就可以准备相关信息数据操作了。
准备一个mongoDB集合文档
这里提前准备数据库的文档数据。
创建其对应的实体类
package com.yy.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.models.auth.In;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.io.Serializable;
/**
* @author young
* @date 2022/12/5 21:24
* @description: 政策文档对应类
*/
@Data
@Document(collection = "policy")
@ApiModel(value = "policy文档", description = "对应的政策信息类")
public class Policy implements Serializable {
private static final long serialVersionUID = -7636268378874627600L;
@Id
@ApiModelProperty("政策文档id")
private String ids;
@ApiModelProperty("政策文档大标题")
private String bigTitle;
@ApiModelProperty("政策文档子标题")
private String childTitle;
@ApiModelProperty("政策文档内容")
private String content;
@ApiModelProperty("政策文档自定义id")
private Integer id;
@ApiModelProperty("政策文档点击量")
private Integer hits;
@ApiModelProperty("政策文档创建时间")
private String creatTime;
}
在IDEA中如果SpringBoot项目中导入mongoDB的相关集成依赖后,可以通过注解标名数据库集合对应的实体类信息。
然后和Mysql数据库不同的是,它不需要我们准备常规的Dao层,直接创建Service进行数据操作即可。
创建Service接口和实现类
package com.yy.service;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import com.yy.dto.NewsParam;
import com.yy.dto.PolicyParam;
import com.yy.entity.Net;
import com.yy.entity.Policy;
import java.util.List;
/**
* @author young
* @date 2022/12/5 21:58
* @description:
*/
public interface PolicyService {
/**
* 查询所有的Policy文档信息
* @return
*/
List findAll();
/**
* 根据自定义id查询对应的Policy文档信息
* @param id
* @return
*/
Policy findById(Integer id);
/**
* 查询所有大标题
* @return
*/
List bigTitleList();
/**
* 根据子标题查询对应的Policy文档信息
* @param childTitle
* @return
*/
Policy findByChild(String childTitle);
/**
* 统计大标题数
* @param bigTitle
* @return
*/
long countByType(String bigTitle);
/**
* 分页查询
* @param data
* @return
*/
List findByPage(NewsParam data);
/**
* 新增一个信息
* @param policy
* @return
*/
Policy save(Policy policy);
/**
* 通过子标题更新内容信息
* @param childTitle
* @param content
* @return
*/
UpdateResult update(String childTitle, String content);
/**
* 子标题模糊查询
* @param childTitle
* @return
*/
List findLike(String childTitle);
/**
* 删除集合数据库中的一条信息
* @param id
* @return
*/
DeleteResult delete(Integer id);
/**
* 管理员界面的政策信息分页查询
* @param data
* @return
*/
List findAdmin(PolicyParam data);
}
这里主要是实现一些基本的增删改查操作,通过MongoTamplate来实现操作逻辑。
package com.yy.service.impl;
import cn.hutool.core.date.DateUtil;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import com.yy.dto.NewsParam;
import com.yy.dto.PolicyParam;
import com.yy.entity.Policy;
import com.yy.service.PolicyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
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.util.Assert;
import java.time.LocalDateTime;
import java.util.List;
import java.util.regex.Pattern;
/**
* @author young
* @date 2022/12/5 22:01
* @description:
*/
@Service
public class PolicyServiceImpl implements PolicyService {
public static final String BIG_TITLE = "bigTitle";
public static final String CHILD_TITLE = "childTitle";
@Autowired
private MongoTemplate mongoTemplate;
@Override
public List findAll() {
return mongoTemplate.findAll(Policy.class);
}
@Override
public Policy findById(Integer id) {
return mongoTemplate.findOne(new Query(Criteria.where("_id").is(id)), Policy.class);
}
@Override
public List bigTitleList() {
return mongoTemplate.findDistinct(BIG_TITLE, Policy.class, Policy.class);
}
@Override
public Policy findByChild(String childTitle) {
return mongoTemplate.findOne(new Query(Criteria.where("childTitle").is(childTitle)), Policy.class);
}
@Override
public List findByPage(NewsParam data) {
Assert.notNull(data, "该对象为空");
Query query = new Query(Criteria.where(BIG_TITLE).is(data.getType()));
query.with(Sort.by(Sort.Direction.ASC, "_id"));
query.skip(data.getCurrent() - 1).limit(data.getSize());
return mongoTemplate.find(query, Policy.class);
}
@Override
public Policy save(Policy policy) {
return mongoTemplate.save(policy, "policy");
}
@Override
public long countByType(String bigTitle) {
Query query = new Query(Criteria.where(BIG_TITLE).is(bigTitle));
return mongoTemplate.count(query, Policy.class);
}
@Override
public UpdateResult update(String childTitle, String content) {
Query query = new Query(Criteria.where(CHILD_TITLE).is(childTitle));
Update title = new Update().set("content", content).set("creatTime", DateUtil.format(LocalDateTime.now(), "yyyy年MM月dd日"));
return mongoTemplate.updateFirst(query, title, Policy.class);
}
@Override
public List findLike(String childTitle) {
Pattern pattern = Pattern.compile("^.*" + childTitle.trim() + ".*$", Pattern.CASE_INSENSITIVE);
Query query = new Query(Criteria.where(CHILD_TITLE).regex(pattern));
return mongoTemplate.find(query,Policy.class);
}
@Override
public DeleteResult delete(Integer id) {
return mongoTemplate.remove(new Query(Criteria.where("_id").is(id)), Policy.class);
}
@Override
public List findAdmin(PolicyParam data) {
Assert.notNull(data, "null");
Criteria criteria = new Criteria();
Query query = new Query(criteria);
query.with(Sort.by(Sort.Direction.ASC, "_id"));
query.skip(data.getCurrent() - 1).limit(data.getSize());
return mongoTemplate.find(query, Policy.class);
}
}
如果需要进行复杂点的条件查询操作,可以通过Criteria这个类去操作,具体场景可以自行研究。
Criteria类:封装所有的语句,以方法的形式查询。
Query类:将语句进行封装或者添加排序之类的操作。
Criteria criteria = new Criteria();
criteria.andOperator(Criteria.where("laguage").regex(data.getFlsName()),Criteria.where("name").regex(data.getName()));
Query query = new Query(criteria);
简单测试一下:
完全ok。需要注意的是:和Mysql数据库操作不同的是,mongoDB对于数据的规范敏感性不是很强,因此插入数据时需要注意实体类与集合字段名的对应问题,如果集合上的字段是create_time,但是实体类上是createTime,再插入数据时就会出现下面的情况:
因为没有向Mysql那么强的字段映射约束(Mysql如果不一样并且没做格式化的话基本上会出现字段不一致导致插入失败),MongoDB并不会出现错误,它会产生新的字段并插入集合中,所以在MongoDB的使用上是需要注意这点的!
熟悉Spring Data的同学应该对Repository比较熟悉。MongoRepository 继承于 PagingAndSortingRepository,而PagingAndSortingRepository则是继承于CrudRepository,这两个接口是所有Repository接口的父接口。所以MongoRepository 和前面 JPA、 Elasticsearch 的使用比较类似,都是 Spring Data 家族的产品,最终使用方法也就和 JPA、 ElasticSearch 的使用方式类似。
创建接口继承MongoRepository
package com.yy.service;
import com.yy.entity.Policy;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author young
* Date 2023/4/16 18:06
* Description: final_design
*/
public interface MongoRepositoryTest extends MongoRepository {
}
其中T为仓库保存的bean类,TD为该bean的唯一标识的类型。之后在service中注入该接口就可以使用,无需实现里面的方法,spring会根据定义的规则自动生成。这样很多业务就可以像JPA那样省略了,直接拿来测试即可。
测试
可以看到,非常方便,对于一些查询,分页操作,在使用MongoRepository的过程中,非模糊查询多配合使用Example/ExampleMatcher来完成工作,有兴趣的可以深入了解一下。如果MongoRepository能实现,尽量不要做冗余的工作,如果非要自定义方法才能实现,一定要符合Spring-Data的规则来定义方法名。定义方法名的规则:findBy+属性名(首字母大写)+其他参数(参数类型 参数名),具体参考JPA文档内的命名规则,笔者用的也不多。
但是它并不算特别灵活,因此有些地方两者结合使用是很不错的。
更多学习资源可在MongoDB中文网和菜鸟教程学习哦~