使用 SpringData 操作 Mongodb

发布时间 2023-08-15 21:02:41作者: 乔京飞

本篇博客主要介绍 SpringBoot 如何通过 SpringData 操作 Mongodb。在上篇博客部署的 mongodb 为了方便,在 admin 库中创建了一个 root 角色的账号,使用这个账号具有最高权限,可以访问和操作任何库。在实际项目中强烈建议为每个 mongodb 数据库创建一个低权限角色的用户,比如具有 readwrite 角色的用户。

有关 mongodb 支持的数据类型,以及索引视图等等,这里不做介绍,熟悉关系型数据库的小伙伴,对此肯定很容易入门,具体细节可以参考官网。本篇博客以 Demo 代码的方式介绍如何操作 mongodb,在博客最后会提供源代码下载。

Mongodb 的中文官网地址:https://www.mongodb.com/zh-cn


一、工程搭建

我的虚拟机 ip 地址是:192.168.136.128,在上篇博客中通过 DockerCompose 部署的 mongodb 时,创建了一个 root 角色的账号是 jobs ,密码是 123456,本篇博客的 Demo 代码,连接这个 mongodb 进行操作演示。

搭建一个 SpringBoot 工程,结构如下所示:

image

MongoTransactionConfig 是对 mongodb 进行事务配置,使其支持事务操作。

TransactionService 中编写了一个方法,用来测试 mongodb 对事务的支持。

Employee 是员工实体类,针对 mongodb 数据库中要操作的表 tb_employee 而创建。

MongoTest 类中编写了一些测试方法,用来对 mongodb 中 tb_employee 表进行增删改查。

该工程的 pom 文件内容如下:(最主要的是引入 spring-boot-starter-data-mongodb 这个依赖)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jobs</groupId>
    <artifactId>springboot_mongo</artifactId>
    <version>1.0</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
    </parent>
    <dependencies>
        <!--引入最基本的 springboot 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--引入 springdata mongodb 的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <!--引入 test 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--引入该依赖,可以打印日志,以及省去实例类的 get 和 set 方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

二、代码细节

对于 mongodb 来说,不需要提前创建表,当添加数据后自动就会创建表。

本 demo 中以操作员工为例,创建一个 Employee 类,具体细节如下:

package com.jobs.pojo;

import lombok.Data;
import lombok.experimental.Accessors;

import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoId;

//注意:
//这里只是演示相关注解的使用,但是不建议在实体类上通过注解去建立索引
//最好通过命令,直接操作 mongodb 的文档去建立相关索引

@Data
//使用该注解,标明要操作的 mongodb 的文档(相当于数据库的表)
@Document("tb_employee")
//建立多字段联合索引( 1 表示升序,-1 表示降序)
@CompoundIndex(def = "{'depart':1,'age':-1}")
//使用该注解,可以使用对象实例化赋值采用链式编写
@Accessors(chain = true)
public class Employee {

    //使用该注解,标明 mongodb 文档的主键 id
    @MongoId
    private String id;

    //使用该注解,针对单个字段创建索引,unique = true 表示唯一索引
    @Indexed(unique = true)
    private String workNo; //员工编号

    //如果 mongodb 文档的字段名与该实体类的字段名不一致
    //使用该注解,标明 mongodb 文档中实际的字段名
    @Field("ename")
    private String name; //姓名

    private String depart; //部门

    private Integer age; //年龄

    private Integer money; //薪水
}

需要注意的是:上面虽然引用了 @Indexed 和 @CompoundIndex 去自动为 mongodb 中的 tb_employee 创建索引,但是默认情况下并不会生效,必须在 yml 配置文件中将 auto-index-creation 配置为 true 才会生效。当然这里只是演示注解的使用,在实际项目中不建议通过注解去创建索引,建议根据实际需要,通过 mongodb 的命令去操作 mongodb 创建索引。

项目的 application.yml 配置文件内容如下所示:(主要是配置 mongodb 的连接字符串信息)

spring:
  data:
    mongodb:
      # 连接字符串格式
      # mongodb://用户名:密码@Ip地址:端口/数据库名
      # 如果使用的是 root 角色的用户登录,则必须在后面加上 authSource=admin 参数
      # 之前在 admin 库中创建了一个 root 角色的账号 jobs
      # 在实际项目中,强烈建议,针对每个数据库创建一个 readwrite 角色的用户
      uri: mongodb://jobs:123456@192.168.136.128:27017/mytest?authSource=admin
      # 允许在实体类上,通过 @Indexed 创建单字段索引,通过 @CompoundIndex 创建多字段联合索引
      # 注意:这里只是演示注解的使用,实际项目中一般不推荐使用注解创建索引,
      # 最好通过 mongodb 的命令操作 mongodb 管理索引
      auto-index-creation: true

接下来我们就可以编写测试类,通过 Employee 类去对 mongodb 中的 tb_employee 表进行增删改查了

package com.jobs.test;

import com.jobs.pojo.Employee;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
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 java.util.List;
import java.util.Map;

@SpringBootTest
public class MongoTest {

    //注入 spring-data-mongodb 自带的 MongoTemplate 对象
    @Autowired
    private MongoTemplate mongoTemplate;

    //添加一批员工
    @Test
    public void testAdd() {
        //其实不需要给 id 赋值,因为 mongodb 会自动生成主键 id
        //insert 和 save 都是添加记录,它们的区别是:
        //如果 id 在文档记录中已经存在的情况下,save 会更新,insert 会抛异常

        //前 3 条记录,采用 insert 方法,主键不存在就添加,存在就抛异常
        Employee emp1 = new Employee().setId("001")
                .setWorkNo("nb001").setName("任肥肥").setDepart("研发部").setAge(38).setMoney(2500);
        mongoTemplate.insert(emp1);

        Employee emp2 = new Employee().setId("002")
                .setWorkNo("nb002").setName("候胖胖").setDepart("研发部").setAge(40).setMoney(2900);
        mongoTemplate.insert(emp2);

        Employee emp3 = new Employee().setId("003")
                .setWorkNo("nb003").setName("蔺赞赞").setDepart("商务部").setAge(36).setMoney(2700);
        mongoTemplate.insert(emp3);

        //后面这些记录,采用 save 方法,主键存在就更新,不存在就新增
        Employee emp4 = new Employee().setId("004")
                .setWorkNo("nb004").setName("乔豆豆").setDepart("财务部").setAge(39).setMoney(1800);
        mongoTemplate.save(emp4);

        Employee emp5 = new Employee().setId("005")
                .setWorkNo("nb005").setName("李吨吨").setDepart("研发部").setAge(25).setMoney(2300);
        mongoTemplate.save(emp5);

        Employee emp6 = new Employee().setId("006")
                .setWorkNo("nb006").setName("杨壮壮").setDepart("研发部").setAge(28).setMoney(2200);
        mongoTemplate.save(emp6);
    }

    //查询所有员工
    @Test
    public void testFindAll() {
        List<Employee> list = mongoTemplate.findAll(Employee.class);
        for (Employee emp : list) {
            System.out.println(emp);
        }
    }

    //条件查询:查询【研发部】,并且年龄大于 30 岁的员工,按照年龄【倒序】排列
    @Test
    public void testFindWhere() {
        Criteria criteria = Criteria.where("depart").is("研发部").and("age").gt(30);
        Query query = new Query(criteria).with(Sort.by(Sort.Order.desc("age")));
        List<Employee> list = mongoTemplate.find(query, Employee.class);
        for (Employee emp : list) {
            System.out.println(emp);
        }
    }

    //将年龄在 30 岁以下的员工,薪水增加 100 元,部门改为大数据部门
    @Test
    public void testUpdate() {
        Query query = Query.query(Criteria.where("age").lt(30));
        Update update = new Update();
        update.inc("money", 100);
        update.set("depart", "大数据部门");

        UpdateResult updateResult = mongoTemplate.updateMulti(query, update, Employee.class);
        //打印修改的文档数量
        System.out.println(updateResult.getModifiedCount());
    }

    //分页查询(查询薪水大于等于 2000 的员工,按照薪水【升序】排列)
    @Test
    public void testPage() {
        int page = 2;
        int size = 2;

        Criteria criteria = Criteria.where("money").gt(2000);
        Query queryCount = new Query(criteria);
        long total = mongoTemplate.count(queryCount, Employee.class);
        System.out.println("一共有 " + total + " 条记录,其中第 " + page + " 页的结果为:");

        Query queryLimit = new Query(criteria)
                .skip((page - 1) * size) //跳过多少条
                .limit(size) //返回多少条
                .with(Sort.by(Sort.Order.asc("money")));

        List<Employee> list = mongoTemplate.find(queryLimit, Employee.class);
        for (Employee emp : list) {
            System.out.println(emp);
        }
    }

    //统计每个部门的人数
    @Test
    public void testGroupCout() {
        // 统计各个部门的人数
        AggregationOperation group = Aggregation.group("depart").count().as("empCount");
        // 将操作加入到聚合对象中
        Aggregation aggregation = Aggregation.newAggregation(group);
        // 执行聚合查询
        AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, Employee.class, Map.class);
        for (Map result : results.getMappedResults()) {
            System.out.println(result);
        }
    }

    //统计每个部门,最高的薪水是多少
    @Test
    public void testGroupMax() {
        //这里使用的是 max ,当然你也可以使用 min(最小),sum(总和),avg(平均)等方法
        AggregationOperation group = Aggregation.group("depart").max("money").as("maxMoney");
        Aggregation aggregation = Aggregation.newAggregation(group);
        AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, Employee.class, Map.class);
        for (Map result : results.getMappedResults()) {
            System.out.println(result);
        }
    }

    //删除【薪水最高】的老油条员工
    @Test
    public void testDelete() {
        //先找到薪水最高的老油条员工
        Query maxQuery = new Query().with(Sort.by(Sort.Order.desc("money")));
        Employee one = mongoTemplate.findOne(maxQuery, Employee.class);
        //打印出老油条员工
        System.out.println(one);

        if (one != null) {
            //这里直接删除了,当然你也可以再通过查询条件,比如主键 id 进行删除
            DeleteResult remove = mongoTemplate.remove(one);
            //打印出删除的文档数量
            System.out.println(remove.getDeletedCount());
        }
    }
}

以上代码展示了对 mongodb 的增删改查,其中包含聚合查询,注释比较详细,应该很容易看懂。

通过 navicat 可以查看到 mongodb 中具体的数据,下图是操作过程中的截图:

image


三、事务支持

为了能够让 mongodb 支持事务,我们需要增加对事务的配置,代码如下:

package com.jobs.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.MongoTransactionManager;

@Configuration
public class MongoTransactionConfig {

    //配置 mongodb 事务管理器,能够让 mongodb 支持事务操作
    @Bean
    MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {
        return new MongoTransactionManager(dbFactory);
    }
}

然后在 Service 中编写一个方法,用于测试对事务的支持

package com.jobs.service;

import com.jobs.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TransactionService {

    //注入 spring-data-mongodb 自带的 MongoTemplate 对象
    @Autowired
    private MongoTemplate mongoTemplate;

    @Transactional
    public void transactionTest() {
        Employee emp = new Employee().setId("100")
                .setWorkNo("nb101").setName("任我行").setDepart("总裁办").setAge(44).setMoney(9999);
        mongoTemplate.save(emp);
        //抛出异常,导致添加的数据,回滚撤回
        int num = 1 / 0;
    }
}

然后在 MongoTest 测试方法中,编写一个测试方法,去调用 Service 方法,测试对事务的支持

package com.jobs.test;

import com.jobs.service.TransactionService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MongoTest {

    @Autowired
    private TransactionService transactionService;

    //测试 mongodb 的事务
    @Test
    public void testTransaction() {
        //该方法中,去掉抛出异常的代码,则可以正常添加。
        //如果方面中抛出异常,则会进行事务回滚
        //从而证明:mongodb 也支持事务操作
        transactionService.transactionTest();
    }
}

OK,以上就是 SpringData 操作 Mongodb 的介绍,所有代码都经过测试没有问题。

本篇博客的源代码下载地址为:https://files.cnblogs.com/files/blogs/699532/springboot_mongo.zip