一、基础配置
1. pom 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
2.配置文件
spring:
data:
mongodb:
username: root
password: 123456
host: 127.0.0.1
port: 27017
authentication-database: admin
database: demo
3. 常用注解
注解名称 | 作用对象 | 功能 |
---|---|---|
@Document | 实体类 | 类似于 hibernate 的 entity 注解,标明由 mongo 来维护该表 |
@CompoundIndex | 实体类 | 复合索引 |
@Id | 字段 | 和 mongoDB 中 _id 字段对应 |
@Field | 字段 | 可以通过该注解自定义在 mongo 中的名称 |
@Indexed | 字段 | 声明该字段需要加索引,加索引后以该字段为条件检索将大大提高速度 |
@Transient | 字段 | 表示该字段不在 mongo 中存储,既忽略该字段 |
二、基础操作
以图书管理为例,定义实体类
Book.java
package com.ming.springbootmongo.model;
import org.springframework.data.annotation.Id;
import java.util.Arrays;
public class Book {
/**
* mongo 自增 id
*/
@Id
private String id;
/**
* 图书名称
*/
private String name;
/**
* 价格
*/
private double price;
/**
* 出版社
*/
private String press;
/**
* 作者
*/
private Author[] authors;
/**
* 标签
*/
private String[] tags;
public Book() {
}
public Book(String name, double price, String press, Author[] authors, String[] tags) {
this.name = name;
this.price = price;
this.press = press;
this.authors = authors;
this.tags = tags;
}
......
getter()
setter()
......
}
Author.java
public class Author {
/**
* 姓名
*/
private String name;
/**
* 性别
*/
private String sex;
public Author() {
}
public Author(String name, String sex) {
this.name = name;
this.sex = sex;
}
......
getter()
setter()
......
}
SprintBoot 测试类文件 BookServiceTest.java
import com.ming.springbootmongo.model.Author;
import com.ming.springbootmongo.model.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@SpringBootTest
public class BookServiceTest {
/**
* 文档集合名称
*/
private static final String BOOK_COLLECTION = "book";
private final MongoTemplate mongoTemplate;
@Autowired
public BookServiceTest(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
}
1. 插入
1.1 插入单条
@Test
public void testSaveOne() {
Book book = new Book("高等数学上", 37.70, "高等教育出版社", new Author[]{
new Author("同济大学数学系", "")}, new String[]{"数学", "大学数学", "高等数学"});
final Book ret = mongoTemplate.insert(book, BOOK_COLLECTION);
System.out.println(ret);
}
1.2 插入多条
@Test
public void testSaveBatch() {
List<Book> bookList = Arrays.asList(
new Book("TCP/IP详解卷1:协议", 45.00, "机械工业出版社", new Author[]{
new Author("W.Richard Stevens", "男"),
new Author("范建华", "男"),
new Author("胥光辉", "男"),
new Author("张涛", "男"),
new Author("谢希仁", "男")
}, new String[]{"计算机网络", "网络协议", "编程"}),
new Book("Python程序设计开发宝典", 69.00, "清华大学出版社", new Author[]{new Author(
"董付国", "男")}, new String[]{"程序设计", "编程", "python"}),
new Book("汇编语言", 36.00, "清华大学出版社", new Author[]{new Author("王爽", "")},
new String[]{"程序设计", "编程", "汇编"}),
new Book("SparkSQL入门与实践指南", 49.00, "清华大学出版社", new Author[]{
new Author("纪涵", "女"),
new Author("靖晓文", "女"),
new Author("赵政达", "男")
}, new String[]{"程序设计", "编程", "spark", "spark sql"})
);
final Collection<Book> ret = mongoTemplate.insert(bookList, BOOK_COLLECTION);
System.out.println(ret.size());
}
1.3 去掉 _class 字段
直接使用 insert 进行插入,会在 mongo 中出现 _class 字段,如果想去掉此字段,可以自定义 MappingMongoConverter 配置
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@Configuration
public class MongoConfig {
@Bean
public MappingMongoConverter mappingMongoConverter(MongoDatabaseFactory factory, MongoMappingContext context, BeanFactory beanFactory) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);
try {
mappingConverter.setCustomConversions(beanFactory.getBean(CustomConversions.class));
} catch (NoSuchBeanDefinitionException ignore) {
}
// Don't save _class to mongo
mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
return mappingConverter;
}
}
2. 查询
MognoTemplate 中使用 Criteria + Query 对象 进行查询条件封装
// 创建 Critera 方式
// 第一种 使用静态方法创建 Criteria
Criteria criteria = Criteria.where("name").is("Python程序设计开发宝典");
// 第二种 直接 new 实例,然后设置变量参数
Criteria criteria = new Criteria();
criteria.and("name").is("Python程序设计开发宝典");
2.1 简单查询
2.1.1 等于
@Test
public void testEqual1() {
// 使用静态方法创建 Criteria
Criteria criteria = Criteria.where("name").is("Python程序设计开发宝典");
// new Criteria 实例,然后设置变量参数
// Criteria criteria = new Criteria();
// criteria.and("name").is("Python程序设计开发宝典");
Query query = new Query(criteria);
// 如果只想查询一条或者明确知道仅有一条, 使用 findOne,否则,可以使用 find 或 findAll
final Book ret = mongoTemplate.findOne(query, Book.class, BOOK_COLLECTION);
assert ret != null;
}
@Test
public void testEqual2() {
// 使用静态方法创建 Criteria
Criteria criteria = Criteria.where("press").is("清华大学出版社");
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 3;
}
2.1.2 大于
@Test
public void testGt() {
Criteria criteria = Criteria.where("price").gt(30);
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 5;
}
2.1.3 大于等于
@Test
public void testGte() {
Criteria criteria = Criteria.where("price").gte(45);
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 3;
}
2.1.4 小于
@Test
public void testLt() {
Criteria criteria = Criteria.where("price").lt(45);
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 2;
}
2.1.5 小于等于
@Test
public void testLte() {
Criteria criteria = Criteria.where("price").lte(45);
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 3;
}
2.16 不等于
@Test
public void testNe() {
Criteria criteria = Criteria.where("price").ne(45);
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 4;
}
2.2 多条件查询
2.2.1 and
@Test
public void testAnd() {
Criteria criteria = new Criteria();
criteria.and("price").gt(45)
.and("press").is("清华大学出版社");
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 2;
}
2.2.2 or
@Test
public void testOr() {
Criteria criteria = new Criteria();
// 每个条件构建一个 criteria 对象
Criteria criteria1 = Criteria.where("price").lt(45);
Criteria criteria2 = Criteria.where("press").is("清华大学出版社");
// 将多个 criteria 对象使用 or 操作关联起来
criteria.orOperator(criteria1, criteria2);
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 4;
}
2.2.3 not
// 相当于 not(price < 45 or press='清华大学出版社')
@Test
public void testNot(){
Criteria criteria = new Criteria();
Criteria criteria1 = Criteria.where("price").lt(45);
Criteria criteria2 = Criteria.where("press").is("清华大学出版社");
criteria.norOperator(criteria1, criteria2);
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
}
2.2.4 in
@Test
public void testIn(){
Criteria criteria = Criteria.where("press").in("机械工业出版社","清华大学出版社");
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
}
2.2.5 nin
@Test
public void testNin() {
Criteria criteria = Criteria.where("press").nin("机械工业出版社", "清华大学出版社");
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 1;
}
2.3 正则查询
2.3.1 开头包含字符串
@Test
public void testBeginWith(){
// 定义正则表达式
String name = "高等";
Pattern pattern = Pattern.compile("^" + name + ".*$", Pattern.CASE_INSENSITIVE);
Criteria criteria = Criteria.where("name").regex(pattern);
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 1;
}
2.3.2 包含
@Test
public void testContain(){
String name = "开发";
Pattern pattern = Pattern.compile("^.*" + name + ".*$", Pattern.CASE_INSENSITIVE);
Criteria criteria = Criteria.where("name").regex(pattern);
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 1;
}
2.3.3 结尾包含字符串
@Test
public void testEndWith(){
String name = "指南";
Pattern pattern = Pattern.compile("^.*" + name + "$", Pattern.CASE_INSENSITIVE);
Criteria criteria = Criteria.where("name").regex(pattern);
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 1;
}
2.4 数组查询
2.4.1 匹配数组中单个值
// 数组中包含查询单值
@Test
public void testArrayMatchSingle() {
Criteria criteria = Criteria.where("tags").is("编程");
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 4;
}
2.4.2 匹配数组中多个值
// 数组中同时包含指定的多个值,不要求顺序
@Test
public void testArrayMatchAll() {
Criteria criteria = Criteria.where("tags").all("编程", "程序设计");
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 3;
}
2.4.3 匹配数组中元素个数
@Test
public void testArrayMatchSize() {
Criteria criteria = Criteria.where("tags").size(4);
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 1;
}
2.4.4 匹配指定位置元素
// 满足特定索引下条件
// 数组索引从 0 开始,匹配第二项就用 tags.1 作为键
@Test
public void testMatchIndex() {
Criteria criteria = Criteria.where("tags.1").is("编程");
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 3;
}
2.4.5 匹配整个数组
// 元素个数和顺序都要匹配
@Test
public void testArrayMatch() {
Criteria criteria = Criteria.where("tags").is(new String[]{"程序设计", "编程", "python"});
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 1;
}
2.4.6 匹配子文档
@Test
public void testMatchSubDoc() {
Criteria criteria = Criteria.where("authors.name").is("纪涵");
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() == 1;
}
2.4.7 elementMatch
@Test
public void testElementMatch() {
Criteria criteria = Criteria.where("authors").elemMatch(Criteria.where("name").is("谢希仁").and("sex").is("男"));
Query query = new Query(criteria);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size()== 1;
}
2.5 聚合查询
mongoDB 聚合查询语法,这些命令均在 Aggregation 中实现
$project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
m a t c h : 用 于 过 滤 数 据 , 只 输 出 符 合 条 件 的 文 档 。 match:用于过滤数据,只输出符合条件的文档。 match:用于过滤数据,只输出符合条件的文档。match使用MongoDB的标准查询操作。
$limit:用来限制MongoDB聚合管道返回的文档数。
$skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
$unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
$group:将集合中的文档分组,可用于统计结果。
$sort:将输入文档排序后输出。
$geoNear:输出接近某一地理位置的有序文档。
Spring MongoDB 聚合操作
聚合操作 | 方法列表 |
---|---|
Pipeline Aggregation Operators | bucket , bucketAuto , count , facet , geoNear , graphLookup , group , limit , lookup , match , project , rand , replaceRoot , skip , sort , unwind |
Set Aggregation Operators | setEquals , setIntersection , setUnion , setDifference , setIsSubset , anyElementTrue , allElementsTrue |
Group/Accumulator Aggregation Operators | addToSet , covariancePop , covarianceSamp , expMovingAvg , first , last , max , min , avg , push , sum , count (*), stdDevPop , stdDevSamp |
Arithmetic Aggregation Operators | abs , add (* via plus ), asin , asin , atan , atan2 , atanh , ceil , cos , cosh , derivative , divide , exp , floor , integral , ln , log , log10 , mod , multiply , pow , round , sqrt , subtract (* via minus ), sin , sinh , tan , tanh , trunc |
String Aggregation Operators | concat , substr , toLower , toUpper , strcasecmp , indexOfBytes , indexOfCP , regexFind , regexFindAll , regexMatch , split , strLenBytes , strLenCP , substrCP , trim , ltrim , rtim |
Comparison Aggregation Operators | eq (* via is ), gt , gte , lt , lte , ne |
Array Aggregation Operators | arrayElementAt , arrayToObject , concatArrays , filter , in , indexOfArray , isArray , range , reverseArray , reduce , size , slice , zip |
Literal Operators | literal |
Date Aggregation Operators | dayOfYear , dayOfMonth , dayOfWeek , year , month , week , hour , minute , second , millisecond , dateAdd , dateDiff , dateToString , dateFromString , dateFromParts , dateToParts , isoDayOfWeek , isoWeek , isoWeekYear |
Variable Operators | map |
Conditional Aggregation Operators | cond , ifNull , switch |
Type Aggregation Operators | type |
Convert Aggregation Operators | convert , degreesToRadians , toBool , toDate , toDecimal , toDouble , toInt , toLong , toObjectId , toString |
Object Aggregation Operators | objectToArray , mergeObjects |
Script Aggregation Operators | function , accumulator |
2.5.1 count
@Test
public void testCount() {
Query query = new Query();
final long ret = mongoTemplate.count(query, BOOK_COLLECTION);
System.out.println(ret);
}
2.5.2 distinct
@Test
public void testDistinct(){
final List<String> distinctPress = mongoTemplate.findDistinct(new Query(), "press", BOOK_COLLECTION, String.class);
System.out.println(distinctPress);
}
2.5.3 sum、count、avg
@Test
public void testSum() {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.project("_id", "press", "price"),
Aggregation.group("press")
.count().as("count")
.sum("price").as("total")
.avg("price").as("avg")
);
final AggregationResults<Map> aggregationResults = mongoTemplate.aggregate(aggregation, BOOK_COLLECTION, Map.class);
final List<Map> mappedResults = aggregationResults.getMappedResults();
System.out.println(mappedResults);
}
2.5.4 带有表达式的计算
@Test
public void testSum2() {
// 定义乘法表达式, 可以是两个字段相乘或者字段与数字相乘,同理,ArithmeticOperators 包含 加减乘除模等操作
final ArithmeticOperators.Multiply price = ArithmeticOperators.valueOf("PRICE").multiplyBy(5);
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.project("_id", "press", "PRICE"),
Aggregation.group("press")
.count().as("count")
.sum(price).as("total")
.avg(price).as("avg")
);
final AggregationResults<Map> aggregationResults = mongoTemplate.aggregate(aggregation, BOOK_COLLECTION, Map.class);
final List<Map> mappedResults = aggregationResults.getMappedResults();
System.out.println(mappedResults);
}
2.6 排序与分页
排序与分页功能在 Query 对象中设置
@Test
public void testSortAndLimit() {
Criteria criteria = Criteria.where("press").is("清华大学出版社");
Query query = new Query(criteria);
// with 方法设置排序
query.with(Sort.by(Sort.Direction.ASC, "price"));
// skip limit 方法设置分页
query.skip(1).limit(10);
final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
assert books.size() ==2;
}
3. 更新
定义 Query 对象,定义要更新的数据范围
定义 Update 对象,定义更新内容
3.1 基础更新
3.1.1 更新基础字段
@Test
public void testUpdateField() {
Query query = new Query(Criteria.where("_id").is("614303980f84fe24d7618f28"));
Update update = new Update();
update.set("name", "高等数学");
final UpdateResult result = mongoTemplate.updateFirst(query, update, BOOK_COLLECTION);
assert result.wasAcknowledged();
}
3.1.2 加减数字
@Test
public void testUpdateNumber() {
Query query = new Query(Criteria.where("_id").is("614303980f84fe24d7618f28"));
Update update = new Update();
// inc 方法指定数字增加多少
update.inc("price",30.04);
final UpdateResult result = mongoTemplate.updateFirst(query, update, BOOK_COLLECTION);
assert result.wasAcknowledged();
}
3.1.3 乘除数字
@Test
public void testMultiply() {
Query query = new Query(Criteria.where("_id").is("614303980f84fe24d7618f28"));
Update update = new Update().multiply("price", 0.3);
final UpdateResult result = mongoTemplate.updateFirst(query, update, BOOK_COLLECTION);
assert result.wasAcknowledged();
}
3.2 字段更新
3.2.1 重命名字段
@Test
public void testRenameFiled() {
Query query = new Query();
Update update = new Update().rename("price", "PRICE");
final UpdateResult result = mongoTemplate.updateMulti(query, update, BOOK_COLLECTION);
assert result.wasAcknowledged();
}
3.2.2 增加字段
set 方法在没有字段时,会自动增加字段
@Test
public void testAddField() {
Query query = new Query();
Update update = new Update().set("workspaceId", "default");
final UpdateResult result = mongoTemplate.updateMulti(query, update, BOOK_COLLECTION);
assert result.wasAcknowledged();
}
3.2.3 删除字段
@Test
public void testRemoveField() {
Query query = new Query();
Update update = new Update().unset("_class");
final UpdateResult result = mongoTemplate.updateMulti(query, update, BOOK_COLLECTION);
assert result.wasAcknowledged();
}
3.3 数组更新
3.3.1 插入数组内容
@Test
public void testAddToArray() {
Query query = new Query(Criteria.where("_id").is("614303980f84fe24d7618f28"));
Update update = new Update();
// 添加一个,数组中存在元素在忽略添加
//update.addToSet("tags").value("a");
// 添加多个,数组中存在元素则忽略添加
//update.addToSet("tags").each("a", "b", "c", "c");
// 无论是否存在,都添加到数组, 可以通过 atPosition 指定存放位置
update.push("tags").atPosition(1).value("101");
final UpdateResult ret = mongoTemplate.updateFirst(query, update, BOOK_COLLECTION);
assert ret.wasAcknowledged();
}
3.3.2 移除数组内容
@Test
public void testRemoveArray() {
Query query = new Query(Criteria.where("_id").is("614303980f84fe24d7618f28"));
Update update = new Update();
// 移除单个数据
//update.pull("tags","a");
// 移除多个数据
update.pullAll("tags", new Object[]{"a", "b", "101"});
final UpdateResult ret = mongoTemplate.updateFirst(query, update, BOOK_COLLECTION);
assert ret.wasAcknowledged();
}
3.3.3 更新替换数组内容
// 数组内字符串更新
@Test
public void testUpdateArray1() {
Query query = new Query();
// 将标签数组中,所有 "编程" 替换为 "程序设计"
Update update = new Update();
update.set("tags.$[element]", "程序设计");
update.filterArray(Criteria.where("element").is("编程"));
final UpdateResult ret = mongoTemplate.updateMulti(query, update, BOOK_COLLECTION);
assert ret.wasAcknowledged();
}
// 数组内子文档更新
@Test
public void testUpdateArray2() {
Query query = new Query();
Update update = new Update();
// 将作者姓名为 "王爽" 替换为 "王爽爽"
update.set("authors.$[element].name", "王爽爽");
update.filterArray(Criteria.where("element.name").is("王爽"));
final UpdateResult ret = mongoTemplate.updateMulti(query, update, BOOK_COLLECTION);
assert ret.wasAcknowledged();
}
3.4 复杂的更新操作
使用算数操作和条件操作,根据文档实际数据选择更新内容
AggregationUpdate update = Aggregation.newUpdate()
.set("average").toValue(ArithmeticOperators.valueOf("tests").avg())
.set("grade").toValue(ConditionalOperators.switchCases(
when(valueOf("average").greaterThanEqualToValue(90)).then("A"),
when(valueOf("average").greaterThanEqualToValue(80)).then("B"),
when(valueOf("average").greaterThanEqualToValue(70)).then("C"),
when(valueOf("average").greaterThanEqualToValue(60)).then("D"))
.defaultTo("F")
);
4. 删除
@Test
public void testDelete() {
Criteria criteria = Criteria.where("authors.name").is("王爽爽");
Query query = new Query(criteria);
final DeleteResult ret = mongoTemplate.remove(query, BOOK_COLLECTION);
assert ret.wasAcknowledged();
}
附录
Srping MongoDB 文档
https://docs.spring.io/spring-data/mongodb/docs/current-SNAPSHOT/reference/html/#mongodb-template-update