使用 SpringData 操作 Mongodb
本篇博客主要介绍 SpringBoot 如何通过 SpringData 操作 Mongodb。在上篇博客部署的 mongodb 为了方便,在 admin 库中创建了一个 root 角色的账号,使用这个账号具有最高权限,可以访问和操作任何库。在实际项目中强烈建议为每个 mongodb 数据库创建一个低权限角色的用户,比如具有 readwrite 角色的用户。
有关 mongodb 支持的数据类型,以及索引视图等等,这里不做介绍,熟悉关系型数据库的小伙伴,对此肯定很容易入门,具体细节可以参考官网。本篇博客以 Demo 代码的方式介绍如何操作 mongodb,在博客最后会提供源代码下载。
Mongodb 的中文官网地址:https://www.mongodb.com/zh-cn
一、工程搭建
我的虚拟机 ip 地址是:192.168.136.128,参照上篇博客部署 mongodb,使用 docker-compose 部署并初始化一个 root 角色的账号是 jobs ,密码是 123456,本篇博客的 Demo 代码,连接这个 mongodb 进行操作演示。
搭建一个 SpringBoot 工程,结构如下所示:
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);
}
}
//随机获取 3 个人的信息
@Test
public void testGetRandomData() {
//创建统计对象,设置统计参数,sample 表示随机获取数据
TypedAggregation aggregation = Aggregation.newAggregation(Employee.class, Aggregation.sample(3));
//调用mongoTemplate方法统计
AggregationResults<Employee> results = mongoTemplate.aggregate(aggregation, Employee.class);
//获取统计结果
List<Employee> list = results.getMappedResults();
//循环打印出来
list.forEach(System.out::println);
}
//随机获取 2 个年龄大于 26 岁的员工
@Test
public void testGetWhereRandomData() {
Criteria criteria = Criteria.where("age").gt(26);
TypedAggregation<Employee> aggregation =
TypedAggregation.newAggregation(Employee.class,
Aggregation.match(criteria), Aggregation.sample(2));
AggregationResults<Employee> results = mongoTemplate.aggregate(aggregation, Employee.class);
List<Employee> 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());
}
}
}
以上代码展示了对 mongodb 的增删改查,其中包含聚合查询,注释比较详细,应该很容易看懂。
通过 navicat 可以查看到 mongodb 中具体的数据,下图是操作过程中的截图:
三、事务支持
为了能够让 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