MongoDB事物使用简单总结
一、 说明
工作时间繁忙,过了一个多月了好久才有时间做总结。。。。
假如有写的不对的地方请告诉我,不胜感激涕零 !
1. 开发环境
Jdk :1.8
MongoDB:4.0.3
SpringBoot 2.0+
2. 开发场景说明
提供接口供对方调用。批量处理集合对象。每秒大概达到三千到五千左右。
对象中有某些属性是唯一的(filedId),当同一个集合中有相同的field时,对某些属性更新替换,既更新时有些信息需要更新有些则不需要更新。
数据集合中有某个属性(entityType),处理时根据该属性分集合保存。
每次调用要么成功,要么失败既支持事物。
3. 其他说明
- MongoDB4.0之后才支持事物
- MongoDB 使用事物必须是复制集合。所以事先需要准备好复制集。
搭建复制集教程可以参考:https://mp.csdn.net/postedit/83860908
二、 相应知识点
1. Mongodb事物简介
Mongodb的事务是依靠 Mongodb连接的客户端 session 实现,事务执行的流程大致是 建立 session,通过 session startTransaction 启动事务,如果一系列事务都完成,那么 commitTransaction 完成事务操作,并结束当前事务 session;如果一系列事务中有任意事件失败, 那么 abortTransaction 中止事务,内部将已完成的任务回退到修改之前,并结束当前事务 session。
整个事物处理过程如下:
session = client.startSession();
session.startTransaction();
session.commitTransaction();
session.abortTransaction();
session.endSession();
2. 使用配置
- application.properties
# mongodb配置
spring.data.mongodb.uri=mongodb://192.168.101.110:27027/admin
## 库名称
spring.data.mongodb.dbname=file_info-test
- MongoDB config
@Configuration
public class MongoDBConifg {
private MongoClient client;
@Value("${spring.data.mongodb.uri}")
private String mongodbURI;
@Value("${spring.data.mongodb.dbname}")
private String databaseName;
private MongoDatabase mongoDatabase;
// 设置MongoDB的一些配置
@PostConstruct
public void initMongoDB() {
getLogger("org.mongodb.driver").setLevel(Level.SEVERE);
CodecRegistry codecRegistry = fromRegistries(MongoClient.getDefaultCodecRegistry(), fromProviders(
PojoCodecProvider.builder().register("com.mongodb.models").build()));
CodecRegistry pojoCodecRegistry = fromRegistries(MongoClient.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder().automatic(true).build()));
MongoClientOptions.Builder options = new MongoClientOptions.Builder().codecRegistry(pojoCodecRegistry);
MongoClientURI uri = new MongoClientURI(mongodbURI, options);
client = new MongoClient(uri);
mongoDatabase = client.getDatabase(databaseName);
mongoDatabase.withCodecRegistry(codecRegistry);
}
public MongoClient getClient() {
return client;
}
public MongoDatabase getMongoDatabase() {
return mongoDatabase;
}
- 持久层使用 (ps : 为展示,都写在一个版面,在实际开发过程中不应该这么写。。。)
本次使用updateOneModel 这个WriteModel有其他五种不同的Model实际开发过程中根据不用的业务场景选择选用。
@Autowired
private MongoDBConifg mongoDBConifg;
public boolean bathInsertLayoutFile(List<LayoutFile> layoutFiles) throws MongoCommandException {
MongoClient client = mongoDBConifg.getClient();
ClientSession session = client.startSession();
try {
session.startTransaction(TransactionOptions.builder().writeConcern(WriteConcern.MAJORITY).build());
Map<String, List<LayoutFile>> fileList = layoutFiles.stream().collect(Collectors.groupingBy(LayoutFile::getEntityType));
for (String layoutFile : fileList.keySet()) {
List<LayoutFile> temp = fileList.get(layoutFile);
MongoCollection<LayoutFile> collection = mongoDBConifg.getMongoDatabase().getCollection(layoutFileUtils.getCollectionName(layoutFile), LayoutFile.class);
repository.insetLayoutFileList(session, collection, temp); // 入库动作
}
session.commitTransaction();
} catch (MongoCommandException e) {
session.abortTransaction();
log.error("rollback transaction , insert failure error message {}", e.getMessage(), e);
return false;
} catch (Exception e) {
log.error(" insert failure, error message {}", e.getMessage(), e);
session.abortTransaction();
return false;
} finally {
session.close();
}
return true;
}
public com.mongodb.bulk.BulkWriteResult insetLayoutFileList(ClientSession session, MongoCollection<LayoutFile> collection, List<LayoutFile> layoutFiles) {
com.mongodb.bulk.BulkWriteResult bulkWriteResult = null;
UpdateOptions updateOptions = new UpdateOptions().upsert(true);
List<UpdateOneModel<LayoutFile>> updates = new ArrayList<UpdateOneModel<LayoutFile>>();
// 构建数据
for (LayoutFile layoutFile : layoutFiles) {
Bson filter = new Document("fileId", layoutFile.getFileId()); // 根据fileId ,若是数据存在则更新,不存在插入
if (log.isDebugEnabled()) {
log.info("filedId:" + layoutFile.getFileId());
}
UpdateOneModel<LayoutFile> updateOneModel = new UpdateOneModel(filter, convertToBson(layoutFile), updateOptions);
updates.add(updateOneModel);
}
try {
bulkWriteResult = collection.bulkWrite(session, updates, new BulkWriteOptions().ordered(false));
} catch (Exception e) {
log.error("insetLayoutFileList error message {}", e.getMessage(), e);
throw e;
}
return bulkWriteResult;
}
3. 遇到的问题以及处理过程
- 调试(debug)时提示session不存在
原因是:MongoDB默认回话时间为一分钟,当我们进行业务处理时,超过一分钟之后当前session被回收所以报错。
处理方式: 1) 简化处理逻辑尽可能提升处理速度,在session过期时间内处理完所有的事情。
2) 设置session 过去时间、可以考虑写在MongoDB启动实例配置文件。
ps : 一般开启MongoDB事物动作是在持久层做。不应该在开启MongoDB事物中业务处理。这样就可以不用担心session被回收而且也符合代码规范。假如上述两种方式都没能解决,可以往数据量太大,或者MongoDB提供计算能力方向考虑。。。酌情考虑做优化。 - 保存数据时提示:invalid Bson field name **
既就是Java实体对象转换成Bson 之后别识别出来。或者Bson 类型转换不正确。
处理方式((layoutFile:具体的某个实体对象)):
(1)这种情况下适用于插入:
String json = JSON.toJSONString(layoutFile);
Document document = Document.parse(json);
(2)这种情况下适用于update
// 将对象转换,从新构造一个新的document
private Document convertToBson(LayoutFile layoutFile) {
Document newdoc = new Document();
newdoc.put("status", layoutFile.getStatus());
newdoc.put("entityType", layoutFile.getEntityType());
newdoc.put("updatetime", layoutFile.getUpdatetime());
newdoc.put("dt", layoutFile.getDt());
return new Document("$set", newdoc).
append("$setOnInsert", new Document("createtime", layoutFile.getCreatetime())). // 在第一次插入是赋值。
append("$currentDate", new BasicDBObject("updateTime", true)); // 每次修改一条记录时会更新
}
-
在复制集合中,不能自动的创建集合,并且在给多个集合创建多个信息(如唯一索引)时,需要初始化集合,编写初始化脚本(一个文件)。执行插入数据是报错(第二次插入数据时)。报错信息大体如下: ** 具体某个集合DuplicateKey
当时处理方法:也不知道什么原因。处理方式就是先执行创建集合的脚本。确保集合创建完成之后再进行创建索引信息。
五、 其他
待后续补充完整。。。