乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(版本)记录机制实现。数据增加一个版本标识,在基本数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。
读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个版本 字段,当前值为1;而当前帐户余额字段(余额)为1000元。假设操作员A先更新完,操作员B后更新。a
,操作员A此时将其出读(版本= 1),并从其帐户余额中增加100(1000 + 100 = 1100)
。b,在操作员A操作的过程中,操作员B也可以读入此用户信息(版本= 1),并从其帐户余额中扣除50 (1000-50 = 950)
。c,操作员A完成了修改工作,将数据版本号加一(版本= 2),连同帐户增加后余额(余额= 1100),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录的版本更新为2。
d,操作员乙完成了操作,也将版本号加一(版本= 2)试图向数据库提交数据(余量= 950),但此时比对数据库记录版本时发现,操作员乙提交的数据版本号为2,数据库记录当前版本也为2,不满足“提交版本必须大于记录当前版本才能执行更新”的乐观锁策略,因此,操作员B的提交被驳回。
这样,就避免了操作员乙用基于版本= 1的旧数据修改的结果覆盖操作员甲的操作结果的可能。
示例代码:
占建库脚本
1. 删除表如果存在account_wallet;
2.
3. / * ================================================ ============== * /
4. / *表:account_wallet * /
5. / * ================================================ ============== * /
6. 创建表account_wallet
7. (
8. id int not null评论'用户钱包主键',
9. user_open_id varchar(64)评论'用户中心的用户唯一编号',
10. user_amount decimal(10,5),
11. create_time datetime,
12. update_time datetime,
13. pay_password varchar(64),
14. is_open int comment'0:代表未开启支付密码,1:代表开发支付密码',
15. check_key varchar(64)comment'平台进行用户余额更改时,首先效验键值,否则无法进行用户余额更改操作',
16. 版本int评论'基于mysql乐观锁,解决并发访问'
17. 主键(id)
18. );
DAO层
1. AccountWallet selectByOpenId(String openId);
2.
3. int updateAccountWallet(AccountWallet记录);
服务层
1. AccountWallet selectByOpenId(String openId);
2.
3. int updateAccountWallet(AccountWallet记录);
serviceImpl层
1. public AccountWallet selectByOpenId(String openId){
2. // TODO自动生成的方法存根
3. return accountWalletMapper.selectByOpenId(openId);
4. }
5.
6. public int updateAccountWallet(AccountWallet record){
7. // TODO自动生成的方法存根
8. 返回accountWalletMapper.updateAccountWallet(record);
9. }
sql.xml
1. <! - 通过用户唯一编号,查询用户钱包相关的信息 - >
2. < select id = “selectByOpenId” resultMap = “BaseResultMap” parameterType = “java.lang.String” >
3. 选择
4. < include refid = “Base_Column_List” />
5. 来自account_wallet
6. 其中 user_open_id =#{openId,jdbcType = VARCHAR }
7. </ select >
8. <! - 用户钱包数据更改,通过乐观锁(版本机制)实现 - >
9. < update id = “updateAccountWallet” parameterType = “com.settlement.model.AccountWallet” >
10. <![CDATA [
11. update account_wallet set user_amount = #{userAmount,jdbcType=DECIMAL}, version = version + 1 where id =#{id,jdbcType=INTEGER} and version = #{version,jdbcType=INTEGER}
12. ]]>
13. </update>
controller 层
[html] view plain copy
1. package com.settlement.controller;
2.
3. import java.math.BigDecimal;
4. import javax.servlet.http.HttpServletRequest;
5. import org.springframework.beans.factory.annotation.Autowired;
6. import org.springframework.stereotype.Controller;
7. import org.springframework.web.bind.annotation.RequestMapping;
8. import org.springframework.web.bind.annotation.RequestMethod;
9. import org.springframework.web.bind.annotation.ResponseBody;
10. import com.settlement.commons.base.BaseController;
11. import com.settlement.model.AccountWallet;
12. import com.settlement.service.AccountWalletService;
13. import com.taobao.api.internal.util.StringUtils;
14.
15. /**
16. * 用户钱包Controller
17. *
18. * @author zzg
19. * @date 2017-02-10
20. */
21.
22. @Controller
23. @RequestMapping(value = "/wallet")
24. public class WalletController extends BaseController {
25.
26. @Autowired
27. private AccountWalletService accountWalletService;
28.
29. /**
30. * 针对业务系统高并发-----修改用户钱包数据余额,采用乐观锁
31. *
32. * @return
33. */
34. value = "/walleroptimisticlock.action", method = RequestMethod.POST)
35. @ResponseBody
36. public String walleroptimisticlock(HttpServletRequest request) {
37.
38. result = "";
39.
40. try {
41. openId = request.getParameter("openId") == null ? null
42. : request.getParameter("openId").trim(); // 用户唯一编号
43. openType = request.getParameter("openType") == null ? null
44. : request.getParameter("openType").trim(); // 1:代表增加,2:代表减少
45. amount = request.getParameter("amount") == null ? null
46. : request.getParameter("amount").trim(); // 金额
47.
48. if (StringUtils.isEmpty(openId)) {
49. return "openId is null";
50. }
51. if (StringUtils.isEmpty(openType)) {
52. return "openType is null";
53. }
54. if (StringUtils.isEmpty(amount)) {
55. return "amount is null";
56. }
57. wallet = accountWalletService.selectByOpenId(openId);
58.
59. // 用户操作金额
60. cash = BigDecimal.valueOf(Double.parseDouble(amount));
61. cash.doubleValue();
62. cash.floatValue();
63. if (Integer.parseInt(openType) == 1) {
64. wallet.setUserAmount(wallet.getUserAmount().add(cash));
65. } else if (Integer.parseInt(openType) == 2) {
66. wallet.setUserAmount(wallet.getUserAmount().subtract(cash));
67. }
68.
69. target = accountWalletService.updateAccountWallet(wallet);
70. target == 1 ? "成功" : "失败"));
71.
72. } catch (Exception e) {
73. result = e.getMessage();
74. return result;
75. }
76.
77. return "success";
78. }
79.
80. }
模拟并发访问
[html] view plain copy
1. package com.settlement.concurrent;
2.
3. import java.text.SimpleDateFormat;
4. import java.util.Date;
5. import java.util.concurrent.CountDownLatch;
6.
7. import com.settlement.commons.utils.HttpRequest;
8.
9.
10. /**
11. * 模拟用户的并发请求,检测用户乐观锁的性能问题
12. *
13. * @author zzg
14. * @date 2017-02-10
15. */
16. public class ConcurrentTest {
17. sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
18. public static void main(String[] args){
19. latch=new CountDownLatch(1);//模拟5人并发请求,用户钱包
20.
21. i=0;i<5;i++){//模拟5个用户
22. analogUser = new AnalogUser("user"+i,"58899dcd-46b0-4b16-82df-bdfd0d953bfb","1","20.024",latch);
23. analogUser.start();
24. }
25. latch.countDown();//计数器減一 所有线程释放 并发访问。
26. System.out.println("所有模拟请求结束 at "+sdf.format(new Date()));
27.
28. }
29.
30. static class AnalogUser extends Thread{
31. String workerName;//模拟用户姓名
32. String openId;
33. String openType;
34. String amount;
35. CountDownLatch latch;
36.
37. public AnalogUser(String workerName, String openId, String openType, String amount,
38. CountDownLatch latch) {
39. super();
40. this.workerName = workerName;
41. this.openId = openId;
42. this.openType = openType;
43. this.amount = amount;
44. this.latch = latch;
45. }
46.
47. @Override
48. public void run() {
49. // TODO Auto-generated method stub
50. try {
51. latch.await(); //一直阻塞当前线程,直到计时器的值为0
52. } catch (InterruptedException e) {
53. e.printStackTrace();
54. }
55. post();//发送post 请求
56.
57.
58.
59. }
60.
61. public void post(){
62. result = "";
63. System.out.println("模拟用户: "+workerName+" 开始发送模拟请求 at "+sdf.format(new Date()));
64. 导致 = HttpRequest的.sendPost( “HTTP://本地主机:8080 /结算/钱包/ walleroptimisticlock.action”, “ 的OpenID = ”+的OpenID +“ &的openType = ”+的openType +“ &量=” +量);
65. 的System.out.println( “操作结果:” +结果);
66. System.out.println(“模拟用户:”+ workerName +“模拟请求结束at”+ sdf.format(new Date()));
67.
68. }
69.
70.
71.
72.
73.
74. }
75.
76. }