Table of contents
error code
@Component
@Slf4j
public class PropertyNameStatManagerImpl implements PropertyNameStatManager {
// 批量保存
@Override
@Transactional(rollbackFor = Exception.class)
public void batchSavePropertyStat(List<PropertyStatDto> dtos) {
……
// 批量保存属性值和属性名称
for (PropertyStatDto dto : dtoList) {
// 位置1
this.save(dto);
}
}
// 批量删除
@Override
@Transactional(rollbackFor = Exception.class)
public void batchRemovePropertyStat(List<PropertyStatDto> dtos) {
……
// 批量删除属性值和属性名称
for (PropertyStatDto dto : dtoList) {
// 位置2
this.remove(dto);
propertyValueStatManager.remove(dto);
}
}
// 单个保存
@Override
public synchronized void save(PropertyStatDto dto) {
saveOrRemove(dto, 1);
}
// 单个删除
@Override
public synchronized void remove(PropertyStatDto dto) {
saveOrRemove(dto, -1);
}
/**
* 保存 / 删除 统计信息
*
* @param dto 统计信息
* @param changeNum 改变数量
* @author 明快de玄米61
* @date 2022/11/21 12:06
**/
private void saveOrRemove(PropertyStatDto dto, Integer changeNum) {
// 获取最新统计结果
PropertyNameStat stat = null;
List<PropertyNameStat> stats = service.list(Wrappers.<PropertyNameStat>lambdaQuery().eq(PropertyNameStat::getPropertyId, dto.getPropertyId()));
if (stats.isEmpty()) {
PropertyNameStat entity = insertPropertyNameStat(dto.getPropertyId(), dto.getPropertyName());
if (entity != null) {
stat = entity;
}
} else {
stat = stats.get(0);
}
// 判断统计结果
if (stat == null) {
String ops = changeNum > 0 ? "添加" : "删除";
log.error("》》》%s属性名称统计结果失败,参数:%s", ops, JSON.toJSONString(dto));
return;
}
// 位置3
// 更新统计结果
UpdateWrapper<PropertyNameStat> wrapper = new UpdateWrapper<>();
wrapper.eq(PropertyNameStat.PROPERTY_ID, dto.getPropertyId());
String operator = changeNum > 0 ? "+" : "";
wrapper.setSql(PropertyNameStat.NUM + "=" + PropertyNameStat.NUM + operator + changeNum);
service.update(null, wrapper);
}
/**
* 保存属性名称统计对象
*
* @param propertyId 属性id
* @param propertyName 属性名称
* @return 属性名称统计对象
* @author 明快de玄米61
* @date 2022/11/21 14:05
**/
private PropertyNameStat insertPropertyNameStat(String propertyId, String propertyName) {
// 初始化
PropertyNameStat entity = new PropertyNameStat();
entity.setPropertyId(propertyId);
entity.setPropertyName(propertyName);
entity.setDataEnable(EnableStatusEnum.ENABLE.getCode());
entity.setDataDelete(DeleteStatusEnum.NOT_DELETE.getCode());
entity.setNum(0L);
// 添加
boolean save = service.save(entity);
return save ? entity : null;
}
}
analyze
If user A executes batchSavePropertyStat()
the method, and then executes this.save()
the method at position 1, since save()
there are saveOrRemove()
methods in the method, I used update
a statement in this method, and because I have not added an index for the fields behind update
the statement , this will trigger the lock table. For details, please where
Look: Is the database update statement a row lock or a table lock? , since the transaction is not over yet, then this.save()
end, ready to be executed at position 1this.save()
At this time, user B executes batchSavePropertyStat()
the method, and then executes this.save()
the method at position 1. Since save()
the method has a synchronized lock, user A has to wait. At this time, user B executes this.save()
the method. Since save()
there are saveOrRemove()
methods in the method, I used update
the statement in this method , since I have not added indexes for the fields behind update
the statement where
, the execution of the update method will acquire the table lock first, but at this time the transaction controlled by user A has not been committed, thus forming a table deadlock