记处理死锁

一、需求背景

        加一段依据手机号同步更新其他自然人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);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值