SpringBoot 整合 自定义MongoDB
1. 添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency><!-- 可选:用于 JSON 序列化/反序列化 --><dependency><groupId>org.mongodb</groupId><artifactId>mongodb-driver-legacy</artifactId><version>4.9.1</version></dependency>
2.自定义MongoDB配置
@Configuration
public class MongoConfig {/*** 自定义 MongoTemplate*/@Beanpublic MongoTemplate mongoTemplate(MongoDatabaseFactory factory, MappingMongoConverter converter) {return new MongoTemplate(factory, converter);}//配置 mongodb 事务管理器,能够让 mongodb 支持事务操作@BeanMongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {return new MongoTransactionManager(dbFactory);}/*** 自定义 MappingMongoConverter*/@Beanpublic MappingMongoConverter mappingMongoConverter(MongoDatabaseFactory factory, MongoMappingContext context, MongoCustomConversions conversions) {DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, context);// 自定义配置converter.setTypeMapper(new DefaultMongoTypeMapper(null)); // 移除 _class 字段converter.setCustomConversions(conversions); // 注册自定义转换器converter.afterPropertiesSet();return converter;}/*** 注册自定义类型转换器*/@Beanpublic MongoCustomConversions customConversions() {return new MongoCustomConversions(Arrays.asList(// 添加自定义转换器new DateToZonedDateTimeConverter(),new ZonedDateTimeToDateConverter()));}
}
// Date 转 ZonedDateTime
class DateToZonedDateTimeConverter implements Converter<Date, ZonedDateTime> {@Overridepublic ZonedDateTime convert(Date source) {return source.toInstant().atZone(ZoneId.systemDefault());}
}// ZonedDateTime 转 Date
class ZonedDateTimeToDateConverter implements Converter<ZonedDateTime, Date> {@Overridepublic Date convert(ZonedDateTime source) {return Date.from(source.toInstant());}
}
3. 环境变量配置
3.1 单机版
(1)第一种方式:
server:port: 8088
spring:data:mongodb:# 连接字符串格式# mongodb://用户名:密码@Ip地址:端口/数据库名# 如果使用的是 root 角色的用户登录,则必须在后面加上 authSource=admin 参数# 之前在 admin 库中创建了一个 root 角色的账号 jobs# 在实际项目中,强烈建议,针对每个数据库创建一个 readwrite 角色的用户uri: mongodb://root:root123456@192.168.146.132:27017/mytest?authSource=admin# 允许在实体类上,通过 @Indexed 创建单字段索引,通过 @CompoundIndex 创建多字段联合索引# 注意:这里只是演示注解的使用,实际项目中一般不推荐使用注解创建索引,# 最好通过 mongodb 的命令操作 mongodb 管理索引auto-index-creation: true
(2)第二种方式
spring.data.mongodb.host=127.0.0.1
spring.data.mongodb.port=27017
spring.data.mongodb.database=database_test
spring.data.mongodb.username=root123
spring.data.mongodb.password=root
3.2 集群模式
server:port: 8088
spring:data:mongodb:# 连接字符串格式# mongodb://用户名:密码@Ip地址:端口/数据库名# 如果使用的是 root 角色的用户登录,则必须在后面加上 authSource=admin 参数# 之前在 admin 库中创建了一个 root 角色的账号 jobs# 在实际项目中,强烈建议,针对每个数据库创建一个 readwrite 角色的用户uri: mongodb://root:root123456@192.168.146.132:27017,192.168.146.132:27018,192.168.146.132:27019/mytest?authSource=admin&replicaSet=mongos&slaveOk=true# 允许在实体类上,通过 @Indexed 创建单字段索引,通过 @CompoundIndex 创建多字段联合索引# 注意:这里只是演示注解的使用,实际项目中一般不推荐使用注解创建索引,# 最好通过 mongodb 的命令操作 mongodb 管理索引auto-index-creation: true
注意:在连接 MongoDB(或类似服务)时,你的连接字符串(connection string)中的用户名或密码包含了特殊字符(如冒号: 或 at 符号 @)。根据 URI 规范,这些特殊字符必须进行 URL 编码(urlencode),否则连接会失败。
1. 找出你用到的连接字符串
一般格式如下:
mongodb://username:password@host:port/db
如果 username
或 password
里有 :
或 @
,必须 URL 编码。
2. 如何 URL 编码
字符 | URL编码后形式 |
---|---|
: | %3A |
@ | %40 |
/ | %2F |
? | %3F |
= | %3D |
& | %26 |
# | %23 |
例子:
假设你的用户名是 user:na@me
,密码是 pass@word:123
,正常这样写会报错:
mongodb://user:na@me:pass@word:123@localhost:27017/test
应改为(正确写法):
用户名 user:na@me
变成 user%3Ana%40me
,密码 pass@word:123
变成 pass%40word%3A123
:
mongodb://user%3Ana%40me:pass%40word%3A123@localhost:27017/test
4.增删改查
4.1 创建实例类
//注意:
//这里只是演示相关注解的使用,但是不建议在实体类上通过注解去建立索引
//最好通过命令,直接操作 mongodb 的文档去建立相关索引
@Data
//使用该注解,标明要操作的 mongodb 的文档(相当于数据库的表)
@Document("tb_employee")
//建立多字段联合索引( 1 表示升序,-1 表示降序)
@CompoundIndex(def = "{'depart':1,'age':-1}")
//使用该注解,可以使用对象实例化赋值采用链式编写
@Accessors(chain = true)
public class Employee {//使用该注解,标明 mongodb 文档的主键 id@MongoIdprivate 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; //薪水
}
4.2 新增
@SpringBootTest
public class MongoTest {//注入 spring-data-mongodb 自带的 MongoTemplate 对象@Autowiredprivate MongoTemplate mongoTemplate;//添加一批员工@Testpublic void testAdd() {//其实不需要给 id 赋值,因为 mongodb 会自动生成主键 id//insert 和 save 都是添加记录,它们的区别是://如果 id 在文档记录中已经存在的情况下,save 会更新,insert 会抛异常//采用 insert 方法,主键不存在就添加,存在就抛异常Employee emp1 = new Employee().setId("001").setWorkNo("nb001").setName("任肥肥").setDepart("研发部").setAge(38).setMoney(2500);mongoTemplate.insert(emp1);//采用 save 方法,主键存在就更新,不存在就新增Employee emp4 = new Employee().setId("004").setWorkNo("nb004").setName("乔豆豆").setDepart("财务部").setAge(39).setMoney(1800);mongoTemplate.save(emp4);}/*** 批量插入*/@Testvoid m4() {List<Person> persons = new ArrayList<>();for (int i = 1; i <= 5; i++) {Person person = Person.builder().name("张三"+i).age(i).id(Long.valueOf(i)).build();persons.add(person);}mongoTemplate.insertAll(persons);}}
4.3 查找
@SpringBootTest
public class MongoTest {//注入 spring-data-mongodb 自带的 MongoTemplate 对象@Autowiredprivate MongoTemplate mongoTemplate;//查询所有员工@Testpublic void testFindAll() {List<Employee> list = mongoTemplate.findAll(Employee.class);for (Employee emp : list) {System.out.println(emp);}}//条件查询:查询【研发部】,并且年龄大于 30 岁的员工,按照年龄【倒序】排列@Testpublic 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 元,部门改为大数据部门@Testpublic void testUpdate() {Query query = Query.query(Criteria.where("age").gt(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 的员工,按照薪水【升序】排列)@Testpublic 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);}}//统计每个部门的人数@Testpublic 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);}}//统计每个部门,最高的薪水是多少@Testpublic 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 个人的信息@Testpublic 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 岁的员工@Testpublic 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);}
}
@SpringBootTest
class QueryTests {@Autowiredprivate MongoTemplate mongoTemplate;/*** 查询集合中的全部文档数据*/@Testpublic void findAll() {List<Person> personList = mongoTemplate.findAll(Person.class);System.out.println("查询结果:" + personList.toString());}/*** 查询集合中指定的ID文档数据*/@Testpublic void findById() {Person byId = mongoTemplate.findById(3L, Person.class);System.out.println("查询结果:" + byId.toString());}/*** 根据条件查询符合条件的文档数据并返回第一条数据*/@Testpublic void findOne() {Query query = new Query(Criteria.where("name").is("宋江1"));Person result = mongoTemplate.findOne(query, Person.class);System.out.println("查询结果:" + result.toString());}/*** 根据条件查询所有符合条件的文档*/@Testpublic void findByCondition() {Query query = new Query(Criteria.where("age").gt(18));List<Person> result = mongoTemplate.find(query, Person.class);System.out.println("查询结果:" + result.toString());}/*** 根据【AND】关联多个查询条件,查询集合中所有符合条件的文档数据*/@Testpublic void findByAndCondition() {// 创建条件Criteria name = Criteria.where("name").is("张三");Criteria age = Criteria.where("age").is(18);// 创建条件对象,将上面条件进行 AND 关联Criteria criteria = new Criteria().andOperator(name, age);// 创建查询对象,然后将条件对象添加到其中Query query = new Query(criteria);List<Person> result = mongoTemplate.find(query, Person.class);System.out.println("查询结果:" + result.toString());}/*** 根据【OR】关联多个查询条件,查询集合中的文档数据*/@Testpublic void findByOrCondition() {// 创建条件Criteria criteriaUserName = Criteria.where("name").is("张三");Criteria criteriaPassWord = Criteria.where("age").is(22);// 创建条件对象,将上面条件进行 OR 关联Criteria criteria = new Criteria().orOperator(criteriaUserName, criteriaPassWord);// 创建查询对象,然后将条件对象添加到其中Query query = new Query(criteria);List<Person> result = mongoTemplate.find(query, Person.class);System.out.println("查询结果:" + result.toString());}/*** 根据【IN】关联多个查询条件,查询集合中的文档数据*/@Testpublic void findByInCondition() {// 设置查询条件参数List<Long> ids = Arrays.asList(10L, 11L, 12L);// 创建条件Criteria criteria = Criteria.where("id").in(ids);// 创建查询对象,然后将条件对象添加到其中Query query = new Query(criteria);List<Person> result = mongoTemplate.find(query, Person.class);System.out.println("查询结果:" + result.toString());}/*** 根据【逻辑运算符】查询集合中的文档数据*/@Testpublic void findByOperator() {// 设置查询条件参数int min = 20;int max = 35;Criteria criteria = Criteria.where("age").gt(min).lte(max);// 创建查询对象,然后将条件对象添加到其中Query query = new Query(criteria);List<Person> result = mongoTemplate.find(query, Person.class);System.out.println("查询结果:" + result.toString());}/*** 根据【正则表达式】查询集合中的文档数据*/@Testpublic void findByRegex() {// 设置查询条件参数String regex = "^张";Criteria criteria = Criteria.where("name").regex(regex);// 创建查询对象,然后将条件对象添加到其中Query query = new Query(criteria);List<Person> result = mongoTemplate.find(query, Person.class);System.out.println("查询结果:" + result.toString());}/*** 根据条件查询集合中符合条件的文档,获取其文档列表并排序*/@Testpublic void findByConditionAndSort() {Query query = new Query(Criteria.where("name").is("张三")).with(Sort.by("age"));List<Person> result = mongoTemplate.find(query, Person.class);System.out.println("查询结果:" + result.toString());}/*** 根据单个条件查询集合中的文档数据,并按指定字段进行排序与限制指定数目*/@Testpublic void findByConditionAndSortLimit() {String userName = "张三";//从第5行开始,查询3条数据返回Query query = new Query(Criteria.where("name").is("张三")).with(Sort.by("createTime")).limit(3).skip(5);List<Person> result = mongoTemplate.find(query, Person.class);System.out.println("查询结果:" + result.toString());}/*** 统计集合中符合【查询条件】的文档【数量】*/@Testpublic void countNumber() {// 设置查询条件参数String regex = "^张*";Criteria criteria = Criteria.where("name").regex(regex);// 创建查询对象,然后将条件对象添加到其中Query query = new Query(criteria);long count = mongoTemplate.count(query, Person.class);System.out.println("统计结果:" + count);}}
4.4 删除
@SpringBootTest
class DeleteTests {@Autowiredprivate MongoTemplate mongoTemplate;/*** 删除id 为1的记录*/@Testvoid m1() {Query query = new Query(Criteria.where("id").is(1L));DeleteResult remove = mongoTemplate.remove(query, Person.class);System.out.println("删除的条数为:" + remove.getDeletedCount());//1}/*** 删除符合条件的所有文档*/@Testpublic void m2() throws Exception {//删除年龄小于18的所有人: lt 表示小于等于Query query = new Query(Criteria.where("age").lt(18));DeleteResult result = mongoTemplate.remove(query, Person.class);System.out.println("删除条数:" + result.getDeletedCount());}/*** 删除符合条件的单个文档 并返回删除的文档*/@Testpublic void m3() throws Exception {Query query = new Query(Criteria.where("id").is(2L));Person per = mongoTemplate.findAndRemove(query, Person.class);System.out.println(per);//Person(id=2, name=宋江, age=2)}/*** 删除符合条件的所有文档* 并返回删除的所有文档*/@Testpublic void m4(){// lt 表示小于Query query = new Query(Criteria.where("age").lt(3));List<Person> pers = mongoTemplate.findAllAndRemove(query, Person.class);System.out.println(pers);}
}