浅谈mybatis 乐观锁实现,解决并发问题
情景展示:
银行两操作员同时操作同一账户就是典型的例子。
比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交。最后实际账户余额为1000-50=950元,但本该为1000+100-50=1050。这就是典型的并发问题。
乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。
读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个version字段,当前值为1;而当前帐户余额字段(balance)为1000元。假设操作员A先更新完,操作员B后更新。
a、操作员A此时将其读出(version=1),并从其帐户余额中增加100(1000+100=1100)。
b、在操作员A操作的过程中,操作员B也读入此用户信息(version=1),并从其帐户余额中扣除50(1000-50=950)。
c、操作员A完成了修改工作,将数据版本号加一(version=2),连同帐户增加后余额(balance=1100),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录version更新为2。
d、操作员B完成了操作,也将版本号加一(version=2)试图向数据库提交数据(balance=950),但此时比对数据库记录版本时发现,操作员B提交的数据版本号为2,数据库记录当前版本也为2,不满足“提交版本必须大于记录当前版本才能执行更新“的乐观锁策略,因此,操作员B的提交被驳回。
这样,就避免了操作员B用基于version=1的旧数据修改的结果覆盖操作员A的操作结果的可能。
示例代码:
account建库脚本
droptableifexistsaccount_wallet; /*==============================================================*/ /*Table:account_wallet*/ /*==============================================================*/ createtableaccount_wallet ( idintnotnullcomment'用户钱包主键', user_open_idvarchar(64)comment'用户中心的用户唯一编号', user_amountdecimal(10,5), create_timedatetime, update_timedatetime, pay_passwordvarchar(64), is_openintcomment'0:代表未开启支付密码,1:代表开发支付密码', check_keyvarchar(64)comment'平台进行用户余额更改时,首先效验key值,否则无法进行用户余额更改操作', versionintcomment'基于mysql乐观锁,解决并发访问' primarykey(id) );
dao层
AccountWalletselectByOpenId(StringopenId);
intupdateAccountWallet(AccountWalletrecord);
service层
AccountWalletselectByOpenId(StringopenId);
intupdateAccountWallet(AccountWalletrecord);
serviceImpl层
publicAccountWalletselectByOpenId(StringopenId){
//TODOAuto-generatedmethodstub
returnaccountWalletMapper.selectByOpenId(openId);
}
publicintupdateAccountWallet(AccountWalletrecord){
//TODOAuto-generatedmethodstub
returnaccountWalletMapper.updateAccountWallet(record);
}
sql.xml
select fromaccount_wallet whereuser_open_id=#{openId,jdbcType=VARCHAR}
controller层
packagecom.settlement.controller;
importjava.math.BigDecimal;
importjavax.servlet.http.HttpServletRequest;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Controller;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestMethod;
importorg.springframework.web.bind.annotation.ResponseBody;
importcom.settlement.commons.base.BaseController;
importcom.settlement.model.AccountWallet;
importcom.settlement.service.AccountWalletService;
importcom.taobao.api.internal.util.StringUtils;
/**
*用户钱包Controller
*
*@authorzzg
*@date2017-02-10
*/
@Controller
@RequestMapping(value="/wallet")
publicclassWalletControllerextendsBaseController{
@Autowired
privateAccountWalletServiceaccountWalletService;
/**
*针对业务系统高并发-----修改用户钱包数据余额,采用乐观锁
*
*@return
*/
@RequestMapping(value="/walleroptimisticlock.action",method=RequestMethod.POST)
@ResponseBody
publicStringwalleroptimisticlock(HttpServletRequestrequest){
Stringresult="";
try{
StringopenId=request.getParameter("openId")==null?null
:request.getParameter("openId").trim();//用户唯一编号
StringopenType=request.getParameter("openType")==null?null
:request.getParameter("openType").trim();//1:代表增加,2:代表减少
Stringamount=request.getParameter("amount")==null?null
:request.getParameter("amount").trim();//金额
if(StringUtils.isEmpty(openId)){
return"openIdisnull";
}
if(StringUtils.isEmpty(openType)){
return"openTypeisnull";
}
if(StringUtils.isEmpty(amount)){
return"amountisnull";
}
AccountWalletwallet=accountWalletService.selectByOpenId(openId);
//用户操作金额
BigDecimalcash=BigDecimal.valueOf(Double.parseDouble(amount));
cash.doubleValue();
cash.floatValue();
if(Integer.parseInt(openType)==1){
wallet.setUserAmount(wallet.getUserAmount().add(cash));
}elseif(Integer.parseInt(openType)==2){
wallet.setUserAmount(wallet.getUserAmount().subtract(cash));
}
inttarget=accountWalletService.updateAccountWallet(wallet);
System.out.println("修改用户金额是否:"+(target==1?"成功":"失败"));
}catch(Exceptione){
result=e.getMessage();
returnresult;
}
return"success";
}
}
模拟并发访问
packagecom.settlement.concurrent;
importjava.text.SimpleDateFormat;
importjava.util.Date;
importjava.util.concurrent.CountDownLatch;
importcom.settlement.commons.utils.HttpRequest;
/**
*模拟用户的并发请求,检测用户乐观锁的性能问题
*
*@authorzzg
*@date2017-02-10
*/
publicclassConcurrentTest{
finalstaticSimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");
publicstaticvoidmain(String[]args){
CountDownLatchlatch=newCountDownLatch(1);//模拟5人并发请求,用户钱包
for(inti=0;i<5;i++){//模拟5个用户
AnalogUseranalogUser=newAnalogUser("user"+i,"58899dcd-46b0-4b16-82df-bdfd0d953bfb","1","20.024",latch);
analogUser.start();
}
latch.countDown();//计数器減一所有线程释放并发访问。
System.out.println("所有模拟请求结束at"+sdf.format(newDate()));
}
staticclassAnalogUserextendsThread{
StringworkerName;//模拟用户姓名
StringopenId;
StringopenType;
Stringamount;
CountDownLatchlatch;
publicAnalogUser(StringworkerName,StringopenId,StringopenType,Stringamount,
CountDownLatchlatch){
super();
this.workerName=workerName;
this.openId=openId;
this.openType=openType;
this.amount=amount;
this.latch=latch;
}
@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
try{
latch.await();//一直阻塞当前线程,直到计时器的值为0
}catch(InterruptedExceptione){
e.printStackTrace();
}
post();//发送post请求
}
publicvoidpost(){
Stringresult="";
System.out.println("模拟用户:"+workerName+"开始发送模拟请求at"+sdf.format(newDate()));
result=HttpRequest.sendPost("http://localhost:8080/Settlement/wallet/walleroptimisticlock.action","openId="+openId+"&openType="+openType+"&amount="+amount);
System.out.println("操作结果:"+result);
System.out.println("模拟用户:"+workerName+"模拟请求结束at"+sdf.format(newDate()));
}
}
}
补充知识:Mybatis-plus代码生成器,自用版本不带xml
packagecom.wuyd.mybatispulsdemo;
importcom.baomidou.mybatisplus.annotation.DbType;
importcom.baomidou.mybatisplus.generator.AutoGenerator;
importcom.baomidou.mybatisplus.generator.config.*;
importcom.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
/**
*@authorwuyd
*创建时间:2019/10/811:17
*/
publicclassCodeGenerator{
publicstaticvoidmain(String[]args){
AutoGeneratormpg=newAutoGenerator();
//全局配置
GlobalConfiggc=newGlobalConfig();
gc.setOutputDir(System.getProperty("user.dir")+"/src/main/java");
gc.setFileOverride(true);
//不需要ActiveRecord特性的请改为false
gc.setActiveRecord(true);
gc.setSwagger2(true);
gc.setAuthor("wuyd");
//自定义文件命名,注意%s会自动填充表实体属性
gc.setControllerName("%sController");
gc.setServiceName("%sService");
gc.setServiceImplName("%sServiceImpl");
gc.setEntityName("%sEntity");
gc.setMapperName("%sMapper");
mpg.setGlobalConfig(gc);
//数据源配置
DataSourceConfigdsc=newDataSourceConfig();
dsc.setDbType(DbType.MYSQL);
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("xxx");
dsc.setPassword("xxx");
dsc.setUrl("jdbc:mysql://xxx.xxx.xxx.xxx:3306/xxxxx?useUnicode=true&useSSL=false&characterEncoding=utf8");
mpg.setDataSource(dsc);
//策略配置
StrategyConfigstrategy=newStrategyConfig();
//此处可以修改您的表前缀
strategy.setTablePrefix(newString[]{});
//表名生成策略
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//需要生成的表
strategy.setInclude(newString[]{"knapsacks","knapsacks_kind","knapsacks_prop","knapsacks_recharge_card"});
strategy.setSuperServiceClass(null);
strategy.setSuperServiceImplClass(null);
strategy.setSuperMapperClass(null);
strategy.setControllerMappingHyphenStyle(true);
strategy.setEntityLombokModel(true);
strategy.setEntitySerialVersionUID(true);
strategy.setEntityTableFieldAnnotationEnable(true);
mpg.setStrategy(strategy);
//配置模板
TemplateConfigtemplateConfig=newTemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
//包配置
PackageConfigpc=newPackageConfig();
pc.setParent("com.wuyd.mybatispulsdemo");
pc.setController("controller");
pc.setService("service");
pc.setServiceImpl("service.impl");
pc.setMapper("mapper");
pc.setEntity("entity");
mpg.setPackageInfo(pc);
//执行生成
mpg.execute();
}
}
pom.xml
com.baomidou mybatis-plus-generator 3.2.0 com.baomidou mybatis-plus-boot-starter ${mybatis-plus-boot-starter.version} mysql mysql-connector-java ${mysql-connector.version} org.apache.velocity velocity-engine-core 2.1
参考列表
官网代码生成器部分
以上这篇浅谈mybatis乐观锁实现,解决并发问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。