最近有一个需求, 用户信息表内有一个唯一索引用户 id, 希望在插入时如果不存在该用户的信息则插入, 否则更新, 看了网上的方法, 似乎 MongoTemplate 并没有提供这项功能 (有类似的功能但只对主键 id 有效), 所以我选择了先删除, 再插入, 由此引发了我对事务的需求

修改 MongoDB 配置

MongoDB 的事务只能在开启副本集的时候才能使用,Windows 上的 MongoDB 安装后默认是单副本,所以我们首先需要将其转换为多副本.

找到 MongoDB 的安装目录, 在 bin 目录下有一个 mongod.cfg 配置文件, 在文件中增加配置:

replication:
    replSetName: rs0

然后重启 MongoDB, 以管理员身份运行终端:

net stop mongodb

net start mongodb

此时可以连上 MongoDB, 但是无法读取其中的数据, 所以需要初始化将其配置为副本集的成员, 在 MongoDB 的命令行界面执行以下命令 (这里使用 IDEA 提供的命令行):

# 初始化
rs.initiate()

# 查看状态
rs.status()

在 SpringBoot 中开启事务

先在配置文件中增加一项自定义配置, 这是为了兼容性

spring:
    data:
        mongodb:
            #      uri: mongodb://localhost/tcm
            host: localhost
            port: 27017
            database: test
            auto-index-creation: true
            transactionEnabled: true # 自定义配置, 打开事务

写配置类, 配置事务

@Configuration
public class MongoTransactionConfiguration {

    @Bean
    @ConditionalOnProperty(name = "spring.data.mongodb.transactionEnabled", havingValue = "true")
    MongoTransactionManager mongoTransactionManager(MongoDatabaseFactory factory) {
        return new MongoTransactionManager(factory);
    }
}

然后在需要的方法上面加上 @Transactional 注解就可以开启事务啦

测试结果

@Override
@Transactional(rollbackFor = Exception.class)
public UserInfo saveUserInfo(UserInfo userInfo) {
    userInfo.setUpdateTime(DateUtil.getNowTime());

    Query q = Query.query(Criteria.where("userId").is(userInfo.getUserId()));
    UserInfo info = mongoTemplate.findOne(q, UserInfo.class);
    if (info != null) {
        userInfo.setId(info.getId());
        mongoTemplate.remove(q, UserInfo.class);
        int i = 1 / 0; // 报错测试事务效果

    }
    return mongoTemplate.save(userInfo);
}

可以看到报错以后执行事务回滚, 并没有删除原来的 userId 为 1 的数据