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

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

相关文章

C语言函数递归实际应用举例详解

《C语言函数递归实际应用举例详解》程序调用自身的编程技巧称为递归,递归做为一种算法在程序设计语言中广泛应用,:本文主要介绍C语言函数递归实际应用举例的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录前言一、递归的概念与思想二、递归的限制条件 三、递归的实际应用举例(一)求 n 的阶乘(二)顺序打印

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

Ubuntu中远程连接Mysql数据库的详细图文教程

《Ubuntu中远程连接Mysql数据库的详细图文教程》Ubuntu是一个以桌面应用为主的Linux发行版操作系统,这篇文章主要为大家详细介绍了Ubuntu中远程连接Mysql数据库的详细图文教程,有... 目录1、版本2、检查有没有mysql2.1 查询是否安装了Mysql包2.2 查看Mysql版本2.

Oracle数据库常见字段类型大全以及超详细解析

《Oracle数据库常见字段类型大全以及超详细解析》在Oracle数据库中查询特定表的字段个数通常需要使用SQL语句来完成,:本文主要介绍Oracle数据库常见字段类型大全以及超详细解析,文中通过... 目录前言一、字符类型(Character)1、CHAR:定长字符数据类型2、VARCHAR2:变长字符数

Win11安装PostgreSQL数据库的两种方式详细步骤

《Win11安装PostgreSQL数据库的两种方式详细步骤》PostgreSQL是备受业界青睐的关系型数据库,尤其是在地理空间和移动领域,:本文主要介绍Win11安装PostgreSQL数据库的... 目录一、exe文件安装 (推荐)下载安装包1. 选择操作系统2. 跳转到EDB(PostgreSQL 的

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Mysql表的简单操作(基本技能)

《Mysql表的简单操作(基本技能)》在数据库中,表的操作主要包括表的创建、查看、修改、删除等,了解如何操作这些表是数据库管理和开发的基本技能,本文给大家介绍Mysql表的简单操作,感兴趣的朋友一起看... 目录3.1 创建表 3.2 查看表结构3.3 修改表3.4 实践案例:修改表在数据库中,表的操作主要

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处