SpringBoot整合MongoDB讲解
1 SpringBoot整合MongoDB
本次讲解依赖版本 :jdk17,springboot 2.6.11
1.1 环境&配置
1.1.1 pom.xml
mongo 是springboot 整合的,直接使用springboot中集成的版本了
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
1.1.2 yml配置
spring:
application:
name: mongo-demo
data:
mongodb:
# 开启自动创建 索引,如果在实体上添加了 @Index 但没有下面配置则不会创建索引
auto-index-creation: true
uri: mongodb://xxx:xxx@127.0.0.1:27017/test
# 使用下面的拆开的方式也可以
# host: 127.0.0.1
# port: 27017
# database: test
# username: xxx
# password: xxx
如果mongodb的uri中有特殊字符比如@,则可以用%40代替或者就使用拆开的配置方法
srv方式:mongodb+srv:// 是 MongoDB 官方约定的一种 URI 方案修饰符,含义一句话:让驱动先去 DNS 查 SRV 记录和 TXT 记录,自动拿到副本集所有节点地址 + 鉴权数据库、参数,再真正连接。
不加 +srv 时必须把 每个节点 IP/域名、端口、副本集名字、authSource 全写在 URI 里,加 +srv 后,可以不用写详细uri了
如果引入了 spring-boot-starter-data-mongodb,但是没有使用,则需要排序mongo的配置不然没有配置uri,项目启动有问题
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
- org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
1.2 创建实体
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document("user")
@TypeAlias("")
public class UserEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private String id;
private String name;
private String password;
@Indexed(unique = true)
private String email;
@Field("phone_num")
private String phoneNum;
}
注解讲解:
@Document:推荐写指定集合名,不写默认用类名小写(User → user)。写了更清晰,可指定 collection = "xxx"@Field:db中字段和实体中字段不匹配就需要该注解映射处理@Id:标记哪个字段对应MongoDB的_id,可以是 String、ObjectId、Long 等类型,也是Spring Data定义的通用主键注解@MongoId:用于完全自定义_id字段类型、生成类型策略等,默认不会进行类型转换,专用于MongoDB@Indexed:用于创建单字段索引,可以指定索引的各种属性,如唯一性、排序方向等,还需要配合
spring.data.mongodb.auto-index-creation: true搭配使用@CompoundIndex:用于创建复合索引(多字段索引),通常应用在类级别,定义多个字段的组合索引
1.3 相关配置
如果写入mongo时对应得表里面有 _class字段,可以这样配置处理下
@Configuration
public class MongoConfig {
@Autowired
private MongoDatabaseFactory mongoDbFactory;
@Autowired
private MongoMappingContext mongoContext;
@Bean
public MappingMongoConverter mappingMongoConverter() {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoContext);
converter.setTypeMapper(new DefaultMongoTypeMapper(null)); // 禁止写入 _class 字段
return converter;
}
}
为什么会有class字段?
Spring Data MongoDB的目标是提供一种简化MongoDB访问的方式。它通过将文档转换为Java对象来实现这一目标。为了实现文档和POJO之间的映射,Spring Data MongoDB需要知道每个文档所属的Java类。
在Java中,每个对象都有一个getClass()方法,可以返回对象所属的类。然而,在MongoDB中,文档只是一个普通的JSON对象,并没有有类的概念。为了解决这个问题,Spring Data MongoDB引入了class字段,用来存储Java类的全限定名。
1.4 实际操作
1.4.1 简单操作
@Slf4j
@Service
public class UserService {
@Autowired
private MongoTemplate mongoTemplate;
public String addUser(UserEntity userEntity){
log.info("准备保存用户实体: {}", userEntity);
UserEntity save = mongoTemplate.save(userEntity);
log.info("保存后的用户实体: {}", save);
return save.getId();
}
public UserEntity getById(String id){
log.info("根据ID查询用户: {}", id);
return mongoTemplate.findById(id, UserEntity.class);
}
// 添加一个方法来查询所有用户,用于调试
public List<UserEntity> getAllUsers(){
return mongoTemplate.findAll(UserEntity.class);
}
// 添加一个方法通过其他条件查询用户
public UserEntity getByName(String name) {
Query query = new Query(Criteria.where("name").is(name));
return mongoTemplate.findOne(query, UserEntity.class);
}
}
1.4.2 插入或更新
根据唯一索引更新,假如 email 上有唯一索引
Query query = new Query(Criteria.where("email").is(email));
Update update = new Update()
.set("name", name)
.set("age", age);
mongoTemplate.upsert(query, update, UserEntity.class);
如果实体中字段很多,可以使用 Update.fromDocument ,实体转 Document,再构造 Update:
Query query = new Query(Criteria.where("email").is(email));
Document doc = new Document();
mongoTemplate.getConverter().write(userEntity, doc);
Update update = Update.fromDocument(doc);
mongoTemplate.upsert(query, update, UserEntity.class);
使用 MongoConverter 把对象转成 Update
DBObject dbObject = new BasicDBObject();
mongoTemplate.getConverter().write(userEntity, dbObject);
Update update = new Update();
dbObject.forEach((key, value) -> {
if (!"_id".equals(key)) {
update.set(key, value);
}
});
动态构建 Update ,如果希望忽略 null 变量,只更新有值的字段:
Update update = new Update();
if (userEntity.getName() != null) {
update.set("name", userEntity.getName());
}
if (userEntity.getPassword() != null) {
update.set("password", userEntity.getPassword());
}
if (userEntity.getPhoneNum() != null) {
update.set("phoneNum", userEntity.getPhoneNum());
}
注意:永远不要让 Update 设置 _id 字段,因为_id 值一旦写入,就不允许修改且会报错
1.4.3 批量插入或更新 BulkOperations
List<UserEntity> list = ...; // 需要批量插入或更新的列表
BulkOperations ops = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, UserEntity.class);
for (UserEntity user : list) {
Query query = new Query(Criteria.where("email").is(user.getEmail()));
Document doc = new Document();
mongoTemplate.getConverter().write(user, doc);
doc.remove("_id"); // 一定要移除 _id,避免更新时报错
Document setDoc = new Document("$set", doc);
Update update = Update.fromDocument(setDoc);
ops.upsert(query, update);
}
BulkWriteResult result = ops.execute();
如果想忽略 null(不更新 null 字段),可以这样:
Document doc = new Document();
mongoTemplate.getConverter().write(user, doc);
doc.remove("_id");
doc.entrySet().removeIf(it -> it.getValue() == null);
1.4.4 批量更新和单条更新 $set封装问题
为什么批量更新需要$set封装,而单个upser不需要?
mongoTemplate.upsert(query, update, ...)在内部对Update的序列化/包装和BulkOperations把Update打包进bulk write的方式不同,所以在单个upsert时可被“容忍”的Update.fromDocument(doc)在bulk环境里会被当作非法BSON文档直接编码,进而抛出Invalid BSON field name ...
解决办法:显式把要写入的字段放到$set(或其它 update operator)里,或把doc清理成合法的BSON字段名
那么为什么会这样:
Invalid BSON field name name表示在要发送给MongoDB的某个Document中存在 不合法的字段名(例如包含.、以$开头、为null等),或者根本就没有放在$set这类operator下导致driver在编码时校验失败。mongoTemplate.upsert(...)(单条)在内部对Update的转换/包装流程不同,可能会将Update最终变成安全的{$set: {...}}或做了额外处理;而BulkOperations直接把提供的update文档作为bulk的一部分交给驱动,驱动会严格地尝试编码该Document,遇到非法字段名就报错。- 简言之:单条
upsert有宽容的转换路径,bulk写入更严格则导致Document失败

浙公网安备 33010602011771号