Springboot 集成 MongoDB (使用事务 | 读写分离)

Springboot 集成 MongoDB (使用事务 | 读写分离)

事务个读写分离都需要副本集模式才能使用,副本集搭建参考我的文章:Linux 之 MongoDB副本集安装与配置

源码:

gitee: https://gitee.com/shanlingshi/springboot-series/tree/master/springboot-mongodb
github: https://github.com/shanlingshi/springboot-series/tree/master/springboot-mongodb

配置mongodb数据源 application.properties

spring.data.mongodb.uri=mongodb://[username:password@]host1[:port1][,host2[:port2]/
# 开启读写分离后不支持事务 slaveOk=true
# spring.data.mongodb.uri=mongodb://[username:password@]host1[:port1][,host2[:port2]/?slaveOk=true&replicaSet=rs&write=1&readPreference=secondaryPreferred
spring.data.mongodb.database=mongo-transactional
spring.data.mongodb.authentication-database=admin

标准 URI 连接语法

mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]
[username]:[password]@
可选项,如果设置,在连接数据库服务器之后,驱动都会尝试登录这个数据库
[host]
必须的指定至少一个host, host1 是这个URI唯一要填写的。它指定了要连接服务器的地址。如果要连接复制集,请指定多个主机地址
[port]
可选的指定端口,如果不填,默认为27017

options

replicaSet=name 选项
指定replicaSet=name后则为replicaSet模式,并指定replicaSet名称
slaveOk 选项
replicaSet模式下,驱动会发送所有的写请求到主并且把读取操作分布在其他从服务器
readPreference 选项
(1) primary:只主(默认模式)。只从primary节点读数据。

(2) primaryPreferred:先主后从。优先从primary读取,primary不可用时从secondary读。

(3) secondary:只从。只从副本集中secondary节点读数据。

(4) secondaryPreferred:先从后主。优先从secondary读取,如果secondary不可用时就从primary读。

(5) nearest:就近。根据网络距离,就近读取,根据客户端与服务端的PingTime是实现。

配置事务管理器

/**
 * @author shanlingshi
 * @since 2021-09-03
 */
@Configuration
public class MongoDBConfiguration {

    @Bean
    public MongoTransactionManager transactionManager(MongoDatabaseFactory mongoDatabaseFactory) {
        return new MongoTransactionManager(mongoDatabaseFactory);
    }

}

启动类

/**
 * @author shanlingshi
 * @since 2022-08-30
 */
@EnableMongoAuditing
@EnableTransactionManagement
@SpringBootApplication(scanBasePackages = "com.springboot.mongodb")
public class MongoTransactionalApplication {

    public static void main(String[] args) {

        SpringApplication.run(MongoTransactionalApplication.class, args);
    }

}

@EnableMongoAuditing

启用审计功能,为了使用 @CreateDate | @LastModifiedDate

/**
 * @author shanlingshi
 * @since 2021-09-03
 */
@Getter
@Setter
@Document("bank_account")
public class BankAccount implements Serializable {

    private static final long serialVersionUID = 7665190409172218778L;
    @Id
    private String id;

    @Version
    private Integer version;

    private String account;

    private String username;

    private BigDecimal surplusAmount;

    @CreatedDate
    private LocalDateTime createTime;

    @LastModifiedDate
    private LocalDateTime updateTime;

}

@EnableTransactionManagement

启用事务

MongoTemplate

/**
 * @author shanlingshi
 * @since 2021-09-03
 */
@Component
public class BankAccountService {

    @Resource
    private MongoTemplate mongoTemplate;

    public void init(BankAccount bankAccount) {

        mongoTemplate.insert(bankAccount);
    }

    public BankAccount oneByAccount(String account) {
        Criteria criteria = new Criteria();
        criteria.and("account").is(account);

        return mongoTemplate.findOne(new Query(criteria), BankAccount.class);
    }

    public void update(BankAccount bankAccount) {

        mongoTemplate.save(bankAccount);
    }

}

事务核心操作类

/**
 * @author shanlingshi
 * @since 2021-09-03
 */
@Component
public class BankAccountCore {

    @Resource
    private BankAccountService bankAccountService;

    @Transactional
    public void create(CreateAccount createAccount) {
        BankAccount bankAccount = new BankAccount();
        bankAccount.setAccount(createAccount.getAccount());
        bankAccount.setUsername(createAccount.getUsername());
        bankAccount.setSurplusAmount(createAccount.getSurplusAmount());

        bankAccountService.init(bankAccount);
    }

    @Transactional
    public void transfer(String sourceAccount, String targetAccount, BigDecimal amount) {
        BankAccount source = bankAccountService.oneByAccount(sourceAccount);
        BankAccount target = bankAccountService.oneByAccount(targetAccount);

        if (source.getSurplusAmount().compareTo(amount) < 0) {
            throw new RuntimeException("Sorry, your credit is running low");
        }

        source.setSurplusAmount(source.getSurplusAmount().subtract(amount).setScale(4, RoundingMode.CEILING));
        bankAccountService.update(source);
        // 打开注释测试事务
//        if (1 == 1) {
//            throw new RuntimeException("Bank account transfer failed");
//        }

        target.setSurplusAmount(target.getSurplusAmount().add(amount).setScale(4, RoundingMode.CEILING));
        bankAccountService.update(target);
        // 打开注释测试事务
//        if (1 == 1) {
//            throw new RuntimeException("Bank account transfer failed");
//        }
    }

}

SpringBootTest

/**
 * 测试
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MongoTransactionalApplication.class)
public class ApplicationTests {

    @Resource
    private BankAccountCore bankAccountCore;

    /**
     * 测试增删改查
     */
    @Test
    public void create() {
        CreateAccount zhangsan = new CreateAccount();
        zhangsan.setAccount("6222021807379524377");
        zhangsan.setUsername("张三");
        zhangsan.setSurplusAmount(BigDecimal.valueOf(1000).setScale(4, RoundingMode.CEILING));
        bankAccountCore.create(zhangsan);

        CreateAccount lisi = new CreateAccount();
        lisi.setAccount("6222024161091314169");
        lisi.setUsername("李四");
        bankAccountCore.create(lisi);
    }

    /**
     * 测试增删改查
     */
    @Test
    public void transfer() {
        // 张三
        String sourceAccount = "6222021807379524377";
        // 李四
        String targetAccount = "6222024161091314169";
        // 转账金额
        BigDecimal transferAmount = BigDecimal.valueOf(50).setScale(4, RoundingMode.CEILING);

        bankAccountCore.transfer(sourceAccount, targetAccount, transferAmount);
    }

}

数据结果,执行两次 ApplicationTests # transfer() 的结果

在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值