Do you really change your status?

Keywords: Java SQL Mybatis network JSON

In enterprise applications, there are too many scenarios involving modifying state. For example, after an enterprise enters the network, it needs to check its qualification. After the individual receives the task, the enterprise administrator shall review the receiver.

In the application management system, usually as shown in the figure below, there are operation buttons behind the list to modify the status of data records.

Click "pass" / "reject" to modify the status field of the data record. How to implement the server program logic?

 

First define the api interface of the server:

  1. Request URL:
    http://***/api/enterprise/taskApply/audit
  2. Request Method:
    POST
  3. Content-Type:
    application/json;charset=UTF-8
  4. User-Agent:
    Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
  5. Request Payload:
    {"applyIds":"1,2,3","auditStatus":"PASS"}

 

The front and back end of the system are separated, and the system architecture is distributed. Spring MVC's RestController implements CRUD of data by calling Dubbo interface remotely.

The project uses jeecg boot reverse engineering to generate code, and then makes adjustments on this basis.

 

Program logic v1

    @RequestMapping(value = "/enterprise/taskApply/audit",method = RequestMethod.POST)
    public Result audit(@RequestBody TaskApplyVO taskApplyVO, HttpServletRequest req){
        //Currently logged in Enterprise
        EnterpriseVO enterpriseVo = EnterpriseContext.getEnterpriseVo();
        if(enterpriseVo == null){
            return Result.error("Not logged in");
        }
        if(StringUtils.isEmpty(taskApplyVO.getApplyIds())){
            return Result.error("No choice");
        }
        String[] applyIds = taskApplyVO.getApplyIds().split(",");
        for (String applyId : applyIds) {
            TaskApplyVO taskApply = new TaskApplyVO();
            taskApply.setApplyId(Long.parseLong(applyId));
            taskApply.setStatus(taskApplyVO.getStatus());
            taskApply.setAuditTime(DateUtils.getDate());
            taskApplyService.updateById(taskApply);
        }

        return Result.ok("Success");
    }

 

Where taskApplyService is the remote RPC interface instance.

After using the system for a period of time, bug s appear - the audited records can be reviewed again. When will this happen? Let's not say. However, looking at the code logic, we can see that the initial value of the status field is not determined before modifying the status field of a record (it can only be modified to approved or approved rejected in the status to be approved), so this situation will occur.

Program logic v2

Fix the bug above. Add the pre state judgment.

    @RequestMapping(value = "/enterprise/taskApply/audit",method = RequestMethod.POST)
    public Result audit(@RequestBody TaskApplyVO taskApplyVO, HttpServletRequest req){
        //Currently logged in Enterprise
        EnterpriseVO enterpriseVo = EnterpriseContext.getEnterpriseVo();
        if(enterpriseVo == null){
            return Result.error("Not logged in");
        }
        if(StringUtils.isEmpty(taskApplyVO.getApplyIds())){
            return Result.error("No choice");
        }
        String[] applyIds = taskApplyVO.getApplyIds().split(",");
        for (String applyId : applyIds) {
            TaskApplyVO taskApply = taskApplyService.getById(applyId);
            if(!TaskApplyStatusEnum.TO_AUDIT.name().equals(taskApply.getStatus())){
                continue;
            }

            taskApply.setStatus(taskApplyVO.getStatus());
            taskApply.setAuditTime(DateUtils.getDate());
            taskApplyService.updateById(taskApply);
        }

        return Result.ok("Success");
    }

 

After using the system for a period of time, bug s appear - the modification of data records by users is inexplicably lost. Ha ha, look at the above code. When there are many different operation requests for the same record of this data table, such as audit and information modification, will there be coverage? Yes, because there is a time interval between rpc call getById and rpc call updateById. Both threads get the data record through getById, and then execute updateById after modifying different fields. The database operation itself has a sequence, so one update may overwrite the other. How to change that? Control with distributed transactions? That would be more difficult. In order to reduce the possibility of such conflict, it is better to reconstruct the logic of our method first.

Program logic v3

Modify the above logic, new an entity object, only update the required fields.

    @RequestMapping(value = "/enterprise/taskApply/audit",method = RequestMethod.POST)
    public Result audit(@RequestBody TaskApplyVO taskApplyVO, HttpServletRequest req){
        //Currently logged in Enterprise
        EnterpriseVO enterpriseVo = EnterpriseContext.getEnterpriseVo();
        if(enterpriseVo == null){
            return Result.error("Not logged in");
        }
        if(StringUtils.isEmpty(taskApplyVO.getApplyIds())){
            return Result.error("No choice");
        }
        String[] applyIds = taskApplyVO.getApplyIds().split(",");
        for (String applyId : applyIds) {
            TaskApplyVO byId = taskApplyService.getById(applyId);
            if(!TaskApplyStatusEnum.TO_AUDIT.name().equals(byId.getStatus())){
                continue;
            }
            TaskApplyVO taskApply = new TaskApplyVO();
            taskApply.setApplyId(Long.parseLong(applyId));
            taskApply.setStatus(taskApplyVO.getStatus());
            taskApply.setAuditTime(DateUtils.getDate());
            taskApplyService.updateById(taskApply);
        }

        return Result.ok("Save successfully");
    }

 

The system has been in use for some time, and bugs appear. What bug? It's also the first bug - the audited records can be reviewed again!!! Run away

Program logic v4

Use the status lock.

At the same time, change the program implementation. Encapsulate the logical post of audit into RPC service.

    @RequestMapping(value = "/enterprise/taskApply/audit",method = RequestMethod.POST)
    public Result audit(@RequestBody TaskApplyVO taskApplyVO, HttpServletRequest req){
        //Currently logged in Enterprise
        EnterpriseVO enterpriseVo = EnterpriseContext.getEnterpriseVo();
        if(enterpriseVo == null){
            return Result.error("Not logged in");
        }
        if(StringUtils.isEmpty(taskApplyVO.getApplyIds())){
            return Result.error("No choice");
        }
        String[] applyIds = taskApplyVO.getApplyIds().split(",");
        return taskApplyService.audit(applyIds, taskApplyVO.getStatus());
    }

 

The audit method in the taskApplyService implementation class:
    @Override
    public Result audit(String[] taskIdArr, String auditStatus) {
        for (String id : taskIdArr) {
            TaskApply taskApply = new TaskApply();
            taskApply.setStatus(auditStatus);
            taskApply.setAuditTime(DateUtils.getDate());
            taskApplyManager.update(taskApply, new LambdaQueryWrapper<TaskApply>()
                    .eq(TaskApply::getApplyId, Long.parseLong(id))
                    .eq(TaskApply::getStatus, TaskApplyStatusEnum.TO_AUDIT.name()));
        }
        return Result.ok();
    }

 


We modify the configuration of mybatis plus to print out the sql executed by the program
mybatis-plus:
  configuration:
    # This configuration will print out the executed sql, which can be used during development or test
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
The following is the sql executed by the program. There is a status field in the where condition. The update execution returns 1 successfully, otherwise 0.
==>  Preparing: UPDATE emax_task_apply SET apply_status=?, audit_time=? WHERE apply_id = ? AND status = ? 
==> Parameters: TASKAPPLY_PASS(String), 2020-03-23 16:52:44.186(Timestamp), 1(Long), TO_AUDIT(String)
<==    Updates: 1

 

 

End

Thanks for reading. It takes 2:51:00 (18:00 ~ 20:51) to organize this article. Shortcomings, welcome to exchange!

Posted by Pastulio on Mon, 23 Mar 2020 06:45:23 -0700