1、MongoDB 简介
MongoDB 是一个开源的、面向文档的 NoSQL 数据库,旨在提供高性能、高可用性和易扩展性。它与传统的关系型数据库(如 MySQL)有着根本性的区别。
面向文档(Document-Oriented):数据以 BSON(Binary JSON)文档的形式存储,结构类似于 JSON 对象。一个文档相当于关系型数据库中的一行,但一个文档中可以存储非常复杂的、嵌套的数据结构。
模式自由(Schema-less):集合(Collection,类似于表)中的文档不需要具有相同的字段集。这提供了极大的灵活性,允许你在不中断应用程序的情况下动态调整数据结构。
高性能:通过嵌入式数据模型、索引、复制和分片等特性,MongoDB 可以提供极高的读写吞吐量。
高可用性:通过副本集(Replica Set) 提供自动故障转移。副本集是一组维护相同数据集的 MongoDB 服务器,确保了数据的冗余和服务的连续性。
易扩展性:通过分片(Sharding) 实现水平扩展。分片将数据分布到一个集群中的多台机器上,以应对海量数据和高并发场景。
MongoDB
核心概念对比:
MongoDB与SQL对比
MongoDB 被设计用来满足现代应用程序的需求,特别是那些需要处理大量非结构化或半结构化数据的应用场景,比如内容管理系统、实时分析、移动应用后端等。由于其灵活性和性能优势,MongoDB 成为许多开发者和企业的首选数据库解决方案之一。
2、MongoDB 安装
这里以在本地开发环境(如 macOS 、Linux 和 Windows)安装为例。
2.1. macOS (使用 Homebrew)
# 1. 更新 Homebrew
brew update
# 2. 安装 MongoDB Community Edition
brew tap mongodb/brew
brew install mongodb-community
# 3. 启动 MongoDB 服务
brew services start mongodb-community
# 4. (可选) 连接验证
mongosh
2.2. Windows
访问 MongoDB Community Server 下载页面:https://www.mongodb.com/try/download/community
选择版本,下载 .msi 安装包。
运行安装程序,建议选择 "Complete" 完整安装。
安装完成后,MongoDB 通常会被安装到 D:\Program Files\MongoDB。
创建数据存储目录:D:\data\MongoDB。
通过命令行启动 MongoDB 服务:
# 切换到 MongoDB 的 bin 目录
cd D:\Program Files\MongoDB\Server\{version}\bin
# 启动 mongod 进程
mongod.exe
保持该窗口运行,另开一个命令行窗口,运行 mongosh.exe 来连接数据库。
2.3. Linux安装
下载MongoDB Community Server
下载地址:
https://www.mongodb.com/try/download/community
解压后启动MongoDB Server:
#创建dbpath和logpath
mkdir -p /mongodb/data /mongodb/log
#进入mongodb目录,启动mongodb服务
bin/mongod --port=27017 --dbpath=/mongodb/data --logpath=/mongodb/log/mongodb.log
2.4. Docker (跨平台推荐)
对于开发和测试,Docker 是最简单、干净的方式。
# 拉取最新镜像
docker pull mongo
# 运行容器
docker run -d --name my-mongo -p 27017:27017 mongo:latest
# 进入容器内的 shell 进行操作
docker exec -it my-mongo mongosh
3、MongoDB 基本命令使用
通过 mongosh 连接数据库后,可以执行以下基本命令。
// 查看所有数据库
show dbs
// 切换/创建数据库 (如果数据库不存在,插入第一条数据时会自动创建)
use testdb
// 查看当前数据库中的集合
show collections
// 插入文档(insertOne/insertMany)
db.users.insertOne({name: "Alice", age: 25, email: "alice@example.com", hobbies: ["reading", "hiking"]})
db.users.insertMany([{name: "Bob", age: 30}, {name: "Charlie", age: 35}])
// 查询文档(find)
db.users.find() // 查询所有
db.users.findOne({name: "Alice"}) // 查询一个
db.users.find({age: {$gt: 25}}) // 查询年龄大于25的
// 更新文档(updateOne/updateMany)
db.users.updateOne({name: "Alice"}, {$set: {age: 26}}) // 设置字段
db.users.updateMany({age: {$lt: 30}}, {$inc: {age: 1}}) // 自增
// 删除文档(deleteOne/deleteMany)
db.users.deleteOne({name: "Charlie"})
db.users.deleteMany({age: {$gt: 40}})
// 创建索引
db.users.createIndex({email: 1}) // 1为升序,-1为降序
db.users.createIndex({name: 1, age: -1}) // 复合索引
4、SpringBoot集成MongoDB
4.1、环境准备
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
完整依赖如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
</dependencies>
4.2、配置
application.properties
# MongoDB
spring.data.mongodb.host = 127.0.0.1
spring.data.mongodb.port = 27017
spring.data.mongodb.database = test
spring.data.mongodb.auto-index-creation = true
# 或者直接使用uri方式
#spring.data.mongodb.uri=mongodb://user:password@127.0.0.1:27017/test?authSource=admin&maxPoolSize=100&minPoolSize=10&maxIdleTimeMS=60000&waitQueueTimeoutMS=120000&connectTimeoutMS=10000&socketTimeoutMS=30000
# 打印mongoDB日志
logging.level.org.springframework.data.mongodb.core.MongoTemplate=DEBUG
logging.level.org.mongodb.driver.connection=DEBUG
连接配置可参考文档 https://www.mongodb.com/zh-cn/docs/manual/reference/connection-string/
4.3、文档操作
1、新增实体
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Document(collection = "person") // 指定集合名称
public class Person {
@Id // 映射文档中的_id
private Long id;
private String name;
private Integer age;
}
2、添加集合文档
集合操作时注入MongoTemplate
@SpringBootTest(classes = MongoApplication.class)
@RunWith(SpringRunner.class)
public class InsertTests {
@Resource
private MongoTemplate mongoTemplate;
@Test
public void testInsert() {
Person person = Person.builder().id(2L).name("river").age(18).build();
mongoTemplate.insert(person);
// 如果有就修改, 没有就插入
mongoTemplate.save(Person.builder().id(2L).name("zhangsan").age(28).build());
// 批量写入
List<Person> persons = new ArrayList<>();
for (int i = 100; i <= 150; i++) {
person = Person.builder().id(Long.valueOf(i)).name("lisi" + i).age(i).build();
persons.add(person);
}
mongoTemplate.insertAll(persons);
}
}
插入数据时: insert插入的_id 不能有重复,否则会报 DuplicateKeyException 提示主键重复; save对已存在的数据进行更新;
批处理操作时: insertAll可以一次性插入所有数据,效率较高;save需遍历所有数据,一次插入或更新,效率较低。
3、查询集合文档
@Slf4j
@SpringBootTest(classes = MongoApplication.class)
@RunWith(SpringRunner.class)
public class QueryTests {
@Autowired
private MongoTemplate mongoTemplate;
@Test
public void find() {
// 查询集合中的全部文档数据
List<Person> personList = mongoTemplate.findAll(Person.class);
log.info("查询结果:{}", personList);
// 查询集合中指定的ID文档数据
Person byId = mongoTemplate.findById(1L, Person.class);
log.info("查询结果:{}", byId.toString());
// 根据条件查询符合条件的文档数据并返回第一条数据
Query query = new Query(Criteria.where("name").is("river"));
Person result = mongoTemplate.findOne(query, Person.class);
log.info("查询结果:{}", result);
// 根据条件查询所有符合条件的文档
query = new Query(Criteria.where("age").gt(18));
List<Person> list = mongoTemplate.find(query, Person.class);
log.info("查询结果:{}", list);
// 创建查询对象,然后将条件对象添加到其中
Criteria criteria = Criteria.where("age").gt(18).lte(30);
query = new Query(criteria);
list = mongoTemplate.find(query, Person.class);
log.info("查询结果:{}", list);
Criteria name = Criteria.where("name").is("zhangsan");
Criteria age = Criteria.where("age").is(18);
// 创建条件对象,将上面条件进行 AND 关联
criteria = new Criteria().andOperator(name, age);
query = new Query(criteria);
list = mongoTemplate.find(query, Person.class);
log.info("查询结果:{}", list);
// 从第5行开始,查询3条数据返回
query = new Query(Criteria.where("age").is("20"))
.with(Sort.by("id"))
.limit(3).skip(5);
list = mongoTemplate.find(query, Person.class);
log.info("查询结果:{}", list);
}
}
Criteria是标准查询的接口,可以引用静态的Criteria.where的把多个条件组合在一起,就可以轻松地将多个方法查询连接起来,方便我们操作查询语句。
4、更新集合文档
@Slf4j
@SpringBootTest(classes = MongoApplication.class)
@RunWith(SpringRunner.class)
public class UpdateTests {
@Autowired
private MongoTemplate mongoTemplate;
@Test
public void update() {
Person person = mongoTemplate.findById(1L, Person.class);
person.setName("wangwu");
mongoTemplate.save(person);
Query query = new Query(Criteria.where("id").is(1L));
// 修改内容
Update update = new Update().set("name", "lisi");
UpdateResult updateResult = mongoTemplate.updateMulti(query, update, Person.class);
// // 只更新满足条件的第一条记录
// UpdateResult updateResult = mongoTemplate.updateFirst(query, update, Person.class);
// 没有符合条件的记录则插入数据
// // update.setOnInsert("id",11); // 指定_id
// updateResult = mongoTemplate.upsert(query, update, Person.class);
// 返回修改的记录数
log.info("updateResult: {}", updateResult.getModifiedCount());
}
}
如果更新后的结果和更新前的结果是相同,返回0。
updateFirst() 只更新满足条件的第一条记录
updateMulti() 更新所有满足条件的记录
upsert() 没有符合条件的记录则插入数据
5、删除集合文档
@Slf4j
@SpringBootTest(classes = MongoApplication.class)
@RunWith(SpringRunner.class)
public class DeleteTests {
@Autowired
private MongoTemplate mongoTemplate;
@Test
public void delete() {
// 删除id为1的记录
Query query = new Query(Criteria.where("id").is(1L));
DeleteResult remove = mongoTemplate.remove(query, Person.class);
log.info("删除的条数为:{}", remove.getDeletedCount());
// 删除符合条件的单个文档并返回删除的文档
query = new Query(Criteria.where("id").is(2L));
Person per = mongoTemplate.findAndRemove(query, Person.class);
log.info("删除的文档: {}", per);
}
}
4.4、去掉_class属性
使用MongoTemplate写入文档时,会自动将_class属性添加到文档中,一般不需要使用_class属性,可以配置去掉_class属性。
@Configuration
public class MongoConfig {
@Bean("mongoTemplate")
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoMappingContext mongoMappingContext) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
// 去掉_class字段
mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
return new MongoTemplate(mongoDbFactory, mappingMongoConverter);
}
}
4.5、忽略字段
忽略字段,即不将字段写入到数据库中,可以通过注解@Transient实现。
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Document(collection = "person") // 指定集合名称
public class Person {
@Id // 映射文档中的_id
private Long id;
private String name;
@Transient
private Integer age;
}
5、使用时注意事项
正确使用索引:分析查询模式,为经常查询的字段、排序字段创建索引。避免全集合扫描。但索引不是越多越好,它会占用空间并降低写操作性能。
设计文档结构:嵌入式(Embed) vs 引用式(Reference):对于一对一且不频繁变化的关系,使用嵌入式(将子文档直接放在主文档中)。对于一对多或多对多且频繁查询独立的关系,使用引用式(存储另一个文档的 _id)。
避免大文档:MongoDB 单个文档大小限制为 16MB。避免无限制地增长数组。
使用 Repository 抽象层:充分利用 Spring Data 的派生查询和 @Query 注解,减少手写模板代码。
连接池配置:生产环境中,务必在 application.properties 中配置连接池参数,以避免资源耗尽。
使用 MongoTemplate 进行复杂操作:对于复杂的聚合查询、更新操作,可以直接注入 MongoTemplate,它提供了更细粒度的控制。
绝不使用单节点:生产环境必须部署副本集(Replica Set)。这提供了数据冗余、高可用和自动故障转移能力。
启用认证和授权:为 MongoDB 实例设置用户名和密码,并遵循最小权限原则,为应用创建专属用户,只授予其必要的数据库操作权限。
备份与恢复:制定并定期测试备份策略。可以使用 mongodump/mongorestore 或文件系统快照等工具。
监控与告警:监控数据库的关键指标,如内存使用率、CPU 使用率、操作计数器、复制延迟等。
6、总结
Spring Boot 与 MongoDB 的集成极大地简化了 Java 开发者使用 NoSQL 数据库的流程。通过 Spring Data MongoDB,我们可以用面向对象的方式高效地操作 MongoDB,几乎不需要编写底层的数据库代码,然而,将 MongoDB 用于生产环境务必做好副本集、安全、备份和监控,才能构建出稳定、可靠的应用系统。对于大多数 Web 应用,尤其是需要处理多样化数据、高吞吐读写的场景,Spring Boot + MongoDB 是一个不错的技术组合。