1、 背景
需要根据一些规则来生成自增编号,比如:95JS0001,950002
95JS
是固定的,而后缀的0001的长度也是可配置的,因为有一张表来进行维护
CREATE TABLE `number_control` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`number_category` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '编号类别',
`number_type` char(2) COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '0为微机编号,1为人工编号',
`is_department_number` char(2) COLLATE utf8mb4_general_ci DEFAULT '1' COMMENT '是否开启单位号,默认开启',
`project_num` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '项目号',
`is_year` char(2) COLLATE utf8mb4_general_ci DEFAULT '1' COMMENT '是否开启年份,默认开启',
`is_month` char(2) COLLATE utf8mb4_general_ci DEFAULT '1' COMMENT '是否开启月份,默认开启',
`divide_sign` char(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '分隔符号',
`digit` int DEFAULT '1' COMMENT '位数,默认为1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2181 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='编号管理表';
为了方便编号的查询,于是又维护另一张表来存储生成编号的进度
CREATE TABLE `generate_number` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`prefix` varchar(100) DEFAULT NULL COMMENT '前缀,根据编号规则生成的前缀',
`number` bigint DEFAULT '1' COMMENT '记录生成的号码',
`number_category` char(2) DEFAULT NULL COMMENT '编号类别',
`digit` int DEFAULT NULL COMMENT '位数'
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='记录编号生成表';
因为要求编号必须连续且自增,所以必然要使用到锁,这里使用的是ReentrantLock,因为涉及到多张表的查看及更新,也用到了事务,因为锁和事务,遇到了一些问题
2、解决
先说解决方案,遇到的问题放在第三点,想看的可以看看
首先参考这篇章
https://blog.csdn.net/zzhongcy/article/details/103583008
写了一个切面,但要注意的是必须要加@order
注解,否则锁不住。
另外要把切面的catch去掉,将异常抛出,否则事务会失效
@Component
@Aspect
@Order(1)
public class LockAspect {
private final Lock lock = new ReentrantLock();
@Pointcut("@annotation(Servicelock)")
public void lockAspect() {}
@Around("lockAspect()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
lock.lock();
Object obj;
//catch会导致事务失效,所以抛出异常
try {
obj = joinPoint.proceed();
}
finally {
lock.unlock();
}
return obj;
}
}
3、思路及解决步骤
3.1、首先我想的是直接写一个工具类,让其他人调用生成编号的
public String getNumber(Long reportId, String category) {
NumberControl numberControl = numberControlManage.selectByReportIdAndNumberCategory(reportId, category);
if (numberControl != null) {
//人工编号返回null
if (ConstantPool.ONE.toString().equals(numberControl.getNumberType())) {
return null;
}
Integer digit = numberControl.getDigit();
String predix = generatePrefix(numberControl);
lock.lock();
try {
//去生成编号中查询一下
GenerateNumber generateNumber = generateNumberManage.getByPrefixAndCategory(predix, category, digit);
//说明没有
if (generateNumber == null) {
generateNumber = new GenerateNumber();
generateNumber.setPrefix(predix);
generateNumber.setNumberCategory(category);
generateNumber.setDigit(digit);
generateNumberManage.save(generateNumber);
return predix + formatNumber(digit, 1);
} else {
Long number = generateNumber.getNumber() + 1;
generateNumber.setNumber(number);
generateNumberManage.updateById(generateNumber);
return predix + formatNumber(digit, number.intValue());
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
lock.unlock();
}
}
return null;
}
这里直接使用ReentrantLock锁住,但是当上层方法加上事务的时候,会导致锁失效。
简单来说就是:事务还未提交的时候,锁已经释放了
@Transactional(rollbackFor = Exception.class)
public R<Boolean> copy(EntrustCopyDto dto) {
EntrustSamples byId = entrustSamplesManage.getById(dto.getId());
//将id置为空
byId.setId(null);
String number;
if (ConstantPool.YES.toString().equals(dto.getPreEntrust())) {
//这里走完锁就会被释放掉,而事务还未被提交
number = numberControlService.getNumber(byId.getCheckProjectId(), NumberCategoryEnum.PREENTRUST.getCode());
} else {
number = numberControlService.getNumber(byId.getCheckProjectId(), NumberCategoryEnum.ENTRUST.getCode());
}
byId.setCommissionNumber(number);
return R.ok(entrustSamplesManage.save(byId));
}
3.2、发现上面这样写不行,改成切面方式
https://blog.csdn.net/zzhongcy/article/details/103583008
当然也有其他方式,可参考
https://blog.csdn.net/small_love/article/details/127263276
按理说,这样应该可以了,但是发现还是锁不住
3.3、添加@order
因为看文章提到了@order,抱着死马当活马医的心态加上去了,结果竟然好了。不信邪,又去掉@order压测,加上@order压测尝试了好几遍,发现加上去之后确实可以。
于是之后就开始查找原因,之前看文章说@Transactional
的默认优先级就是Integer.MAX_VALUE
,数字越大优先级越低,想着默认的怎么也不至于比它优先级还低吧。。。。
结果还真是一样低。
首先进去@order注解,发现是通过这个类来进行比较优先级别的
然后进去该类,翻译
大体就是这样了,折腾了很久。。。。