SpringBoot + MongoDB
一、MongoDB 基本概念
MongoDB 是一个基于 文档模型(Document Model) 的数据库,它的数据以类似 JSON 的 BSON 格式存储。
MongoDB + SpringBoot 3.x 的开发效率非常高,尤其适合:
- • IoT 设备数据
- • 用户系统
- • 日志/监控平台
- • 内容管理系统
- • 电商类复杂字段数据
与MySQL的对比关系
| MongoDB | MySQL | 含义 |
|---|---|---|
| database | database | 数据库 |
| collection | table | 集合(表) |
| document | row | 文档(行) |
| field | column | 字段(列) |
| _id | primary key | 唯一主键(自动生成 ObjectId) |
一个典型的 MongoDB 文档
json
{
"_id": "672bcd94d12345a98efaa032",
"name": "张三",
"age": 26,
"tags": ["java", "iot"],
"address": {
"city": "上海",
"district": "浦东新区"
}
}二、SpringBoot3 集成 MongoDB
1.添加依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>2.配置连接
yaml
spring:
data:
mongodb:
# 无账号密码添加
uri: mongodb://localhost:27017/test
# 有账号密码添加
# url: mongodb://user:password@localhost:27017/test3.实体类(Document)定义
java
@Data
@Schema(description = "用户表")
public class User implements Serializable {
@Serial
private static final long serialVersionUID = 7478513151262531440L;
@Schema(description = "id")
@NotBlank(groups = {UpdateGroup.class})
private String id;
@NotBlank(groups = {SaveGroup.class,UpdateGroup.class})
@Schema(description = "用户名称")
private String name;
@NotBlank(groups = {SaveGroup.class,UpdateGroup.class})
@Schema(description = "用户年龄")
private Integer age;
}4. 核心 CRUD 实战
增删改查
java
User saveUser(User user);
boolean updateUser(User user);
boolean deleteUser(String id);
List<User> findUserById(String id);
List<User> findUserByName(String name);分页: 因为MongoDB本身不原生支持分页,只能通过跳页的方式,自定义实现分页(MongoDB深度分页效率极低)
java
//分页
PageResult<User> findUserByPage(Integer current, Integer size, String name);
//分页
PageResult<UserVO> findUserVOByPage(Integer current, Integer size);构建分页结果类
java
@Data
@Schema(description = "分页结果")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PageResult<T> {
@Schema(description = "页码,从1开始")
private Integer pageNum;
@Schema(description = "页面大小")
private Integer pageSize;
@Schema(description = "总数")
private Long total;
@Schema(description = "总页数")
private Integer pages;
@Schema(description = "数据")
private List<T> list;
}分页工具
java
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class MongoPageHelper {
/**
* 起始页号
*/
private static final int FIRST_PAGE_NUM = 1;
/**
* mongodb中的id字段
*/
private static final String ID = "_id";
/**
* 分页查询,直接返回集合类型的结果
*/
public static <T> PageResult<T> pageQuery(Query query, Class<T> entityClass, Integer pageNum, Integer pageSize) {
return pageQuery(query, entityClass, Function.identity(), pageNum, pageSize, null);
}
/**
* 分页查询,不考虑条件分页,直接使用skip-limit来分页
*/
public static <T, R> PageResult<R> pageQuery(Query query, Class<T> entityClass, Function<T, R> mapper,
Integer pageNum, Integer pageSize) {
return pageQuery(query, entityClass, mapper, pageNum, pageSize, null);
}
/**
* 分页查询 采用find(_id>lastId).limit分页
*/
public static <T> PageResult<T> pageQuery(Query query, Class<T> entityClass, Integer pageNum, Integer pageSize, String lastId) {
return pageQuery(query, entityClass, Function.identity(), pageNum, pageSize, lastId);
}
/**
* 分页查询
*
* @param query Mongo Query对象,构造你自己的查询条件.
* @param entityClass Mongo collection定义的entity class,用来确定查询哪个集合.
* @param mapper 映射器,你从db查出来的list的元素类型是entityClass, 如果你想要转换成另一个对象,比如去掉敏感字段等,可以使用mapper来决定如何转换.
* @param pageNum 当前页.
* @param pageSize 分页的大小.
* @param lastId 条件分页参数, 区别于skip-limit,采用find(_id>lastId).limit分页.
* 如果不跳页,像朋友圈,微博这样下拉刷新的分页需求,需要传递上一页的最后一条记录的ObjectId。 如果是null,则返回pageNum那一页.
* @param <T> collection定义的class类型.
* @param <R> 最终返回时,展现给页面时的一条记录的类型。
* @return PageResult,一个封装page信息的对象.
*
* @see MongoPageHelper#pageQuery(org.springframework.data.mongodb.core.query.Query,java.lang.Class,
* java.util.function.Function,java.lang.Integer,java.lang.Integer,java.lang.String)
*
*/
public static <T, R> PageResult<R> pageQuery(Query query, Class<T> entityClass, Function<T, R> mapper,
Integer pageNum, Integer pageSize, String lastId) {
MongoTemplate mongoTemplate = ApplicationContextUtils.getBean(MongoTemplate.class);
//分页逻辑
long total = mongoTemplate.count(query, entityClass);
final Integer pages = (int) Math.ceil(total / (double) pageSize);
if (pageNum <= 0 || pageNum > pages) {
pageNum = FIRST_PAGE_NUM;
}
final Criteria criteria = new Criteria();
if (StringUtils.isNotBlank(lastId)) {
if (pageNum != FIRST_PAGE_NUM) {
criteria.and(ID).gt(new ObjectId(lastId));
}
query.limit(pageSize);
} else {
int skip = pageSize * (pageNum - 1);
query.skip(skip).limit(pageSize);
}
final List<T> entityList = mongoTemplate
.find(query.addCriteria(criteria)
.with(Sort.by(Order.asc(ID))),
entityClass);
final PageResult<R> pageResult = new PageResult<>();
pageResult.setTotal(total);
pageResult.setPages(pages);
pageResult.setPageSize(pageSize);
pageResult.setPageNum(pageNum);
pageResult.setList(entityList.stream().map(mapper).toList());
return pageResult;
}
}接口实现类
java
@Slf4j
@Service
@RequiredArgsConstructor
public class MongodbServiceImpl implements MongodbService {
private final MongoTemplate mongoTemplate;
@Override
public User saveUser(User user) {
return mongoTemplate.save(user);
}
@Override
public boolean updateUser(User user) {
List<User> users = mongoTemplate.find(new Query(Criteria.where("_id").is(user.getId())), User.class);
if (users.isEmpty()){
throw new RuntimeException("用户不存在");
}
UpdateResult updateResult = mongoTemplate.updateFirst(
new Query(Criteria.where("_id").is(user.getId())),
new Update()
.set("name",user.getName())
.set("age",user.getAge()),
User.class);
return updateResult.wasAcknowledged();
}
@Override
public boolean deleteUser(String id) {
DeleteResult deleteResult = mongoTemplate.remove(new Query(Criteria.where("_id").is(id)), User.class);
return deleteResult.wasAcknowledged();
}
@Override
public List<User> findUserById(String id) {
return mongoTemplate.find(new Query(Criteria.where("_id").is(id)),User.class);
}
@Override
public List<User> findUserByName(String name) {
return mongoTemplate.find(new Query(Criteria.where("name").is(name)),User.class);
}
@Override
public PageResult<User> findUserByPage(Integer current, Integer size, String name) {
Query query = new Query();
if (StringUtils.isNotBlank(name)){
query.addCriteria(Criteria.where("name").is(name));
}
return MongoPageHelper.pageQuery(query, User.class, current, size);
}
@Override
public PageResult<UserVO> findUserVOByPage(Integer current, Integer size) {
return MongoPageHelper.pageQuery(
new Query(),
User.class,
user -> {
UserVO userVO = new UserVO();
userVO.setName(user.getName());
userVO.setAge(user.getAge());
return userVO;
} ,
current, size);
}
}5. 索引(性能优化关键)
给 name 字段创建索引
java
@Indexed
private String name;复合索引:运行项目后索引自动创建
java
@CompoundIndex(def = "{'name':1, 'age':-1}")6. MongoDB 事务(SpringBoot 实现)
MongoDB 自 4.0 起支持多文档事务,Spring 使用 @Transactional 即可。
注意:事务仅在 ReplicaSet(副本集)模式下生效
java
@Transactional
public void transfer() {
mongoTemplate.save(a);
mongoTemplate.save(b);
}
biubiu