楚昭男 發表於 2023-8-15 20:59:00

使用 SpringData 操作 Mongodb

<p>本篇博客主要介绍 SpringBoot 如何通过 SpringData 操作 Mongodb。在上篇博客部署的 mongodb 为了方便,在 admin 库中创建了一个 root 角色的账号,使用这个账号具有最高权限,可以访问和操作任何库。在实际项目中强烈建议为每个 mongodb 数据库创建一个低权限角色的用户,比如具有 readwrite 角色的用户。</p>
<p>有关 mongodb 支持的数据类型,以及索引视图等等,这里不做介绍,熟悉关系型数据库的小伙伴,对此肯定很容易入门,具体细节可以参考官网。本篇博客以 Demo 代码的方式介绍如何操作 mongodb,在博客最后会提供源代码下载。</p>
<p>Mongodb 的中文官网地址:https://www.mongodb.com/zh-cn</p>
<br>
<h3 id="一工程搭建">一、工程搭建</h3>
<p>我的虚拟机 ip 地址是:192.168.136.128,参照上篇博客部署 mongodb,使用 docker-compose 部署并初始化一个 root 角色的账号是 jobs ,密码是 123456,本篇博客的 Demo 代码,连接这个 mongodb 进行操作演示。</p>
<p>搭建一个 SpringBoot 工程,结构如下所示:</p>
<p><img src="https://img2023.cnblogs.com/blog/2502715/202308/2502715-20230815205812067-1000333567.jpg"></p>
<p>MongoTransactionConfig 是对 mongodb 进行事务配置,使其支持事务操作。</p>
<p>TransactionService 中编写了一个方法,用来测试 mongodb 对事务的支持。</p>
<p>Employee 是员工实体类,针对 mongodb 数据库中要操作的表 tb_employee 而创建。</p>
<p>MongoTest 类中编写了一些测试方法,用来对 mongodb 中 tb_employee 表进行增删改查。</p>
<p>该工程的 pom 文件内容如下:(最主要的是引入 spring-boot-starter-data-mongodb 这个依赖)</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;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"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

    &lt;groupId&gt;com.jobs&lt;/groupId&gt;
    &lt;artifactId&gt;springboot_mongo&lt;/artifactId&gt;
    &lt;version&gt;1.0&lt;/version&gt;

    &lt;properties&gt;
      &lt;maven.compiler.source&gt;8&lt;/maven.compiler.source&gt;
      &lt;maven.compiler.target&gt;8&lt;/maven.compiler.target&gt;
    &lt;/properties&gt;

    &lt;parent&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
      &lt;version&gt;2.3.9.RELEASE&lt;/version&gt;
    &lt;/parent&gt;
    &lt;dependencies&gt;
      &lt;!--引入最基本的 springboot 依赖--&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;
      &lt;/dependency&gt;
      &lt;!--引入 springdata mongodb 的依赖--&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-data-mongodb&lt;/artifactId&gt;
      &lt;/dependency&gt;
      &lt;!--引入 test 依赖--&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
            &lt;scope&gt;test&lt;/scope&gt;
      &lt;/dependency&gt;
      &lt;!--引入该依赖,可以打印日志,以及省去实例类的 get 和 set 方法--&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
            &lt;artifactId&gt;lombok&lt;/artifactId&gt;
      &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/project&gt;
</code></pre>
<br>
<h3 id="二代码细节">二、代码细节</h3>
<p>对于 mongodb 来说,不需要提前创建表,当添加数据后自动就会创建表。</p>
<p>本 demo 中以操作员工为例,创建一个 Employee 类,具体细节如下:</p>
<pre><code class="language-java">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; //薪水
}
</code></pre>
<p>需要注意的是:上面虽然引用了 @Indexed 和 @CompoundIndex 去自动为 mongodb 中的 tb_employee 创建索引,但是默认情况下并不会生效,必须在 yml 配置文件中将 auto-index-creation 配置为 true 才会生效。当然这里只是演示注解的使用,在实际项目中不建议通过注解去创建索引,建议根据实际需要,通过 mongodb 的命令去操作 mongodb 创建索引。</p>
<p>项目的 application.yml 配置文件内容如下所示:(主要是配置 mongodb 的连接字符串信息)</p>
<pre><code class="language-yaml">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
</code></pre>
<p>接下来我们就可以编写测试类,通过 Employee 类去对 mongodb 中的 tb_employee 表进行增删改查了</p>
<pre><code class="language-java">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&lt;Employee&gt; 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&lt;Employee&gt; 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&lt;Employee&gt; 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&lt;Map&gt; 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&lt;Map&gt; results = mongoTemplate.aggregate(aggregation, Employee.class, Map.class);
      for (Map result : results.getMappedResults()) {
            System.out.println(result);
      }
    }

    //随机获取 3 个人的信息
    @Test
    public void testGetRandomData() {
      //创建统计对象,设置统计参数,sample 表示随机获取数据
      TypedAggregation aggregation = Aggregation.newAggregation(Employee.class, Aggregation.sample(3));
      //调用mongoTemplate方法统计
      AggregationResults&lt;Employee&gt; results = mongoTemplate.aggregate(aggregation, Employee.class);
      //获取统计结果
      List&lt;Employee&gt; list = results.getMappedResults();
      //循环打印出来
      list.forEach(System.out::println);
    }

    //随机获取 2 个年龄大于 26 岁的员工
    @Test
    public void testGetWhereRandomData() {

      Criteria criteria = Criteria.where("age").gt(26);
      TypedAggregation&lt;Employee&gt; aggregation =
                TypedAggregation.newAggregation(Employee.class,
                        Aggregation.match(criteria), Aggregation.sample(2));

      AggregationResults&lt;Employee&gt; results = mongoTemplate.aggregate(aggregation, Employee.class);
      List&lt;Employee&gt; list = results.getMappedResults();
      list.forEach(System.out::println);
    }

    //删除【薪水最高】的老油条员工
    @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());
      }
    }
}
</code></pre>
<p>以上代码展示了对 mongodb 的增删改查,其中包含聚合查询,注释比较详细,应该很容易看懂。</p>
<p>通过 navicat 可以查看到 mongodb 中具体的数据,下图是操作过程中的截图:</p>
<p><img src="https://img2023.cnblogs.com/blog/2502715/202308/2502715-20230815205836030-1841595337.jpg"></p>
<br>
<h3 id="三事务支持">三、事务支持</h3>
<p>为了能够让 mongodb 支持事务,我们需要增加对事务的配置,代码如下:</p>
<pre><code class="language-java">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);
    }
}
</code></pre>
<p>然后在 Service 中编写一个方法,用于测试对事务的支持</p>
<pre><code class="language-java">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;
    }
}
</code></pre>
<p>然后在 MongoTest 测试方法中,编写一个测试方法,去调用 Service 方法,测试对事务的支持</p>
<pre><code class="language-java">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();
    }
}
</code></pre>
<br>
<p>OK,以上就是 SpringData 操作 Mongodb 的介绍,所有代码都经过测试没有问题。</p>
<p>本篇博客的源代码下载地址为:https://files.cnblogs.com/files/blogs/699532/springboot_mongo.zip<br>
<br></p><br><br>
来源:https://www.cnblogs.com/studyjobs/p/17632443.html
頁: [1]
查看完整版本: 使用 SpringData 操作 Mongodb