本文主要对SpringBoot2.x集成SpringDataMongoDB及其基本使用进行简单总结,其中SpringBoot使用的2.4.5
版本。
一、SpringDataMongoDB简介
MongoDB是一个通用的、基于文档的分布式数据库,它将数据存储在类似JSON的文档中。
Spring Data MongoDB是Spring Data项目的一部分,该项目旨在为新的数据存储提供熟悉且一致的基于Spring的编程模型,同时保留存储的特定特性和功能。Spring Data MongoDB项目提供与MongoDB文档数据库的集成。Spring Data MongoDB的关键功能领域是以POJO为中心的模型,用于与MongoDB的DBCollection交互并轻松编写Repository风格的数据访问层。
二、集成SpringDataMongoDB
通过Maven新建一个名为springboot-data-mongodb
的项目。
1.引入依赖
<!-- Spring Data MongoDB 起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
2.编写配置文件
在application.yml
中进行如下配置:
spring:
data:
mongodb:
# MongoDB的uri连接
# 集群可以配置为mongodb://username:password@ip:port,ip:port,ip:port
uri: mongodb://root:root@localhost:27017/admin
# 数据库
database: test
# 输出nosql日志
logging:
level:
org:
springframework:
data:
mongodb:
core: debug
3.创建实体类
package com.rtxtitanv.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import java.time.LocalDateTime;
/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.model.User
* @description 用户实体类
* @date 2021/5/26 18:34
*/
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
@Document(collection = "user")
public class User {
@Id
private ObjectId id;
@Field(name = "username")
private String username;
@Field(name = "password")
private String password;
@Field(name = "realname")
private String realname;
@Field(name = "gender")
private String gender;
@Field(name = "age")
private Integer age;
@Field(name = "email")
private String email;
@Field(name = "user_point")
private Integer userPoint;
@Field(value = "user_level")
private Byte userLevel;
@Field(name = "birthday")
private LocalDateTime birthday;
}
MongoDB要求要为所有文档的设置一个_id
字段(field)。如果没有提供,驱动程序将会一个分配一个带有生成值的ObjectId
。用@Id
(org.springframework.data.annotation.Id
)注解的属性或域(field)会映射到_id
字段。没有注解但名为id
的属性或域也会映射到_id
字段。
几个常用的映射注解:
@Id
:应用于域上,以标记用于身份识别的字段(主键)。@MongoId
:应用于域上,以标记用于身份识别的字段(主键)。接受一个可选的FieldType
来自定义id转换。@Document
:应用于类上,表示这个类是映射到数据库的候选。可以指定存储数据的集合的名称。@Indexed
:应用于域上,描述如何对字段进行索引。@CompoundIndex
(可重复):应用于类上,声明复合索引。@Transient
:默认情况下,所有实例域都被映射到文档中。这个注解排除了它所应用的实例域被存储在数据库中。Transient属性不能在持久化构造函数中使用,因为转换器不能为构造函数参数具体化一个值。@Field
:应用于域上,它允许描述字段的名称和类型。@Version
:应用于域上,用于乐观锁,并在保存操作中检查是否有修改。初始值为0(原始类型为1),在每次更新时都会自动增加。
4.创建Repository接口
package com.rtxtitanv.repository;
import com.rtxtitanv.model.User;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.repository.UserRepository
* @description UserRepository用来操作用户集合,此处的集合是MongoDB中的术语
* @date 2021/5/26 18:34
*/
public interface UserRepository extends MongoRepository<User, ObjectId> {}
MongoRepository<User, ObjectId>
中的类型参数User
为实体类型,ObjectId
为主键类型。
5.创建测试类
package com.rtxtitanv;
import com.rtxtitanv.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.MongodbTest
* @description SpringDataMongoDB单元测试类
* @date 2021/5/26 18:11
*/
@SpringBootTest
class MongodbTest {
@Resource
private UserRepository userRepository;
private static Logger logger = LoggerFactory.getLogger(MongodbTest.class);
}
三、基本使用
MongoRepository
接口中常用的数据操作方法:
继承MongoRepository
接口之后就可以直接使用这些方法。在进行测试之前MongoDB的test数据库中没有Collection:
1.增加方法
保存5条测试文档:
/**
* 保存测试,这里保存5条测试文档,一次插入一条文档
* 使用方法 <S extends T> S save(S var1)
*/
@Test
void testSave() {
User user;
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
user = userRepository.save(new User().setUsername("guanyuchang123").setPassword("a123456").setRealname("关羽")
.setGender("男").setAge(28).setEmail("yunchang@xxx.com").setUserPoint(100).setUserLevel(Byte.valueOf("1"))
.setBirthday(LocalDateTime.parse("1992-10-01 17:15:20", dateTimeFormatter)));
logger.info(user.toString());
user = userRepository.save(new User().setUsername("qiaolaoda888").setPassword("ss0635gh").setRealname("大乔")
.setGender("女").setAge(20).setEmail("daqiao@xxx.com").setUserPoint(360).setUserLevel(Byte.valueOf("1"))
.setBirthday(LocalDateTime.parse("2000-12-25 13:22:32", dateTimeFormatter)));
logger.info(user.toString());
user = userRepository.save(new User().setUsername("feige45").setPassword("qwer1234aa").setRealname("张飞")
.setGender("男").setAge(25).setEmail("yide@xxx.com").setUserPoint(1000).setUserLevel(Byte.valueOf("3"))
.setBirthday(LocalDateTime.parse("1995-05-16 08:10:15", dateTimeFormatter)));
logger.info(user.toString());
user = userRepository.save(new User().setUsername("zilongzhao01").setPassword("qscrdx265").setRealname("赵云")
.setGender("男").setAge(21).setEmail("zilong@xxx.com").setUserPoint(666).setUserLevel(Byte.valueOf("2"))
.setBirthday(LocalDateTime.parse("1999-11-27 22:15:25", dateTimeFormatter)));
logger.info(user.toString());
user = userRepository.save(new User().setUsername("qiaoxiaomei886").setPassword("123wwqqs36").setRealname("小乔")
.setGender("女").setAge(18).setEmail("xiaoqiao@xxx.com").setUserPoint(2500).setUserLevel(Byte.valueOf("4"))
.setBirthday(LocalDateTime.parse("2002-09-06 12:28:33", dateTimeFormatter)));
logger.info(user.toString());
}
控制台打印的日志:
user集合已经自动创建并且成功插入5条文档:
批量保存:
/**
* 批量保存测试,这里保存5条测试文档
* 使用方法 <S extends T> List<S> saveAll(Iterable<S> var1)
*/
@Test
void testSaveAll() {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
User user1 = new User().setUsername("shangxiang23").setPassword("asrf0325ss").setRealname("孙尚香").setGender("女")
.setAge(22).setEmail("shangxiang@xxx.com").setUserPoint(10000).setUserLevel(Byte.valueOf("6"))
.setBirthday(LocalDateTime.parse("1998-03-11 21:51:10", dateTimeFormatter));
User user2 = new User().setUsername("liuxuande66").setPassword("zxcv456es").setRealname("刘备").setGender("男")
.setAge(35).setEmail("xuande@xxx.com").setUserPoint(5000).setUserLevel(Byte.valueOf("5"))
.setBirthday(LocalDateTime.parse("1985-12-25 13:22:32", dateTimeFormatter));
User user3 = new User().setUsername("diaochan321").setPassword("asoplk66").setRealname("貂蝉").setGender("女")
.setAge(22).setEmail("diaochan@xxx.com").setUserPoint(888).setUserLevel(Byte.valueOf("3"))
.setBirthday(LocalDateTime.parse("1998-06-19 07:22:36", dateTimeFormatter));
User user4 = new User().setUsername("xiahou360").setPassword("a1s2d3q6").setRealname("夏侯惇").setGender("男")
.setAge(30).setEmail("xiahoudun@xxx.com").setUserPoint(1200).setUserLevel(Byte.valueOf("3"))
.setBirthday(LocalDateTime.parse("1990-08-16 23:17:51", dateTimeFormatter));
User user5 = new User().setUsername("jiangdongyige09").setPassword("637cvxs").setRealname("孙策").setGender("男")
.setAge(25).setEmail("sunce@xxx.com").setUserPoint(3000).setUserLevel(Byte.valueOf("4"))
.setBirthday(LocalDateTime.parse("1995-12-06 11:16:45", dateTimeFormatter));
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
users.add(user3);
users.add(user4);
users.add(user5);
users = userRepository.saveAll(users);
users.forEach(user -> logger.info(user.toString()));
}
控制台打印的日志:
user集合中成功插入5条文档:
2.查询方法
查询所有文档:
/**
* 查询所有测试
* 使用方法 List<T> findAll()
*/
@Test
void testFindAll() {
List<User> users = userRepository.findAll();
users.forEach(user -> logger.info(user.toString()));
}
控制台打印的日志:
根据id查询:
/**
* 根据id查询测试
* 使用方法 Optional<T> findById(ID var1)
*/
@Test
void testFindById() {
Optional<User> optional = userRepository.findById(new ObjectId("60b9d4ce3ddb3147679f2b20"));
if (!optional.isPresent()) {
logger.info("该用户不存在");
} else {
logger.info(optional.get().toString());
}
}
控制台打印的日志:
根据id批量查询:
/**
* 根据id批量查询测试
* 使用方法 Iterable<T> findAllById(Iterable<ID> var1)
*/
@Test
void testFindAllById() {
ObjectId[] array = {new ObjectId("60b9d3db579bb97eea876e73"), new ObjectId("60b9d3dc579bb97eea876e77"),
new ObjectId("60b9d4ce3ddb3147679f2b23")};
List<ObjectId> ids = Arrays.stream(array).collect(Collectors.toList());
Iterable<User> users = userRepository.findAllById(ids);
users.forEach(user -> logger.info(user.toString()));
}
控制台打印的日志:
查询文档总数:
/**
* 查询文档总数测试
* 使用方法 long count()
*/
@Test
void testCount() {
long count = userRepository.count();
logger.info("count: " + count);
}
控制台打印的日志:
查询指定id文档是否存在:
/**
* 查询指定id文档是否存在测试
* 使用方法 boolean existsById(ID var1)
*/
@Test
void testExistsById() {
boolean exists = userRepository.existsById(new ObjectId("60b9d3dc579bb97eea876e75"));
if (exists) {
logger.info("存在");
} else {
logger.info("不存在");
}
}
控制台打印的日志:
3.删除方法
根据id删除:
/**
* 根据id删除测试
* 使用方法 void deleteById(ID var1)
*/
@Test
void testDeleteById() {
userRepository.deleteById(new ObjectId("60b9d4ce3ddb3147679f2b20"));
}
控制台打印的日志:
user集合id为60b9d4ce3ddb3147679f2b20的文档已成功删除:
批量删除:
/**
* 批量删除测试
* 使用方法 void deleteAll(Iterable<? extends T> var1)
*/
@Test
void testDeleteInBatch() {
ObjectId[] array = {new ObjectId("60b9d3db579bb97eea876e73"), new ObjectId("60b9d3dc579bb97eea876e75"),
new ObjectId("60b9d4ce3ddb3147679f2b23")};
List<ObjectId> ids = Arrays.stream(array).collect(Collectors.toList());
Iterable<User> users = userRepository.findAllById(ids);
userRepository.deleteAll(users);
}
控制台打印的日志:
user集合id为60b9d3db579bb97eea876e73、60b9d3dc579bb97eea876e75、60b9d4ce3ddb3147679f2b23的文档已成功删除:
删除单条文档:
/**
* 删除单条文档测试
* 使用方法 delete(T var1)
*/
@Test
void testDelete() {
Optional<User> optional = userRepository.findById(new ObjectId("60b9d3dc579bb97eea876e77"));
if (!optional.isPresent()) {
logger.info("该用户不存在");
} else {
userRepository.delete(optional.get());
}
}
控制台打印的日志:
user集合id为60b9d3dc579bb97eea876e77的文档已成功删除:
删除所有文档(测试之前先复制user集合):
/**
* 删除所有测试
* 使用方法 void deleteAll()
*/
@Test
void testDeleteAll() {
userRepository.deleteAll();
}
控制台打印的日志:
user集合所有文档已成功删除:
四、方法命名规则的使用
SpringDataMongoDB也支持使用方法命名规则在repository接口中声明一个方法来对MongoDB数据库进行查询。通过findBy
后面跟属性名称,方法名称遵循驼峰式命名规则,方法命名规则查询是通过解析方法名称中可以与And
和Or
连接的约束条件而得出的。查询方法所支持的关键字也可以与delete...By
或remove...By
结合使用,创建删除匹配文档的查询。
1.查询方法
按性别和年龄查询。在UserRepository
接口中添加自定义方法findByGenderAndAge
:
/**
* 方法命名规则之按性别和年龄查询
*
* @param gender 性别
* @param age 年龄
* @return 查询结果集
*/
List<User> findByGenderAndAge(String gender, Integer age);
测试方法:
/**
* 方法命名规则测试之按性别和年龄查询
*/
@Test
void testFindByGenderAndAge() {
List<User> users = userRepository.findByGenderAndAge("男", 25);
users.forEach(user -> logger.info(user.toString()));
}
控制台打印的日志:
查询真名不等于且年龄大于且积分小于等于且id在指定集合中的文档。在UserRepository
接口中添加自定义方法findByRealnameNotAndAgeGreaterThanAndUserPointLessThanEqualAndIdIn
:
/**
* 方法命名规则之查询真名不等于且年龄大于且积分小于等于且id在指定集合中的文档
*
* @param realname 用户真实姓名
* @param age 年龄
* @param userPoint 用户积分
* @param ids id列表
* @return 查询结果集
*/
List<User> findByRealnameNotAndAgeGreaterThanAndUserPointLessThanEqualAndIdIn(String realname, Integer age,
Integer userPoint, List<ObjectId> ids);
测试方法:
/**
* 方法命名规则测试之查询真名不等于且年龄大于且积分小于等于且id在指定集合中的文档
*/
@Test
void testFindByRealnameNotAndAgeGreaterThanAndUserPointLessThanEqualAndIdIn() {
ObjectId[] array = {new ObjectId("60b9d3db579bb97eea876e73"), new ObjectId("60b9d3dc579bb97eea876e77"),
new ObjectId("60b9d4ce3ddb3147679f2b21"), new ObjectId("60b9d4ce3ddb3147679f2b23")};
List<User> users = userRepository.findByRealnameNotAndAgeGreaterThanAndUserPointLessThanEqualAndIdIn("张飞", 20,
5000, Arrays.stream(array).collect(Collectors.toList()));
users.forEach(user -> logger.info(user.toString()));
}
控制台打印的日志:
分页查询出生日期大于指定日期并且用户积分在指定开区间与按用户名模糊查询的并集。在UserRepository
接口中添加自定义方法findByBirthdayAfterAndUserPointBetweenOrUsernameLike
:
/**
* 方法命名规则之分页查询出生日期大于指定日期并且用户积分在指定开区间与按用户名模糊查询的并集
*
* @param birthday 出生日期
* @param minUserPoint 最小用户积分
* @param maxUserPoint 最大用户积分
* @param username 用于模糊查询用户名的字符串
* @param pageable 分页参数
* @return 查询结果
*/
Page<User> findByBirthdayAfterAndUserPointBetweenOrUsernameLike(LocalDateTime birthday, Integer minUserPoint,
Integer maxUserPoint, String username, Pageable pageable);
测试方法:
/**
* 方法命名规则测试之分页查询出生日期大于给定日期并且用户积分在指定开区间与按用户名模糊查询的并集
*/
@Test
void testFindByBirthdayAfterAndUserPointBetweenOrUsernameLike() {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
Sort sort = Sort.by(Sort.Direction.ASC, "age");
Pageable pageable = PageRequest.of(1, 2, sort);
Page<User> page = userRepository.findByBirthdayAfterAndUserPointBetweenOrUsernameLike(
LocalDateTime.parse("1995-05-16 00:10:15", dateTimeFormatter), 1000, 5000, "ao", pageable);
logger.info("查询到的文档总数:" + page.getTotalElements());
logger.info("总页数:" + page.getTotalPages());
List<User> users = page.getContent();
users.forEach(user -> logger.info(user.toString()));
}
控制台打印的日志:
按年龄查询单条文档。在UserRepository
接口中添加自定义方法findByAge
:
/**
* 方法命名规则之按年龄查询单条文档,如果发现多条文档,则会抛出异常
*
* @param age 年龄
* @return 查询结果
*/
User findByAge(Integer age);
测试方法:
/**
* 方法命名规则测试之按年龄查询单条文档,如果发现多条文档,则会抛出异常
*/
@Test
void testFindByAge() {
User user = userRepository.findByAge(28);
logger.info(user.toString());
}
控制台打印的日志:
如果查询结果超出一条记录会出错。将传递的age参数改为25:
统计密码以指定后缀结尾的文档总数。在UserRepository
接口中添加自定义方法countByPasswordEndingWith
:
/**
* 方法命名规则之统计密码以指定后缀结尾的文档总数
*
* @param password 用于与密码后缀匹配的字符串
* @return 密码以指定后缀结尾的文档总数
*/
Long countByPasswordEndingWith(String password);
测试方法:
/**
* 方法命名规则测试之统计密码以指定后缀结尾的文档总数
*/
@Test
void testCountByPasswordEndingWith() {
Long count = userRepository.countByPasswordEndingWith("6");
logger.info("count: " + count);
}
控制台打印的日志:
限制查询,返回按用户LV降序排序的第一条文档。在UserRepository
接口中添加自定义方法findFirstByOrderByUserLevelDesc
:
/**
* 方法命名规则之限制查询,返回按用户LV降序排序的第一条文档
*
* @return 查询结果
*/
User findFirstByOrderByUserLevelDesc();
用
First
关键字将查询限制为仅第一个结果。如果找到多个匹配项,此方法也不会引发异常。对于将结果集限制为一个实例的查询,支持将结果用Optional
进行包装。
测试方法:
/**
* 方法命名规则测试之限制查询,返回按用户LV降序排序的第一条文档
*/
@Test
void testFindFirstByOrderByUserLevelDesc() {
User user = userRepository.findFirstByOrderByUserLevelDesc();
logger.info(user.toString());
}
控制台打印的日志:
限制查询,返回按用户LV升序排序的第一条文档。在UserRepository
接口中添加自定义方法findTopByOrderByUserLevelAsc
:
/**
* 方法命名规则之限制查询,返回按用户LV升序排序的第一条文档
*
* @return 查询结果
*/
User findTopByOrderByUserLevelAsc();
first
和top
这两个关键字可以互换使用。
测试方法:
/**
* 方法命名规则测试之限制查询,返回按用户LV升序排序的第一条文档
*/
@Test
void testFindTopByOrderByUserLevelAsc() {
User user = userRepository.findTopByOrderByUserLevelAsc();
logger.info(user.toString());
}
控制台打印的日志:
限制查询,返回查询结果中的前五条文档并分页。在UserRepository
接口中添加自定义方法findFirst5ByGender
:
/**
* 方法命名规则之限制查询,返回查询结果中的前五条文档并分页
*
* @param gender 性别
* @param pageable 分页参数
* @return 查询结果
*/
Page<User> findFirst5ByGender(String gender, Pageable pageable);
可以在
top
或first
后将一个可选的数值,以指定要返回的最大结果大小。如果不加数字,则假定结果大小为1。如果分页或切片应用于限制查询(以及可用页数的计算),则会在限制性结果中应用(在限制查询结果中进行分页)。限制查询也支持Distinct
关键字返回不相同的结果。
测试方法:
/**
* 方法命名规则测试之限制查询,返回查询结果中的前五条文档并分页
*/
@Test
void testFindFirst5ByGender() {
Sort sort = Sort.by("age").descending();
Pageable pageable = PageRequest.of(0, 2, sort);
Page<User> page = userRepository.findFirst5ByGender("男", pageable);
logger.info("查询到的文档总数:" + page.getTotalElements());
logger.info("总页数:" + page.getTotalPages());
List<User> users = page.getContent();
users.forEach(user -> logger.info(user.toString()));
}
控制台打印的日志:
2.删除方法
删除按密码模糊查询出的文档。在UserRepository
接口中添加自定义方法deleteByPasswordNotLike
:
/**
* 方法命名规则之删除按密码模糊查询出的文档
*
* @param password 用于模糊查询密码的字符串
* @return 删除的结果集
*/
@Transactional(rollbackFor = Exception.class)
List<User> deleteByPasswordNotLike(String password);
测试方法:
/**
* 方法命名规则测试之删除按密码模糊查询出的文档
*/
@Test
void testDeleteByPasswordNotLike() {
List<User> users = userRepository.deleteByPasswordNotLike("3");
users.forEach(user -> logger.info(user.toString()));
}
控制台打印的日志:
user集合中password没有与3模糊匹配的文档已成功删除:
删除年龄不在指定集合中的文档。在UserRepository
接口中添加自定义方法deleteByAgeNotIn
:
/**
* 方法命名规则之删除年龄不在指定集合中的文档
*
* @param ages 年龄列表
* @return 删除文档数
*/
@Transactional(rollbackFor = Exception.class)
Long deleteByAgeNotIn(List<Integer> ages);
测试方法:
/**
* 方法命名规则测试之删除年龄不在指定集合中的文档
*/
@Test
void testDeleteByAgeNotIn() {
Integer[] array = {20, 22, 25};
Long count = userRepository.deleteByAgeNotIn(Arrays.stream(array).collect(Collectors.toList()));
logger.info("删除的记录总数:" + count);
}
控制台打印的日志:
user集合中age不为20、22、25的文档已成功删除:
删除邮箱以指定前缀开头的第一条文档。在UserRepository
接口中添加自定义方法deleteByEmailStartingWith
:
/**
* 方法命名规则之删除邮箱以指定前缀开头的第一条文档
*
* @param email 用于与邮箱前缀匹配的字符串
* @return 删除的结果
*/
@Transactional(rollbackFor = Exception.class)
Optional<User> deleteByEmailStartingWith(String email);
从Spring Data 2.0开始,返回单个聚合实例的repository的CRUD方法使用Java 8的
Optional
来表示可能没有值。Spring Data还支持在查询方法上返回以下封装类型:
com.google.common.base.Optional
scala.Option
io.vavr.control.Option
另外,查询方法也可以选择完全不使用包装类型。没有查询结果会通过返回
null
来表示。
测试方法:
/**
* 方法命名规则测试之删除邮箱以指定前缀开头的第一条文档
*/
@Test
void testDeleteByEmailStartingWith() {
Optional<User> optional = userRepository.deleteByEmailStartingWith("s");
if (!optional.isPresent()) {
logger.info("该用户不存在");
} else {
logger.info(optional.get().toString());
}
}
控制台打印的日志:
user集合中email以s开头的第一条文档已成功删除:
3.查询方法支持的关键字
Keyword | Sample | Logical result |
---|---|---|
After | findByBirthdateAfter(Date date) | {"birthdate" : {"$gt" : date}} |
GreaterThan | findByAgeGreaterThan(int age) | {"age" : {"$gt" : age}} |
GreaterThanEqual | findByAgeGreaterThanEqual(int age) | {"age" : {"$gte" : age}} |
Before | findByBirthdateBefore(Date date) | {"birthdate" : {"$lt" : date}} |
LessThan | findByAgeLessThan(int age) | {"age" : {"$lt" : age}} |
LessThanEqual | findByAgeLessThanEqual(int age) | {"age" : {"$lte" : age}} |
Between | findByAgeBetween(int from, int to) findByAgeBetween(Range<Integer> range) | {"age" : {"$gt" : from, "$lt" : to}} lower / upper bounds ($gt / $gte & $lt / $lte ) according to Range |
In | findByAgeIn(Collection ages) | {"age" : {"$in" : [ages…]}} |
NotIn | findByAgeNotIn(Collection ages) | {"age" : {"$nin" : [ages…]}} |
IsNotNull , NotNull | findByFirstnameNotNull() | {"firstname" : {"$ne" : null}} |
IsNull , Null | findByFirstnameNull() | {"firstname" : null} |
Like , StartingWith , EndingWith | findByFirstnameLike(String name) | {"firstname" : name} (name as regex) |
NotLike , IsNotLike | findByFirstnameNotLike(String name) | {"firstname" : { "$not" : name }} (name as regex) |
Containing on String | findByFirstnameContaining(String name) | {"firstname" : name} (name as regex) |
NotContaining on String | findByFirstnameNotContaining(String name) | {"firstname" : { "$not" : name}} (name as regex) |
Containing on Collection | findByAddressesContaining(Address address) | {"addresses" : { "$in" : address}} |
NotContaining on Collection | findByAddressesNotContaining(Address address) | {"addresses" : { "$not" : { "$in" : address}}} |
Regex | findByFirstnameRegex(String firstname) | {"firstname" : {"$regex" : firstname }} |
(No keyword) | findByFirstname(String name) | {"firstname" : name} |
Not | findByFirstnameNot(String name) | {"firstname" : {"$ne" : name}} |
Near | findByLocationNear(Point point) | {"location" : {"$near" : [x,y]}} |
Near | findByLocationNear(Point point, Distance max) | {"location" : {"$near" : [x,y], "$maxDistance" : max}} |
Near | findByLocationNear(Point point, Distance min, Distance max) | {"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}} |
Within | findByLocationWithin(Circle circle) | {"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}} |
Within | findByLocationWithin(Box box) | {"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}} |
IsTrue , True | findByActiveIsTrue() | {"active" : true} |
IsFalse , False | findByActiveIsFalse() | {"active" : false} |
Exists | findByLocationExists(boolean exists) | {"location" : {"$exists" : exists }} |
五、分页和排序
使用Pageable
和Sort
参数,可以动态地进行分页和排序查询。将Pageable
实例传递给查询方法,可以将分页动态添加到静态定义的查询中。排序选项也是通过Pageable
实例处理的。如果只需要排序,可以在方法中加入Sort
参数。
查询所有并排序:
/**
* 查询所有并排序测试
* 使用方法 List<T> findAll(Sort var1)
*/
@Test
void testFindAllAndSort() {
// 使用属性名称定义简单的排序表达式
Sort sort = Sort.by(Sort.Direction.DESC, "age");
List<User> users = userRepository.findAll(sort);
users.forEach(user -> logger.info(user.toString()));
}
使用
Sort
和Pageable
的API希望将非null
值传递到方法中。如果不想进行任何排序或分页,需使用Sort.unsorted()
和Pageable.unpaged()
。
控制台打印的日志:
分页查询所有并排序:
/**
* 分页查询所有并排序测试
* 使用方法 Page<T> findAll(Pageable var1)
*/
@Test
void testFindAllAndPageSort() {
// 使用类型安全API定义排序表达式
Sort.TypedSort<User> typedSort = Sort.sort(User.class);
// 使用方法引用来定义排序的属性
Sort sort = typedSort.by(User::getBirthday).ascending();
Pageable pageable = PageRequest.of(0, 3, sort);
Page<User> page = userRepository.findAll(pageable);
logger.info("查询到的文档总数:" + page.getTotalElements());
logger.info("总页数:" + page.getTotalPages());
List<User> users = page.getContent();
users.forEach(user -> logger.info(user.toString()));
}
分页查询通过返回
Page
可以获取查询到的文档总数和总页数,它通过基础设施触发一个计数查询来计算总数。也可以返回一个Slice
,一个Slice
只知道下一个Slice
是否可用。也可以返回List
,在这种情况下,构建实际的Page
实例所需的额外元数据并没有被创建,相反,它限制为只查询给定范围的实体。
控制台打印的日志:
查询用户积分大于等于指定值的文档并排序。在UserRepository
接口中添加自定义方法findByUserPointGreaterThanEqual
:
/**
* 查询用户积分大于等于指定值的文档并排序
*
* @param userPoint 用户积分
* @param sort 排序参数
* @return 查询结果集
*/
List<User> findByUserPointGreaterThanEqual(Integer userPoint, Sort sort);
测试方法:
/**
* 查询用户积分大于等于给定值的文档并排序测试
*/
@Test
void testFindByUserPointGreaterThanEqual() {
Sort sort = Sort.sort(User.class).by(User::getUserPoint).descending();
List<User> users = userRepository.findByUserPointGreaterThanEqual(3000, sort);
users.forEach(user -> logger.info(user.toString()));
}
控制台打印的日志:
六、基于MongoDB JSON的查询方法
通过将org.springframework.data.mongodb.repository.Query
注解添加到repository的查询方法中,可以指定一个MongoDB JSON查询字符串来使用,而不是让查询从方法名称中导出。
查询指定性别并且年龄小于指定值的文档,并限制映射到Java对象的字段集。在UserRepository
接口中添加自定义方法findByTheUserGenderAndAgeLessThan
:
/**
* 基于MongoDB JSON的查询方法,查询指定性别并且年龄小于指定值的文档,并限制映射到Java对象的字段集
*
* @param gender 性别
* @param age 年龄
* @return 查询结果集
*/
@Query(value = "{ 'gender' : ?0, 'age' : { '$lt' : ?1 } }", fields = "{ 'realname' : 1, 'gender' : 1, 'age' : 1 }")
List<User> findByTheUserGenderAndAgeLessThan(String gender, Integer age);
占位符让
?0
可以将方法参数的值替换到JSON查询字符串中。字符串参数值在绑定过程中会被转义,这意味着不可能通过参数添加MongoDB特定的操作符。使用fields
属性可以限制被映射到Java对象中的属性集。该例只返回User对象的realname
、gender
、age
和id
属性,其他没有被设置的属性的值为null。
测试方法:
/**
* 基于MongoDB JSON的查询方法测试,查询指定性别并且年龄小于指定值的文档,并限制映射到Java对象的字段集
*/
@Test
void testFindByTheUserGenderAndAgeLessThan() {
List<User> users = userRepository.findByTheUserGenderAndAgeLessThan("女", 25);
users.forEach(user -> logger.info(user.toString()));
}
控制台打印的日志:
查询指定用户LV的文档并按年龄降序排序。在UserRepository
接口中添加自定义方法findByTheUserLevelAndSortDescByAge
:
/**
* 基于MongoDB JSON的查询方法,查询指定用户LV的文档并按年龄降序排序
*
* @param userLevel 用户LV
* @return 查询结果集
*/
@Query(value = "{ 'user_level' : ?0 }", sort = "{ 'age' : -1 }")
List<User> findByTheUserLevelAndSortDescByAge(Byte userLevel);
通过
Query
注释进行静态排序。按照sort
属性中的说明对排序参数进行排序。
测试方法:
/**
* 基于MongoDB JSON的查询方法测试,查询指定用户LV的文档并按年龄降序排序
*/
@Test
void testFindByTheUserLevelAndSortDescByAge() {
List<User> users = userRepository.findByTheUserLevelAndSortDescByAge(Byte.valueOf("3"));
users.forEach(user -> logger.info(user.toString()));
}
控制台打印的日志:
七、用SpEL表达式进行基于JSON的查询
查询字符串和字段定义可以与SpEL表达式一起使用,在运行时创建动态查询。SpEL表达式可以提供predicate的值,并可用于通过子文档扩展predicate。
查询年龄在指定开区间中的文档。在UserRepository
接口中添加自定义方法findByQueryWithExpression
:
/**
* 用SpEL表达式进行基于JSON的查询,查询年龄在指定开区间中的文档
*
* @param age0 年龄参数1
* @param age1 年龄参数2
* @return 查询结果集
*/
@Query(value = "{ 'age' : { '$gt' : ?#{[0]}, '$lt' : ?#{[1]} } }")
List<User> findByQueryWithExpression(Integer age0, Integer age1);
表达式通过一个包含所有参数的数组来暴露方法参数。该例查询使用
[0]
和[1]
来声明age的predicate的值(相当于?0
和?1
的参数绑定)。
测试方法:
/**
* 用SpEL表达式进行基于JSON的查询测试,查询年龄在指定开区间中的文档
*/
@Test
void testFindByQueryWithExpression() {
List<User> users = userRepository.findByQueryWithExpression(22, 30);
users.forEach(user -> logger.info(user.toString()));
}
控制台打印的日志:
查询在指定用户LV集合的文档的用户名和用户真实姓名。首先使用SpringDataMongoDB提供的Projections功能,创建一个接口NameOnly
,充当一个“视图”的作用,该接口公开要读取的属性的访问器方法:
package com.rtxtitanv.projections;
/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.projections.NameOnly
* @description 将查询结果限制为只有名称属性的projections
* @date 2021/5/27 14:34
*/
public interface NameOnly {
String getUsername();
String getRealname();
}
在UserRepository
接口中添加自定义方法findNameByUserLevelIn
:
/**
* 用SpEL表达式进行基于JSON的查询,查询在指定用户LV集合的文档的用户名和用户真实姓名
*
* @param userLevels0 用户积分列表
* @return List<NameOnly> 名称列表,projections接口NameOnly只包含用户名和用户真实姓名
*/
@Query(value = "{ 'user_level' : { '$in' : ?#{[0]} } }")
List<NameOnly> findNameByUserLevelIn(List<Byte> userLevels0);
测试方法:
/**
* 用SpEL表达式进行基于JSON的查询测试,查询在指定用户LV集合的文档的用户名和用户真实姓名
*/
@Test
void testFindNameByUserLevelIn() {
Byte[] array = {Byte.valueOf("1"), Byte.valueOf("3"), Byte.valueOf("5")};
List<NameOnly> names = userRepository.findNameByUserLevelIn(Arrays.stream(array).collect(Collectors.toList()));
names.forEach(
nameOnly -> logger.info("username: " + nameOnly.getUsername() + ", realname: " + nameOnly.getRealname()));
}
控制台打印的日志:
代码示例