SpringBoot 操作 MongoDB CRUD
SpringBoot 操作 MongoDB CRUD
上接 SpringBoot 整合 MongoDB,记一下 MongoDB 的 CRUD 方法。
Create 新增
使用 MongoRepository 方式的新增非常简单,之前的整合中已经尝试过,这里再总结一下:
首先需要有对应的实体类对象:
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
@Id
private String userId;
private String name;
private Integer age;
private Date createDate;
}
然后创建这个实体类对应的仓库类,继承 MongoRepository 接口,泛型为实体类和实体类的ID类型:
public interface UserRepository extends MongoRepository<User, String> {
// 无需实现
}
最后直接调用 UserRepository 的 save 方法就可以实现新增操作:
@RequestMapping("/createUser")
public User createUser(){
User user = new User("0723","qy",21, new Date());
User saveUser = userRepository.save(user);
System.out.println(saveUser);
return saveUser;
}
访问这个请求地址,会返回这个 User 的信息,说明新增数据成功:
{"userId":"0723","name":"qy","age":21,"createDate":"2023-05-06T02:46:28.598+00:00"}
如果是批量新增,则对应的将对象放入 List 中即可:
@RequestMapping("/createUserList")
public List<User> createUserList(){
// 将新增的对象放入 List
List<User> userList = new ArrayList<>();
userList.add(new User("0718","Irror",18, new Date()));
userList.add(new User("0123","Inory",19, new Date()));
List<User> saveUserList = userRepository.saveAll(userList);
System.out.println(saveUserList);
return saveUserList;
}
访问这个请求地址,会以 List 的形式返回增加的 User 的信息,说明增加成功:
[{"userId":"0718","name":"Irror","age":18,"createDate":"2023-05-06T02:48:14.460+00:00"},
{"userId":"0123","name":"Inory","age":19,"createDate":"2023-05-06T02:48:14.460+00:00"}]
查看数据库中的数据,与增加的数据也一致(ID 0118 的是以前加的)。

Read 查询
MongoDB 的查询方式有很多种,这里都试一下吧。
仓库类
MongoRepository 提供了许多增删改查的方法,查询的方法即 findAll(),之前已经使用过,但这个方法除了直接查询所有,通过不同的参数还支持排序、分页、条件查询,对应了 findAll() 方法的四个重载。
先看直接使用,调用 findAll() 可以获取集合中的所有对象:
@RequestMapping("/getAllUsers")
public List<User> getAllUsers() {
return userRepository.findAll();
}
请求这个接口,返回对象列表:
[{"userId":"0118","name":"qyc","age":20,"createDate":"2023-04-14T15:13:00.000+00:00"},
{"userId":"0723","name":"qy","age":21,"createDate":"2023-05-06T02:46:28.598+00:00"},
{"userId":"0718","name":"Irror","age":18,"createDate":"2023-05-06T02:48:14.460+00:00"},
{"userId":"0123","name":"Inory","age":19,"createDate":"2023-05-06T02:48:14.460+00:00"}]
通过构造一个排序 Sort 对象并传入,可以实现排序查询:
@RequestMapping("/getUsersBySort")
public List<User> getUsersBySort() {
// Sort.Direction.ASC 升序 DESC 降序
Sort sort = Sort.by(Sort.Direction.ASC, "age");
// 根据传入的排序条件查询
List<User> users = userRepository.findAll(sort);
return userRepository.findAll();
}
请求这个接口,返回的结果就是按年龄升序排列的了:
[{"userId":"0718","name":"Irror","age":18,"createDate":"2023-05-06T02:48:14.460+00:00"},
{"userId":"0123","name":"Inory","age":19,"createDate":"2023-05-06T02:48:14.460+00:00"},
{"userId":"0118","name":"qyc","age":20,"createDate":"2023-04-14T15:13:00.000+00:00"},
{"userId":"0723","name":"qy","age":21,"createDate":"2023-05-06T02:46:28.598+00:00"}]
通过构造一个分页 PageRequest 对象并传入,可以实现分页查询,需要注意,MongoDB 的页数从0开始:
@RequestMapping("/getUsersByPage/{page}/{rows}")
public Page<User> getUsersByPage(@PathVariable int page, @PathVariable int rows) {
// 构造方法访问权限为 protected
// MongoDB 页数从0开始 需要 -1
PageRequest pageRequest = PageRequest.of(page-1, rows);
return userRepository.findAll(pageRequest);
}
请求 /getUsersByPage/1/2 返回的就是包含第一页的两条数据的分页对象:
{"content":
[{"userId":"0118","name":"qyc","age":20,"createDate":"2023-04-14T15:13:00.000+00:00"},
{"userId":"0723","name":"qy","age":21,"createDate":"2023-05-06T02:46:28.598+00:00"}],
"pageable":
{"sort":{"sorted":false,"unsorted":true,"empty":true},"offset":0,"pageNumber":0,"pageSize":2,"unpaged":false,"paged":true},
"totalPages":2,"totalElements":4,"last":false,"number":0,"size":2,
"sort":{"sorted":false,"unsorted":true,"empty":true},
"numberOfElements":2,"first":true,"empty":false}
最后是条件查询,需要构建模板 Example 对象,在构建这个对象前又需要一个匹配器指定匹配规则、一个实体对象存放匹配参数,还是挺麻烦的,网上能搜到的内容也少,可能就不怎么常用:
@RequestMapping("/getUsersByExample")
public List<User> getUsersByExample() {
// 匹配规则:忽略大小写 + name 包含
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnoreCase()
.withMatcher("name", ExampleMatcher.GenericPropertyMatchers.contains());
User user = new User();
user.setName("qy");
// 查询模板
Example<User> example = Example.of(user, matcher);
List<User> userList = userRepository.findAll(example);
return userList;
}
请求这个接口就返回了条件查询的结果,名字包含 qy 的对象:
[{"userId":"0118","name":"qyc","age":20,"createDate":"2023-04-14T15:13:00.000+00:00"},
{"userId":"0723","name":"qy","age":21,"createDate":"2023-05-06T02:46:28.598+00:00"}]
仓库类的查询方式大概就这么多,虽然可以直接用,但条件查询好像不是很友好啊。。。
@Query注解
上面是直接使用仓库类自带的方法进行查询,在简单场景下应该是够用,但复杂一点的场景就会麻烦。所以 MongoDB 支持使用 @Query 注解自定义查询方法,使用的也是 MongoDB 的原生查询语句。
在仓库类 UserRepository 中添加两个方法,一个是上面实现了的根据名字查询(忽略大小写)的方法,还有一个是查询年龄小于输入参数的方法:
public interface UserRepository extends MongoRepository<User, String> {
// 自定义查询方法
@Query("{ 'name' : { $regex: ?0, $options: 'i' } }")
List<User> findByNameContainingIgnoreCase(String name);
@Query("{ 'age' : { $lt: ?0 } }")
List<User> findByAgeLessThan(int age);
}
这两个方法都通过 @Query 注解自定义了查询逻辑,这里使用了 $regex 来进行模糊匹配,$options 选项设置为 'i' 表示不区分大小写,$lt 即 LessThan,?0 则代表了第一个参数。
使用时直接调用,传入相应的参数即可:
@RequestMapping("/getUserByName/{name}")
public List<User> getUserByName(@PathVariable String name){
// 调用仓库类的方法
List<User> saveUserList = userRepository.findByNameContainingIgnoreCase(name);
return saveUserList;
}
@RequestMapping("/getUserByLessThanAge/{age}")
public List<User> getUserByLessThanAge(@PathVariable int age){
// 调用仓库类的方法
List<User> saveUserList = userRepository.findByAgeLessThan(age);
return saveUserList;
}
为了方便我都使用了路径参数,请求 /getUserByName/qy 会返回名字包含 qy的用户
[{"userId":"0118","name":"qyc","age":20,"createDate":"2023-04-14T15:13:00.000+00:00"},
{"userId":"0723","name":"qy","age":21,"createDate":"2023-05-06T02:46:28.598+00:00"}]
请求 /getUserByLessThanAge/20 会返回年龄小于20的用户:
[{"userId":"0718","name":"Irror","age":18,"createDate":"2023-05-06T02:48:14.460+00:00"},
{"userId":"0123","name":"Inory","age":19,"createDate":"2023-05-06T02:48:14.460+00:00"}]
这种方式通过自己写查询语句,比仓库类自带的方法灵活多了,但也相对复杂一定,有舍有得。
Query对象
除了使用 @Query 预先定义查询方法外,也可以通过创建 Query 对象并设置其中的 Criteria 指定查询条件,这种方式胜在随处可用,但需要引入 MongoTemplate 对象:
@Autowired
private MongoTemplate mongoTemplate;
举个例子,写一个支持名字和年龄查询的方法:
@RequestMapping("/getUserByQuery/{name}/{date}")
public List<User> getUserByQuery(@PathVariable String name, @PathVariable int age){
// 指定查询的集合
String collectionName = "user";
// 构造查询对象
Query query = Query.query(Criteria.where("name").regex(".*" + name + ".*", "i")
.and("age").gte(age));
// 调用 mongoTemplate 查询
List<User> users = mongoTemplate.find(query, User.class, collectionName);
return users;
}
其中的 Criteria 就是查询条件,通过链式编程的方式可以一直往下写。最后通过 mongoTemplate 对象进行查询,注意需要指定查询的集合。
如请求 /getUserByGreaterThanDate/qy/21 查询名字包括 qy且年龄大于等于 21 的用户:
[{"userId":"0723","name":"qy","age":21,"createDate":"2023-05-06T02:46:28.598+00:00"}]
使用 Query 对象查询的方式本质还是构造 MongoDB 原生的查询语句去查询,如上面这个查询参数对应的就是
Query: { "name" : { "$regularExpression" : { "pattern" : ".*qy.*", "options" : "i"}}, "age" : { "$gte" : 21}}, Fields: {}, Sort: {}
因此这种方式可以说是最灵活的了,可以指定的东西非常多。
Update 修改
本来问 GPT 修改怎么搞,它说也有 @Query 注解和 Query 对象两种方式,结果尝试用 @Query 注解搞了半天一直搞不好,头都痛了。然后在 Stackoverflow 上看到有人说 @Query 不管用,只能用 Query 对象,晕了。
修改的话其实要 Query 对象和 Update 对象一起使用,如:
@RequestMapping("/updateUserNameById/{id}/{name}")
public List<User> updateUserNameById(@PathVariable String id, @PathVariable String name){
Query query = new Query(Criteria.where("_id").is(id));
Update update = new Update().set("name", name);
mongoTemplate.updateFirst(query, update, User.class);
return userRepository.findAll();
}
其中先通过构造 Query 对象,决定修改的对象,再构造 Update 对象决定修改的内容,最后通过 mongoTemplate 的 updateFirst 方法进行修改,这个方法只会修改查询到的第一条记录,要修改多条记录的话则要使用 updateMulti。
修改完后查询所有记录返回,确认确实修改成功了。这么简单的问题搞了半天,折磨。
Delete 删除
和修改类似,直接使用 Query 对象就完事了。
@RequestMapping("/deleteUserById/{id}")
public List<User> deleteUserById(@PathVariable String id){
// 构建查询条件
Query query = Query.query(Criteria.where("_id").is(id));
// 删除符合条件的记录
mongoTemplate.remove(query, User.class, "user");
return userRepository.findAll();
}
通过 Query 对象按条件查询出需要删除的文档,调用 mongoTemplate 的 remove 方法就可以删除这些文档。
总结
在 MongoDB 中尝试了基本的 CRUD 操作,最后发现还是使用 Query 对象的方式最靠谱,怪不得项目 jlr-cons-mongo 里也是这么用的,真得操作了才知道。

浙公网安备 33010602011771号