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

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

相关文章

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

Python位移操作和位运算的实现示例

《Python位移操作和位运算的实现示例》本文主要介绍了Python位移操作和位运算的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 位移操作1.1 左移操作 (<<)1.2 右移操作 (>>)注意事项:2. 位运算2.1

Java Spring 中 @PostConstruct 注解使用原理及常见场景

《JavaSpring中@PostConstruct注解使用原理及常见场景》在JavaSpring中,@PostConstruct注解是一个非常实用的功能,它允许开发者在Spring容器完全初... 目录一、@PostConstruct 注解概述二、@PostConstruct 注解的基本使用2.1 基本代

C语言中位操作的实际应用举例

《C语言中位操作的实际应用举例》:本文主要介绍C语言中位操作的实际应用,总结了位操作的使用场景,并指出了需要注意的问题,如可读性、平台依赖性和溢出风险,文中通过代码介绍的非常详细,需要的朋友可以参... 目录1. 嵌入式系统与硬件寄存器操作2. 网络协议解析3. 图像处理与颜色编码4. 高效处理布尔标志集合

Python ZIP文件操作技巧详解

《PythonZIP文件操作技巧详解》在数据处理和系统开发中,ZIP文件操作是开发者必须掌握的核心技能,Python标准库提供的zipfile模块以简洁的API和跨平台特性,成为处理ZIP文件的首选... 目录一、ZIP文件操作基础三板斧1.1 创建压缩包1.2 解压操作1.3 文件遍历与信息获取二、进阶技

Java中字符串转时间与时间转字符串的操作详解

《Java中字符串转时间与时间转字符串的操作详解》Java的java.time包提供了强大的日期和时间处理功能,通过DateTimeFormatter可以轻松地在日期时间对象和字符串之间进行转换,下面... 目录一、字符串转时间(一)使用预定义格式(二)自定义格式二、时间转字符串(一)使用预定义格式(二)自

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

数据库面试必备之MySQL中的乐观锁与悲观锁

《数据库面试必备之MySQL中的乐观锁与悲观锁》:本文主要介绍数据库面试必备之MySQL中乐观锁与悲观锁的相关资料,乐观锁适用于读多写少的场景,通过版本号检查避免冲突,而悲观锁适用于写多读少且对数... 目录一、引言二、乐观锁(一)原理(二)应用场景(三)示例代码三、悲观锁(一)原理(二)应用场景(三)示例