一、需求背景
加一段依据手机号同步更新其他自然人id字段为空的信息的逻辑。上线后遭遇死锁问题-->R-12738部分代码
@ApiOperation("APP扫码侧更新企业与自然人状态(补偿)")
@PutMapping("/updateCompanyAndPersonStatus")
public AjaxResult updateCompanyAndPersonStatus(@Validated @RequestBody AppScanAccountStatusDTO accountDTO) {
log.info("APP扫码侧更新企业与自然人状态:{}", JSON.toJSONString(accountDTO));
if (accountDTO.getQyId() == null && accountDTO.getGsId() == null) {
return new AjaxResult().fail("参数异常,公司ID和企业ID不能同时为空!");
}
Long taxUserId = null;
if (Objects.equals(accountDTO.getStatus(), StatusConstant.BYTE_VALUE_ONE)) {
Assert.notBlank(accountDTO.getCardNo(), "[自然人信息]证件号码不能为空");
Assert.notBlank(accountDTO.getCardType(), "[自然人信息]证件类型不能为空");
Assert.notBlank(accountDTO.getFullName(), "[自然人信息]姓名不能为空");
Assert.notBlank(accountDTO.getPhone(), "[自然人信息]手机号不能为空");
YzfTaxUserInfo taxUserInfo = taxUserService.queryTaxUserInfoByCardNo(accountDTO.getCardNo());
//自然人手机号和已经入库的手机号不一致
// if (Objects.nonNull(taxUserInfo) && !Objects.equals(taxUserInfo.getPhone(), accountDTO.getPhone())) {
// taxUserInfo.setPhone(accountDTO.getPhone());
// //更新自人手机号
// taxUserService.updateDataByUserInfo(taxUserInfo);
// }
boolean uptFlag = false;
if (taxUserInfo == null) {
uptFlag = true;
} else {
taxUserId = taxUserInfo.getId();
LocalDate collectDate = taxUserInfo.getUpdateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
// 上次采集距离现在是否超过7天
uptFlag = collectDate.until(LocalDate.now(), ChronoUnit.DAYS) > collectDayDiff;
}
if (uptFlag) {
BindMobileDTO mobileDTO = new BindMobileDTO();
mobileDTO.setMobile(accountDTO.getPhone());
mobileDTO.setDefaultFlag("1");
taxUserId = taxUserService.saveOrUpdateTaxUserInfo(YzfTaxUserInfo.builder()
.id(taxUserId)
.cardNo(accountDTO.getCardNo())
.isBindFull(Boolean.FALSE)
.cardType(accountDTO.getCardType())
.fullName(accountDTO.getFullName())
.phone(accountDTO.getPhone())
.mobileList(Collections.singletonList(mobileDTO))
.collectTaxId(accountDTO.getCollectTaxId())
.build());
/*// R12738,扫码账号更新自然人信息时,补偿同手机号的其他账号的taxuserid字段信息
Example accountInfoExample = new Example(YzfPersonAccountInfo.class);
accountInfoExample.createCriteria().andEqualTo("zrrSjh", accountDTO.getPhone());
log.info("[AccountController][updateCompanyAndPersonStatus] 更新其他账号的税局自然人id字段信息 TaxUserId:{}, zrrsjh:{}", taxUserId, accountDTO.getPhone());
YzfPersonAccountInfo updateRecord = new YzfPersonAccountInfo();
updateRecord.setTaxUserId(taxUserId);
updateRecord.setUpdateTime(new Date());
yzfPersonAccountInfoMapper.updateByExampleSelective(updateRecord, accountInfoExample);*/
}
}
// 保存扫码帐号及标签信息
taxAppInfoService.updateCompanyAndPersonStatus(accountDTO, taxUserId);
return new AjaxResult().success();
}
报错日志:
2025-01-17 09:27:30,834 [TID:28bab5b00d4e4f62a7f82015b12ce383.65.17370771340044927] [ERROR] [http-nio-8001-exec-29] druid.sql.Statement [Log4jFilter.java : 152] {conn-10033, pstmt-276265} execute error. UPDATE yzf_person_account_info SET id = id,tax_user_id = ?,update_time = ? WHERE ( zrr_sjh = ? )
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
at sun.reflect.GeneratedConstructorAccessor182.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.Util.getInstance(Util.java:408)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:952)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3978)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3914)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2530)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2683)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2495)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1903)
at com.mysql.jdbc.PreparedStatement.execute$original$ybXtipv4(PreparedStatement.java:1242)
at com.mysql.jdbc.PreparedStatement.execute$original$ybXtipv4$accessor$mFUus66H(PreparedStatement.java)
at com.mysql.jdbc.PreparedStatement$auxiliary$jLWImvz6.call(Unknown Source)
at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:86)
at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java)
at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3409)
at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(FilterEventAdapter.java:440)
at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3407)
at com.alibaba.druid.wall.WallFilter.preparedStatement_execute(WallFilter.java:620)
at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3407)
at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(FilterEventAdapter.java:440)
at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3407)
at com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.execute(PreparedStatementProxyImpl.java:167)
at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:498)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:46)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:74)
at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:50)
at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
at sun.reflect.GeneratedMethodAccessor538.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:63)
at com.sun.proxy.$Proxy283.update(Unknown Source)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:198)
at sun.reflect.GeneratedMethodAccessor530.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
at com.sun.proxy.$Proxy170.update(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate.java:294)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:63)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
at com.sun.proxy.$Proxy180.updateByExampleSelective(Unknown Source)
at com.yzf.accounting.acmanager.web.AccountController.updateCompanyAndPersonStatus$original$ee8JvmUV(AccountController.java:202)
at com.yzf.accounting.acmanager.web.AccountController.updateCompanyAndPersonStatus$original$ee8JvmUV$accessor$Zj4mVEhX(AccountController.java)
at com.yzf.accounting.acmanager.web.AccountController$auxiliary$Q1vTxESI.call(Unknown Source)
at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:86)
at com.yzf.accounting.acmanager.web.AccountController.updateCompanyAndPersonStatus(AccountController.java)
at com.yzf.accounting.acmanager.web.AccountController$$FastClassBySpringCGLIB$$9d8859e9.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
at com.yzf.accounting.acmanager.web.AccountController$$EnhancerBySpringCGLIB$$e3625b5e.updateCompanyAndPersonStatus(<generated>)
at sun.reflect.GeneratedMethodAccessor1016.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:891)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
at org.springframework.web.servlet.FrameworkServlet.doPut(FrameworkServlet.java:888)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:664)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.yzf.accounting.acmanager.filter.ApiAccessFilter.doFilter(ApiAccessFilter.java:31)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:90)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:155)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:123)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)
at org.apache.catalina.core.StandardHostValve.invoke$original$ilgrqbua(StandardHostValve.java:140)
at org.apache.catalina.core.StandardHostValve.invoke$original$ilgrqbua$accessor$ZlGhu7OP(StandardHostValve.java)
at org.apache.catalina.core.StandardHostValve$auxiliary$xMTTKQ9W.call(Unknown Source)
at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:86)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
二、原因分析
yzf_tax_user_info表发生超时等待,yzf_person_account_info表发生死锁;根据自然人手机号批量更新person_account_info表中的信息会导致大批行锁,即使zrr_sjh字段有索引,在高并发的情况下仍会与其他操作锁冲突,而批量更新(按手机号) + 其他地方的精确更新(按ID) + 可能的关联表更新,导致了不一致的加锁顺序,引发死锁。
三、解决措施
避免在同一业务/同一事务内既有大范围批量更新,又有单行精确更新;或尽量保证它们以相同顺序更新相同表。
那么这部分更新异步去实现即可,一开始想到使用异步线程,但是在同一时间内还是有死锁的风险。于是想到了使用死信队列延迟转发。
四、方案
1、异步任务单独执行;
2、先基于“手机号”等查询条件检索出所有主键 ID;之后按主键来进行更新;
代码调用链如下:
public void sendUpdateOtherAccountMessage(Long taxUserId, String phone, Long personId) {
Map<String, Object> data = new HashMap<>();
data.put("phone", phone);
data.put("taxUserId", taxUserId);
data.put("personId", personId);
rabbitGateway.send(AsyncUpdateOtherAccountTaxUserIdRabbitConfig.ACCOUNT_UPDATE_TAXUSERID_DELAY_QUEUE, new RabbitMessage(data));
}
package com.yzf.accounting.acmanager.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.amqp.core.Queue;
import java.util.HashMap;
import java.util.Map;
/**
* AsyncUpdateOtherAccountTaxUserIdRabbitConfig
*
* @author panjiaqing
* @Description 异步更新其他账号的自然人id信息RabbitMQ配置
* @date 2025/3/25
*/
@Configuration
public class AsyncUpdateOtherAccountTaxUserIdRabbitConfig {
/**
* 延迟队列名称(1分钟后执行)
*/
public static final String ACCOUNT_UPDATE_TAXUSERID_DELAY_QUEUE = "account.update.taxuserid.real.onedelay.queue";
/**
* 死信交换机名称
*/
public static final String ACCOUNT_UPDATE_TAXUSERID_DLX_EXCHANGE = "account.update.taxuserid.real.onedelay.dead.exchange";
/**
* 死信队列名称(最终消费)
*/
public static final String ACCOUNT_UPDATE_TAXUSERID_DLQ = "account.update.taxuserid.onedelay.dead.queue";
/**
* 死信路由 key
*/
public static final String ACCOUNT_UPDATE_TAXUSERID_DLQ_ROUTING_KEY = "account.update.taxuserid.onedelay.dead.routingkey";
/**
* 延迟队列,TTL = 60秒,过期后转发到死信交换机
*/
@Bean("accountUpdateTaxUserIdDelayQueue")
public Queue accountUpdateTaxUserIdDelayQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 60000); // 延迟 1 分钟
args.put("x-dead-letter-exchange", ACCOUNT_UPDATE_TAXUSERID_DLX_EXCHANGE);
args.put("x-dead-letter-routing-key", ACCOUNT_UPDATE_TAXUSERID_DLQ_ROUTING_KEY);
return new Queue(ACCOUNT_UPDATE_TAXUSERID_DELAY_QUEUE, true, false, false, args);
}
/**
* 死信队列(最终消费队列)
*/
@Bean("accountUpdateTaxUserIdDLQ")
public Queue accountUpdateTaxUserIdDLQ() {
return new Queue(ACCOUNT_UPDATE_TAXUSERID_DLQ, true);
}
/**
* 死信交换机
*/
@Bean("accountUpdateTaxUserIdDLXExchange")
public DirectExchange accountUpdateTaxUserIdDLXExchange() {
return new DirectExchange(ACCOUNT_UPDATE_TAXUSERID_DLX_EXCHANGE);
}
/**
* 绑定死信队列到死信交换机上
*/
@Bean
public Binding bindAccountUpdateTaxUserIdDLQ(@Qualifier("accountUpdateTaxUserIdDLQ") Queue queue,
@Qualifier("accountUpdateTaxUserIdDLXExchange") DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(ACCOUNT_UPDATE_TAXUSERID_DLQ_ROUTING_KEY);
}
}
@Slf4j
@Component
@RequiredArgsConstructor
public class AsyncUpdateOtherAccountTaxUserIdListener {
private final UpdateOtherAccountTaxUserIdService updateOtherAccountTaxUserIdService;
@RabbitListener(queues = AsyncUpdateOtherAccountTaxUserIdRabbitConfig.ACCOUNT_UPDATE_TAXUSERID_DLQ)
public void handleUpdateTaxUserId(@Payload RabbitMessage message, Message msg, Channel channel) {
try {
Map<String, Object> body = (Map<String, Object>) message.getBody();
String phone = MapUtils.getString(body, "phone");
Long taxUserId = MapUtils.getLong(body, "taxUserId");
Long personId = MapUtils.getLong(body, "personId");
log.info("[AsyncUpdateOtherAccountTaxUserIdListener][handleUpdateTaxUserId] 接收到延迟更新任务 phone={}, taxUserId={}, personId={}", phone, taxUserId, personId);
updateOtherAccountTaxUserIdService.updateTaxUserIdByPersonId(phone, taxUserId, personId);
log.info("[AsyncUpdateOtherAccountTaxUserIdListener][handleUpdateTaxUserId] 异步更新成功 phone={}, taxUserId={}, personId={}", phone, taxUserId, personId);
} catch (Exception e) {
log.error("[AsyncUpdateOtherAccountTaxUserIdListener][handleUpdateTaxUserId] 消息消费异常,data:{},exception:{}", JSON.toJSONString(message), ExceptionUtils.getStackTrace(e));
}
}
package com.yzf.accounting.acmanager.service;
import com.yzf.accounting.acmanager.dao.account.YzfPersonAccountInfoMapper;
import com.yzf.accounting.acmanager.entity.YzfPersonAccountInfo;
import com.yzf.accounting.common.exception.BizRuntimeException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* updateOtherAccountTaxUserId
*
* @author panjiaqing
* @Description 异步更新其他账号的自然人id信息
* @date 2025/2/16
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UpdateOtherAccountTaxUserIdService {
private final YzfPersonAccountInfoMapper yzfPersonAccountInfoMapper;
/**
* @param taxUserId
* @param personId
* @return void
* @Author panjiaqing
* @Description 异步更新该手机号下其他为空的taxUserId值
* @Date 2025/2/14
* @Param phone
**/
public void updateTaxUserIdByPersonId(String phone, Long taxUserId, Long personId) {
if (StringUtils.isBlank(phone) || Objects.isNull(taxUserId) || Objects.isNull(personId)) {
log.error("[UpdateOtherAccountTaxUserIdService][updateTaxUserIdByPersonId] 参数不允许有空值");
throw new BizRuntimeException("异步更新该手机号:{}下其他为空的taxUserId值,入参有空值!", phone);
}
log.info("[UpdateOtherAccountTaxUserIdService][updateTaxUserIdByPersonId] phone:{}, taxUserId:{}, personId:{}", phone, taxUserId, personId);
// 查询需要更新的 ID(加排序避免死锁)
Example personAccountInfoExample = new Example(YzfPersonAccountInfo.class);
personAccountInfoExample.createCriteria()
.andEqualTo("zrrSjh", phone)
.andEqualTo("taxUserId", null);
personAccountInfoExample.setOrderByClause("id ASC");
List<Long> updateIdList = yzfPersonAccountInfoMapper.selectByExample(personAccountInfoExample)
.stream()
.filter(yzfPersonAccountInfo -> !Objects.equals(yzfPersonAccountInfo.getId(), personId))
.map(YzfPersonAccountInfo::getId)
.distinct()
.collect(Collectors.toList());
if (!updateIdList.isEmpty()) {
Example updateExample = new Example(YzfPersonAccountInfo.class);
updateExample.createCriteria().andIn("id", updateIdList);
YzfPersonAccountInfo updateRecord = new YzfPersonAccountInfo();
updateRecord.setTaxUserId(taxUserId);
updateRecord.setUpdateTime(new Date());
yzfPersonAccountInfoMapper.updateByExampleSelective(updateRecord, updateExample);
}
}
}