记一个,生产遇到的redission锁 + 事务提交,导致重复插入数据的问题
场景: 在插入表1的时候需要先查询id 是否存在。如果不存在,则插入,存在则update。
问题: 并发的时候,即便加锁了,也存在问题,因为线程1在insert结束之后还要处理其他逻辑,但是线程2在等待的时候已经去查询了数据库,
此时线程1还没有commit事务,所以线程2查询是看不到数据的,导致也insert了。 这表又不设置主键,可以重复插入。
解决:在事务提交之前不能释放锁,一定要等事务完全提交之后才可以释放锁。代码如下:
最外层方法,是在checkIsOcToPusCard(buName,posMemberQueryVo);里面去调用加锁的方法,里面逻辑太长,省略了:
@Transactional
public ResponseMessages<PosMemberQueryResVO> posMemberQuery(PosMemberQueryVo posMemberQueryVo) {
String buId = buManager.getBuId(posMemberQueryVo.getBuId());
String buName = buManager.getBuName(posMemberQueryVo.getBuId());
String subBuCode = posMemberQueryVo.getBuId();
if (buId == null) {
return new ResponseMessages<>(ResultCode.INVALID_BU_ID.getCode(),
ResultCode.INVALID_BU_ID.getMessage(), SystemConstant.SUCCESS_FALSE);
}
DistLock distLock = null;
try (ShardingCtx s = ShardingCtx.setShardingValue(buId)) {
// ACSCHK-3970
distLock = checkIsOcToPusCard(buName,posMemberQueryVo);
PosMemberQueryResVO posMemberQueryResVO = Stream.of(
executeIf(posMemberQueryVo.getIsPartnerCallFlag(), () -> {
PosMemberQueryResVO partnerMemberRes = executePosMemberQuery(posMemberQueryVo, buId);
buildSegmentAndAccount(buId, posMemberQueryVo, partnerMemberRes);
return partnerMemberRes;
}),
executeIf(posMemberQueryVo.getIsStaffCallFlag(), () ->
executePosMemberQueryByStaffCall(buId, posMemberQueryVo)
)
)
.filter(Objects::nonNull)
.filter(e -> StringUtils.hasText(e.getCardId()))
.reduce(this::combinePosMemberRes)
.orElse(null);
buildFiveLine(buName, subBuCode, posMemberQueryResVO);
return new ResponseMessages<>(ERROR_CODE_1000, SUCCESS, SUCCESS_TRUE, posMemberQueryResVO);
} catch (ApiVerifyException e) {
return new ResponseMessages<>(Integer.parseInt(e.getErrorCode()), e.getErrorMessage(),
SystemConstant.SUCCESS_FALSE);
} catch (Exception e) {
log.error(ResultCode.GENERAL_ERROR.getMessage(), e);
return new ResponseMessages<>(ResultCode.GENERAL_ERROR.getCode(), e.getMessage(),
SystemConstant.SUCCESS_FALSE);
} finally {
releaseTransactionLock(distLock);
}
}
//事务完全提交在释放锁
private void releaseTransactionLock(DistLock distLock) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCompletion(int status) {
releaseLock(distLock);
}
});
} else {
releaseLock(distLock);
}
}
private void releaseLock(DistLock distLock) {
if (distLock != null) {
distLock.unlock();
}
}
代码2:
加锁代码:
private DistLock checkIsUpdateContactSegment(MemberCard memberCard, String ocToPusNew, String ocToPusOld) {
// ACSCHK-3826
boolean locked = false;
String lockId = CONTACT_SEGMENT_UPDATE_LOCK + memberCard.getBuId() + "_" + memberCard.getVisibleCard();
DistLock distLock = distLockRegistry.getLock(lockId);
try {
locked = distLock.tryLock(contactXmUpsetLockWaitTime, contactXmLockLeaveTime, TimeUnit.SECONDS);
} catch (Exception ignored) {
}
if (!locked) {
String errorMessage = String.format("failed to lock Octopus card for jointMember MemberNumber: %s", memberCard.getVisibleCard());
log.error(errorMessage);
throw new RuntimeException(errorMessage);
}
String otpNew = String.valueOf(Integer.parseInt(ocToPusNew));
String otpOld = String.valueOf(Integer.parseInt(ocToPusOld));
Optional.of(memberCard)
.filter(card -> IS_DEFAULT_Y.equals(card.getOcToPusFlag()))
.filter(card -> Objects.equals(otpOld, card.getSegmentNum()))
.ifPresent(card -> updateSegment(otpNew, otpOld, card.getPrevConId()));
return distLock;
}
private void updateSegment(String otpNew, String otpOld, String prevConId) {
String name = PARTNER_SEGMENT + otpNew + LocalDate.now().toString().replaceAll("-", "");
CustomerLoyContactXmPO loyContactXmPO = buildLoyContactXm(otpNew, name, prevConId);
boolean numFlag = customerLoyContactXMRepository.findIsExistByIdAndTypeAndName(prevConId, name, SEGMENT_TYPE);
boolean countFlag = customerLoyContactXMRepository.findIsExistByIdAndTypeAndOctoPusCard(prevConId, SEGMENT_TYPE, PARTNER_SEGMENT, otpNew, MBAPP_OCTOPUS);
if (numFlag || countFlag) {
updateCustomerLoyContactXMByFlag(numFlag, countFlag, loyContactXmPO);
} else {
customerLoyContactXMRepository.insertLoyContactXMByOctoPus(loyContactXmPO, otpNew);
}
CustomerLoyContactXmPO loyContactXmOld = buildOldLoyContactXm(otpOld, prevConId);
customerLoyContactXMRepository.updateCustomerLoyContactXMByOctopus(loyContactXmOld, otpOld);
}
学海无涯 代码作伴