并发实际场景(保持余额操作的正确:数据库余额字段版)

2023-12-27 12:20

本文主要是介绍并发实际场景(保持余额操作的正确:数据库余额字段版),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

场景:

一个人在一家银行办了一个账户,银行给了 一张卡(存取款)、一本存折(存取款)、一个网银(查询余额)

卡和存储不断存款和取款,网银不断查询余额。如何保持余额的正确。

 

数据库余额表:原本想用版本号来实现的,后面弃用version字段。

DROP TABLE IF EXISTS `t_test`;
CREATE TABLE `t_test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `account` decimal(11,2) DEFAULT NULL,
  `version` int(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of t_test
-- ----------------------------
INSERT INTO `t_test` VALUES ('1', '50.00', '1');

mapper.xml文件:仔细看两个sql的写法,这里是重点,请不要在java代码中进行余额的加减操作。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.taotao.mapper.TTestMapper" ><resultMap id="BaseResultMap" type="com.taotao.pojo.TTest" ><id column="id" property="id" jdbcType="INTEGER" /><result column="account" property="account" jdbcType="DECIMAL" /><result column="version" property="version" jdbcType="INTEGER" /></resultMap><update id="updateAccountAdd" parameterType="com.taotao.pojo.TTest" >update t_testset account = account + #{newAccount,jdbcType=DECIMAL}where id = #{id,jdbcType=INTEGER}</update><update id="updateAccountSub" parameterType="com.taotao.pojo.TTest" >update t_testset account = account - #{newAccount,jdbcType=DECIMAL}where id = #{id,jdbcType=INTEGER} and account >= #{newAccount,jdbcType=DECIMAL}</update></mapper>

dao暂时不贴出:

service:请在每个方法上加入事物和synchronized。

@Service
public class TestServiceImpl implements TestService {@Autowiredprivate TTestMapper testMapper;/*** 存钱** @param money*/@Override@Transactionalpublic synchronized BigDecimal addAcount(String name, int money) throws TransactionalException {TTest tTest = testMapper.selectByPrimaryKey(1);tTest.setNewAccount(new BigDecimal(money));int i = testMapper.updateAccountAdd(tTest);if (i == 0){System.out.println("添加余额失败!余额=" + tTest.getAccount());return new BigDecimal(money);}System.out.println(name + "...存入:" + money + "..." + Thread.currentThread().getName());return selectAcount(name);}/*** 取钱** @param money*/@Override@Transactionalpublic synchronized BigDecimal subAcount(String name, int money) throws TransactionalException{TTest tTest = testMapper.selectByPrimaryKey(1);tTest.setNewAccount(new BigDecimal(money));int i = testMapper.updateAccountSub(tTest);if (i == 0){System.out.println("账户余额不足!余额=" + tTest.getAccount());return new BigDecimal(money);}System.out.println(name + "...取出:" + money + "..." + Thread.currentThread().getName());return selectAcount(name);}/*** 查询余额*/@Override@Transactionalpublic synchronized BigDecimal selectAcount(String name) throws TransactionalException{TTest tTest = testMapper.selectByPrimaryKey(1);System.out.println(name + "...余额:" + tTest.getAccount());return tTest.getAccount();}
}

controller:

@Controller
public class TestMysqlController {@Autowiredprivate TestService testService;@RequestMapping(value="/cardAddAcountMysql")@ResponseBodypublic TaotaoResult<Integer> cardAddAcount() throws TransactionalException{TaotaoResult<Integer> result = new TaotaoResult<Integer>();result.setData("+100, 余额: " + testService.addAcount("card", 100));return  result;}@RequestMapping(value="/passbookAddAcountMysql")@ResponseBodypublic TaotaoResult<Integer> passbookAddAcount() throws TransactionalException{TaotaoResult<Integer> result = new TaotaoResult<Integer>();result.setData("+100, 余额: " + testService.addAcount("存折", 100));return  result;}@RequestMapping(value="/cardSubAcountMysql")@ResponseBodypublic TaotaoResult<Integer> cardSubAcount(){TaotaoResult<Integer> result = new TaotaoResult<Integer>();result.setData("-150, 余额: " + testService.subAcount("card", 150));return  result;}@RequestMapping(value="/passbookSubAcountMysql")@ResponseBodypublic TaotaoResult<Integer> passbookSubAcount() throws TransactionalException{TaotaoResult<Integer> result = new TaotaoResult<Integer>();result.setData("-200, 余额: " + testService.subAcount("存折", 200));return  result;}@RequestMapping(value="/selectAcountMysql")@ResponseBodypublic TaotaoResult<Integer> selectAcount() throws TransactionalException {TaotaoResult<Integer> result = new TaotaoResult<Integer>();result.setData(testService.selectAcount(""));return  result;}}

执行结果:

card...余额:2850.00

card...取出:150...http-apr-8085-exec-38

card...余额:2700.00

存折...取出:200...http-apr-8085-exec-104

存折...余额:2500.00

存折...取出:200...http-apr-8085-exec-73

存折...余额:2300.00

存折...取出:200...http-apr-8085-exec-105

存折...余额:2100.00

存折...取出:200...http-apr-8085-exec-120

存折...余额:1900.00

存折...取出:200...http-apr-8085-exec-39

存折...余额:1700.00

存折...取出:200...http-apr-8085-exec-107

存折...余额:1500.00

card...取出:150...http-apr-8085-exec-108

card...余额:1350.00

card...取出:150...http-apr-8085-exec-116

card...余额:1200.00

card...取出:150...http-apr-8085-exec-117

card...余额:1050.00

存折...取出:200...http-apr-8085-exec-111

存折...余额:850.00

存折...取出:200...http-apr-8085-exec-119

存折...余额:650.00

存折...取出:200...http-apr-8085-exec-115

存折...余额:450.00

存折...取出:200...http-apr-8085-exec-123

存折...余额:250.00

存折...取出:200...http-apr-8085-exec-54

存折...余额:50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

...余额:50.00

...余额:50.00

...余额:50.00

...余额:50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

...余额:50.00

...余额:50.00

...余额:50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

...余额:50.00

...余额:50.00

...余额:50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

账户余额不足!余额=50.00

...余额:50.00

...余额:50.00

...余额:50.00

账户余额不足!余额=50.00

...余额:50.00

...余额:50.00

...余额:50.00

...余额:50.00

...余额:50.00

...余额:50.00

...余额:50.00

...余额:50.00

...余额:50.00

...余额:50.00

账户余额不足!余额=50.00

...余额:50.00

...余额:50.00

...余额:50.00

...余额:50.00

...余额:50.00

...余额:50.00

...余额:50.00

 

测试用例:

链接:https://pan.baidu.com/s/1YuH8FTu9SX4DxVYNaOL9Lg 密码:4vgr

 

这篇关于并发实际场景(保持余额操作的正确:数据库余额字段版)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/543110

相关文章

Ubuntu 24.04启用root图形登录的操作流程

《Ubuntu24.04启用root图形登录的操作流程》Ubuntu默认禁用root账户的图形与SSH登录,这是为了安全,但在某些场景你可能需要直接用root登录GNOME桌面,本文以Ubuntu2... 目录一、前言二、准备工作三、设置 root 密码四、启用图形界面 root 登录1. 修改 GDM 配

JSONArray在Java中的应用操作实例

《JSONArray在Java中的应用操作实例》JSONArray是org.json库用于处理JSON数组的类,可将Java对象(Map/List)转换为JSON格式,提供增删改查等操作,适用于前后端... 目录1. jsONArray定义与功能1.1 JSONArray概念阐释1.1.1 什么是JSONA

nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析(结合应用场景)

《nginx-t、nginx-sstop和nginx-sreload命令的详细解析(结合应用场景)》本文解析Nginx的-t、-sstop、-sreload命令,分别用于配置语法检... 以下是关于 nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析,结合实际应

SQL server数据库如何下载和安装

《SQLserver数据库如何下载和安装》本文指导如何下载安装SQLServer2022评估版及SSMS工具,涵盖安装配置、连接字符串设置、C#连接数据库方法和安全注意事项,如混合验证、参数化查... 目录第一步:打开官网下载对应文件第二步:程序安装配置第三部:安装工具SQL Server Manageme

C#连接SQL server数据库命令的基本步骤

《C#连接SQLserver数据库命令的基本步骤》文章讲解了连接SQLServer数据库的步骤,包括引入命名空间、构建连接字符串、使用SqlConnection和SqlCommand执行SQL操作,... 目录建议配合使用:如何下载和安装SQL server数据库-CSDN博客1. 引入必要的命名空间2.

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式

《Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式》本文详细介绍如何使用Java通过JDBC连接MySQL数据库,包括下载驱动、配置Eclipse环境、检测数据库连接等关键步骤,... 目录一、下载驱动包二、放jar包三、检测数据库连接JavaJava 如何使用 JDBC 连接 mys

Java操作Word文档的全面指南

《Java操作Word文档的全面指南》在Java开发中,操作Word文档是常见的业务需求,广泛应用于合同生成、报表输出、通知发布、法律文书生成、病历模板填写等场景,本文将全面介绍Java操作Word文... 目录简介段落页头与页脚页码表格图片批注文本框目录图表简介Word编程最重要的类是org.apach

MyBatis-Plus 中 nested() 与 and() 方法详解(最佳实践场景)

《MyBatis-Plus中nested()与and()方法详解(最佳实践场景)》在MyBatis-Plus的条件构造器中,nested()和and()都是用于构建复杂查询条件的关键方法,但... 目录MyBATis-Plus 中nested()与and()方法详解一、核心区别对比二、方法详解1.and()

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os