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 的是以前加的)。

img

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 对象决定修改的内容,最后通过 mongoTemplateupdateFirst 方法进行修改,这个方法只会修改查询到的第一条记录,要修改多条记录的话则要使用 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 对象按条件查询出需要删除的文档,调用 mongoTemplateremove 方法就可以删除这些文档。

总结

在 MongoDB 中尝试了基本的 CRUD 操作,最后发现还是使用 Query 对象的方式最靠谱,怪不得项目 jlr-cons-mongo 里也是这么用的,真得操作了才知道。

posted @ 2023-05-06 15:37  Qirror  阅读(184)  评论(0)    收藏  举报
Live2D