spring注解@Transactional 和乐观锁,悲观锁并发生成有序编号问题
需求:系统中有一个自增的合同编号,在满足并发情况下,生成的合同编号是自增的。
测试工具:Apache Jmeter
实现方法:
创建一个数据库表。编号最大值记录表
表结构类似
CREATE TABLE `project_number_record` (
`id` varchar(64) NOT NULL,
`record_year` date DEFAULT NULL COMMENT '记录年份',
`max_value` int(11) DEFAULT NULL COMMENT '年份最大编号',
`status` char(1) NOT NULL DEFAULT '0' COMMENT '状态(0正常 1删除 2停用)',
`create_by` varchar(64) NOT NULL COMMENT '创建者',
`create_date` datetime NOT NULL COMMENT '创建时间',
`update_by` varchar(64) NOT NULL COMMENT '更新者',
`update_date` datetime NOT NULL COMMENT '更新时间',
`remarks` varchar(500) DEFAULT NULL COMMENT '备注信息',
`bus_type` varchar(64) DEFAULT '' COMMENT '业务类型(合同,项目)',
`version` varchar(20) DEFAULT '0' COMMENT '并发数据控制字段,时间戳数值',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='部门项目编号表';
尝试使用过3种方法进行解决这个问题。
序号有序尝试方式:
1、使用@Transaction(readyOnly=false)+synchronized (this){}代码块的方式保证合同编号有序
2、synchronized (this){} 锁住 调用事务方法的代码
3、使用乐观锁保证合同编号有序(事务情况下执行需要考虑事务隔离级别问题)
1、使用@Transaction(readyOnly=false)+synchronized (this){}代码块的方式保证合同编号有序
遇到一个问题,在事务方法内使用同步代码块 synchronized (this){}
这种情况下,类代码如下。
@Transactional(readOnly = false)
public String generateContractNo(Contract contract) {
String uniqueOfficeCode="uniqueCode";
String uniqueOfficeName="uniqueName";
String numberStr = "0000";
ProjectNumberRecord projectNumberRecord = new ProjectNumberRecord();
projectNumberRecord.setOfficeCode(uniqueOfficeCode); //contract.getOfficeCode()
projectNumberRecord.setBusType(ProjectNumberRecord.BUS_TYPE_CONTRACT);
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
int year = calendar.get(Calendar.YEAR);
calendar.clear();
calendar.set(Calendar.YEAR, year);
projectNumberRecord.setRecordYear(calendar.getTime());//事务和同步锁同时存在导致同步锁失效
synchronized (this){
String updateTimeStamp="";
//获取当前年份的数据记录
List<ProjectNumberRecord> projectNumberRecordList = projectNumberRecordService.findList(projectNumberRecord);
ProjectNumberRecord dbProjectNumberRecord = null;
if (projectNumberRecordList!=null && projectNumberRecordList.size() >= 1) {
dbProjectNumberRecord = projectNumberRecordList.get(0);
} else {
//不存在,新增对应的数据
}
int maxValue = dbProjectNumberRecord.getMaxValue() + 1;
dbProjectNumberRecord.setMaxValue(maxValue);
numberStr = numberStr.substring(String.valueOf(maxValue).length()) + maxValue; // 在更新数据之前判断是否存在数据
if(dbProjectNumberRecord.getIsNewRecord()){
//新数据
projectNumberRecordService.insert(dbProjectNumberRecord);
}else{
// 更新最大值数据
dbProjectNumberRecord.setVersion(String.valueOf(System.currentTimeMillis()));
long updateStatus = projectNumberRecordDao.updateNumberRecord(dbProjectNumberRecord);
}
}
return numberStr;
}
测试结果,10个线程并发产生的同样的合同编号,然后数据库会生成10条相同的数据。结果不符合要求,
失败原因:
Synchronized 失效关键原因:是因为**Synchronized**锁定的是当前调用方法对象,而Spring AOP 处理事务会进行生成一个代理对象,并在代理对象执行方法前的事务开启,方法执行完的事务提交,所以说,事务的开启和提交并不是在 Synchronized 锁定的范围内。出现同步锁失效的原因是:当A(线程) 执行完getSn()方法,会进行释放同步锁,去做提交事务,但在A(线程)还没有提交完事务之前,B(线程)进行执行getSn() 方法,执行完毕之后和A(线程)一起提交事务, 这时候就会出现线程安全问题。
同步锁,锁的是代理对象,锁的对象不同,所以导致同步锁失效。
实际执行顺序线程是同时执行了。
A(线程): Spring begins transactional > 方法> Spring commits transactional
B(线程): Spring begins transactional > 方法> Spring commits transactional
原文链接:https://blog.csdn.net/prin_at/article/details/90671332
2、synchronized (this){} 锁住 调用事务方法的代码
代码如下:
@RequestMapping(value = "testGenerateContractNo")
@ResponseBody
public ReturnObject testGenerateContractNo() {
Contract contract=new Contract();
contract.setId("1241525874512580608");
logger.info("对象哈希编码:"+outSideService.hashCode());
String contractNo;
synchronized (contractService){
contractNo = outSideService.generateContractNo(contract);
} return ReturnObject.success(contractNo);
}
执行结果:10个线程并发下,生成的合同编号是有序的。可能会存在执行效率慢的问题,因为这是单线程操作。
3、使用乐观锁保证合同编号有序(事务情况下执行需要考虑事务隔离级别问题)
@Transactional(readOnly = false)
public String generateContractNo(Contract contract) {
String uniqueOfficeCode="uniqueCode";
String uniqueOfficeName="uniqueName";
String numberStr = "0000";
ProjectNumberRecord projectNumberRecord = new ProjectNumberRecord();
projectNumberRecord.setOfficeCode(uniqueOfficeCode); //contract.getOfficeCode()
projectNumberRecord.setBusType(ProjectNumberRecord.BUS_TYPE_CONTRACT);
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
int year = calendar.get(Calendar.YEAR);
calendar.clear();
calendar.set(Calendar.YEAR, year);
projectNumberRecord.setRecordYear(calendar.getTime());
//使用乐观锁,使用更新时间字段来判断数据是否被更新,如果被更新则线程休眠0.2秒
while(true){
String updateTimeStamp="";
//获取当前年份的数据记录
List<ProjectNumberRecord> projectNumberRecordList = projectNumberRecordService.findList(projectNumberRecord);
ProjectNumberRecord dbProjectNumberRecord = null;
if (projectNumberRecordList!=null && projectNumberRecordList.size() >= 1) {
dbProjectNumberRecord = projectNumberRecordList.get(0);
updateTimeStamp=dbProjectNumberRecord.getVersion();
dbProjectNumberRecord.setOldVersion(updateTimeStamp);
} else {
//不存在,新增部门对应的数据
}
int maxValue = dbProjectNumberRecord.getMaxValue() + 1;
dbProjectNumberRecord.setMaxValue(maxValue);
numberStr = numberStr.substring(String.valueOf(maxValue).length()) + maxValue; // 在更新数据之前判断是否存在数据
if(dbProjectNumberRecord.getIsNewRecord()){
//新数据
projectNumberRecordService.insert(dbProjectNumberRecord);
break;
}else{
// 更新最大值数据
dbProjectNumberRecord.setVersion(String.valueOf(System.currentTimeMillis()));
long updateStatus = projectNumberRecordDao.updateNumberRecord(dbProjectNumberRecord); if(updateStatus>0){
// 更新成功,没有其他线程更新过数据
logger.info("更新成功,没有其他线程更新过数据");
break;
}else{
logger.info("更新失败,休眠1秒");
numberStr="0000";
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
return numberStr;
}
结果:只有第一个抢占的线程才可以正常获取合同编号,其他9个线程一致在做循环显示更新失败。
原因是因为,spring事务的隔离级别默认是 Isolation.DEFAULT:为数据源的默认隔离级别。大多数的数据库隔离级别:read committed 读取提交内容,第一个线程的事务更新的这条数据,然后事务还没有提交,导致其他线程读取的version数据不正确,就一直更新失败,死循环。
当设置数据库隔离级别为:
@Transactional(readOnly = false,isolation = Isolation.READ_UNCOMMITTED)
isolation = Isolation.READ_UNCOMMITTED读事务允许其他读事务和写事务,未提交的写事务
修改完后:结果合同编号有序。
还有一种方式:去掉@Transactionl注解,乐观锁也可以正常执行
spring注解@Transactional 和乐观锁,悲观锁并发生成有序编号问题的更多相关文章
- 最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁
在Java并发场景中,会涉及到各种各样的锁如公平锁,乐观锁,悲观锁等等,这篇文章介绍各种锁的分类: 公平锁/非公平锁 可重入锁 独享锁/共享锁 乐观锁/悲观锁 分段锁 自旋锁 01.乐观锁 vs 悲观 ...
- 乐观锁悲观锁对应的JAVA代码和数据库
乐观锁悲观锁是一种思想.可以用在很多方面. 比如数据库方面.悲观锁就是for update乐观锁就是 version字段 JDK方面:悲观锁就是sync乐观锁就是原子类(内部使用CAS实现) 本质来说 ...
- Java并发 行级锁/字段锁/表级锁 乐观锁/悲观锁 共享锁/排他锁 死锁
原文地址:https://my.oschina.net/oosc/blog/1620279 前言 锁是防止在两个事务操作同一个数据源(表或行)时交互破坏数据的一种机制. 数据库采用封锁技术保证并发操作 ...
- AtomicInteger如何保证线程安全以及乐观锁/悲观锁的概念
众所周知,JDK提供了AtomicInteger保证对数字的操作是线程安全的,线程安全我首先想到了synchronized和Lock,但是这种方式又有一个名字,叫做互斥锁,一次只能有一个持有锁的线程进 ...
- Django - ORM - 事务, 乐观锁, 悲观锁
事务 概念 Transaction 事务:一个最小的不可再分的工作单元:通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元) 一个完整的业务需要批量的DML(inser ...
- Java中的锁-悲观锁、乐观锁,公平锁、非公平锁,互斥锁、读写锁
总览图 如果文中内容有错误,欢迎指出,谢谢. 悲观锁.乐观锁 悲观锁.乐观锁使用场景是针对数据库操作来说的,是一种锁机制. 悲观锁(Pessimistic Lock):顾名思义,就是很悲观,每次去拿数 ...
- Mysql锁机制--乐观锁 & 悲观锁
Mysql 系列文章主页 =============== 从 这篇 文章中,我们知道 Mysql 并发事务会引起更新丢失问题,解决办法是锁.所以本文将对锁(乐观锁.悲观锁)进行分析. 第一部分 悲观锁 ...
- 乐观锁&悲观锁
悲观&乐观,只是对数据加锁的时机与粒度. 悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这 ...
- Java并发之乐观锁悲观锁
定义 乐观锁和悲观锁这两种锁机制,是在多用户环境并发控制的两种所机制. 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作.[1]常见实现如独占锁.乐观锁:假设不会发生并发冲突,只在提交操作 ...
随机推荐
- C#类库推荐 拼多多.Net SDK,开源免费!
背景介绍 近两年拼多多的发展非常迅速,即便口碑一般,也没有网页端,奈何我们已经全面小康,6亿月收入1000以下,9亿月收入2000以下,所以因为价格原因使用拼多多的用户也越来越多了.同样的,拼多多也开 ...
- vue-element-admin改造接入后台,搭建有来商城youlai-mall前后端分离管理平台
一. 前言 本篇基于有来商城youlai-mall微服务项目搭建的后台前端管理平台,技术选型Vue+Element-UI实现前后端分离,解决方案选型vue-element-admin.希望通过本篇你可 ...
- 浅入 ABP 系列(4):事件总线
浅入 ABP 系列(4):事件总线 版权护体作者:痴者工良,微信公众号转载文章需要 <NCC开源社区>同意. 目录 浅入 ABP 系列(4):事件总线 事件总线 关于事件总线 为什么需要这 ...
- JavaScript中常用的数据输出方式解析
在js中,一般使用如下几种方式进行数据的输出: 1. 在浏览器的控制台输出 浏览器F12打开浏览器控制台(一般前端开发人员必备浏览器为谷歌浏览器,下面就以谷歌浏览器为例对控制台尽心解析): 1.1 E ...
- Node.js使用npm安装模块太慢,解决办法
转自 淘宝 npm 地址: http://npm.taobao.org/ 如何使用 有很多方法来配置npm的registry地址,下面根据不同情境列出几种比较常用的方法.以淘宝npm镜像举例: 1.临 ...
- JVM运行时数据区--Java虚拟机栈
虚拟机栈的背景 由于跨平台性的设计,java的指令都是根据栈来设计的.不同平台CPU架构不同,所以不能设计为基于寄存器的. 根据栈设计的优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样 ...
- 从CSDN博客下载的图片如何无损去水印
如果你想下载别人CSDN博客文章中很好看的图片,但却有水印 想要下载去水印的图片,可以先鼠标右击该图片,选择复制图片地址 https://img-blog.csdnimg.cn/20200916140 ...
- (数据科学学习手札95)elyra——jupyter lab平台最强插件集
本文示例文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 jupyter lab是我最喜欢的编辑器,在过往 ...
- 8.Kafka offset机制
- 栈的Java实现-分别使用数组和链表
栈是非常重要的数据结构,栈具有后进先出的特点. 在JVM内部,每个线程维护一个栈,对于每个方法调用,入栈一个元素,成为栈帧,当方法执行完成后,对应的栈帧出栈. 栈帧中,也包含一个栈,称为操作数栈. 一 ...