Recursive and non-recursive implementation of multi-level menu DTO parameter encapsulation

Recursive and non-recursive implementation of multi-level menu DTO parameter encapsulation

  • 1. Introduction to implementation of multi-level menu encapsulation
    • 1.1. Recursive method uses recursion to implement the encapsulation of multi-level menu DTO
    • 1.2. Non-recursive method
  • 2. xxx system scenario
    • 2.1. Input parameter definition
    • 2.2. Parameter DTO definition
    • 2.3. Original recursive implementation
    • 2.4. Non-recursive implementation

Recently, when I was working on requirements in the company, I found that a certain method in another system I was responsible for reported TP50 frequently:
TP50=1037ms[deviation 245.66%], more than 10 times TP50>=300ms

The TP50 here is actually a method performance monitoring indicator: within a time period (such as 5 minutes), count the time consumed by each call of the method, and sort these times from small to large Sort in order, and take the 50% value as the TP50 value; after configuring the alarm threshold corresponding to this monitoring indicator, you need to ensure that at least 50% of the time consumed by all calls to the method within this time period The value must be less than this threshold, otherwise the system will alarm.

Taking advantage of the recent demand for technical improvements, we will optimize this method to avoid constant alarms. So, we started to investigate:

  1. The database query in the method only involves 4 tables, and it is a single-table query. In addition, after analyzing the SQL execution plan, it does go to the index, so the query time does not affect the execution efficiency of the method.
  2. Then, when encapsulating the parameters, the recursive method was used to encapsulate them. If there are a large number of result sets and multiple entities have sub-attributes, recursive encapsulation will waste too much time.

Therefore, the non-recursive method is used for optimization, that is: space for time
In fact, this kind of problem is very common in Java parameter encapsulation, including: multi-level menus, multi-level questions, etc. Therefore, I will make a simple summary here. The specific solutions can be found in 2.4

1. Introduction to implementation of multi-level menu encapsulation

To implement parameter encapsulation of multi-level menu DTO in Java, recursive or non-recursive methods are generally used.

1.1. Recursive method uses recursion to implement the encapsulation of multi-level menu DTO

  • Create a DTO class to represent the menu item, including the name, ID, type, title, submenu list and other attributes of the menu item.
  • Define a recursive method recursion in the DTO class, which receives the ID of a menu item as a parameter and returns a complete menu item DTO object.
  • In the recursive method, first obtain the corresponding menu item information from the data source based on the incoming menu item ID.
  • If there is a submenu for this menu item, call the recursion method recursively, pass in the ID (or entity) of the submenu item, and add the returned submenu item DTO object to the submenu list of the current menu item.
  • Finally, the current menu item DTO object is returned.

1.2. Non-recursive method

  • Create a DTO class to represent the menu item, including the name, ID, type, title, submenu list and other attributes of the menu item.

  • Define a stackstack to save pending menu entities.
    First, push the first-level menu entity into the stack (the content saved in the stack depends on the scene, it may be an id, it may be an instance).

  • Enter a loop until the stack is empty:

    • Pop the menu item entity on top of the stack
    • Obtain the corresponding menu item information from the data source according to the menu item ID (or entity).
    • Create a menu item DTO object that needs to be returned, and set the corresponding properties.
    • If the menu item has a submenu, the IDs (or entities) of the submenu items are pushed onto the stack in sequence.
    • Adds the current menu item DTO object to its parent menu item’s submenu list.
  • Return result set

2. xxx system scenario

Map the question collection Question found in the database into a QuestionDTO collection. The Question here has an attribute called the type field, which is used to indicate whether the current question is a parent question

2.1. Input parameter definition

public class Question extends AbstractEntity {<!-- -->

    /**
     *Corresponding question bank number
     */
    private Long paperId;

    /**
     * Question serial number
     */
    private Long questionIndex;

    /**
     * Paragraph number
     */
    private Long sectionId;

    private Long knowledgeId = 0l;

    private String knowledgeName;

    /**
     * Knowledge base link [old], used to associate knowledge points and display when editing test questions
     */
    private String knowledgeLink;

    /**
     * Questionnaire related content
     */
    private Long dimensionId;

    private String dimensionName;

    private Long subDimensionId;

    private String subDimensionName;

    private String hasElse;

    /**
     * Question type
     *Single choice, multiple choice, scenario
     */
    private Integer type;

    /**
     * Fraction
     */
    private Integer score;

    /**
     * title
     */
    private String title;

    /**
     * Parse
     */
    private String solution;

    /**
     * Knowledge base link [new], used to display answer records and practice mode display at the end of the exam
     */
    private String knowLink;


    /**
     * Scenario test questions ---> Sub-test questions
     */
    private Long parentId = 0L;

    private transient Long tempId;


    private List<Answer> answers;

    private List<Question> children = new ArrayList<Question>();

    private List<QuestionAttachment> questionAttachments = new ArrayList<QuestionAttachment>();

    private Boolean skip;

    private Long targetQuestionId;

    private Long targetQuestionIndex;

    private Long usedPaper=0L;
}


public class AbstractEntity implements Serializable {<!-- -->
    private Long id;
    @JsonSerialize(
        using = Json.DateTimeMinuteSerializer.class
    )
    private Date createdAt;
    private String createdPin;
    @JsonSerialize(
        using = Json.DateTimeMinuteSerializer.class
    )
    private Date updatedAt;
    private String updatedPin;
    private Integer sysVersion;
    privateBooleanyn;
}

2.2. Parameter DTO definition

public class QuestionDTO implements Serializable {<!-- -->
    private Long id;

    private List<QuestionDTO> children;

    private List<AnswerDTO> answerDTOs;
    private Long questionIndex;
    /**
     * title
     */
    private String title;
    /**
     * Question type
     *Single choice, multiple choice, short answer
     */
    private Integer type;

    private Boolean skip;

    private Long targetQuestionId;
}

2.3. Original recursive implementation

/**
     * List convert question into questionDTO
     * @param questions
     * @return
     */
    public List<QuestionDTO> convertQuestions2DTO(List<Question> questions){<!-- -->
        List<QuestionDTO> questionDTOs = new ArrayList<QuestionDTO>();
        for(Question question : questions){<!-- -->
            QuestionDTO questionDTO = convert2QuestionDTO(question);
            questionDTOs.add(questionDTO);
        }
        return questionDTOs;
    }

    /**
     * Convert question 2 questionDTO
     * @param question
     * @return
     */
    private QuestionDTO convert2QuestionDTO(Question question){<!-- -->
        QuestionDTO questionDTO = new QuestionDTO();
        BeanUtils.copyProperties(question,questionDTO);
        // The current question is the parent question. Recursively search for all sub-questions of this question and encapsulate them as questionDTOs.
        if(SurveyTypeEnum.PARENT.getCode().equals(question.getType())){<!-- -->
            List<QuestionDTO> questionDTOs = convertQuestions2DTO(question.getChildren());
            questionDTO.setChildren(questionDTOs);
        }else{<!-- -->
            //The current question is not the parent question, directly set the answer attribute of the current question
            List<AnswerDTO> answerDTOs = convertAnswer2DTO(question);
            questionDTO.setAnswerDTOs(answerDTOs);
        }
        return questionDTO;
    }

    /**
     * Convert list answer 2 answerDto
     * @param question
     * @return
     */
    private List<AnswerDTO> convertAnswer2DTO(Question question){<!-- -->
        List<AnswerDTO> answerDTOs = new ArrayList<AnswerDTO>();
        for(Answer answer : question.getAnswers()){<!-- -->
            AnswerDTO answerDTO = new AnswerDTO();
            BeanUtils.copyProperties(answer,answerDTO);
            answerDTOs.add(answerDTO);
        }
        return answerDTOs;
    }

2.4. Non-recursive implementation

In the non-recursive implementation, I use a stack to hold pending question objects.

  • First, the initial list of problem objects is pushed onto the stack.
  • In the loop, a problem object is popped from the stack, converted into a problem DTO object, and processed differently according to the problem type.
  • If the question type is Parent question (for recursive logic in the 2.3 method), push its child question objects onto the stack one by one, create the corresponding question DTO object, and add it to the parent question DTO object in the list of sub-questions
  • If the question type is other types (no recursion), the answer entity is encapsulated directly.
  • Finally, add the converted question DTO object to the results list
/**
     * Use non-recursive method to encapsulate problem collection DTO
     * @param questions question collection
     * @return Problem instances are mapped one by one
     */
    private List<QuestionDTO> convertQuestions2DTOByNonRecursion(List<Question> questions) {<!-- -->
        List<QuestionDTO> questionDTOs = new ArrayList<>();
        Stack<Question> stack = new Stack<>();
        // Custom thread-safe stack
        ConcurrentStack<QuerySurveyDTO> concurrentStack = new ConcurrentStack<>();
        // Problem entities are pushed onto the stack one after another
        for (Question question : questions) {<!-- -->
            stack.push(question);
        }
        // The stack is empty, indicating the end of encapsulation
        while (!stack.isEmpty()) {<!-- -->
            Question question = stack.pop();
            QuestionDTO questionDTO = new QuestionDTO();
            BeanUtils.copyProperties(question, questionDTO);
         // If the current question is a parent question, create a child question collection childQuestionDTOs.
        // Traverse all sub-problems of the current problem entity and push each sub-problem onto the stack in turn
        // And map it to a dto, add it to the childQuestionDTOs in the child question collection, and set the parent question attribute children to childQuestionDTOs after all traversals are completed.
            if (SurveyTypeEnum.PARENT.getCode().equals(question.getType())) {<!-- -->
                List<QuestionDTO> childQuestionDTOs = new ArrayList<>();
                for (Question childQuestion : question.getChildren()) {<!-- -->
                    stack.push(childQuestion);
                    QuestionDTO childQuestionDTO = new QuestionDTO();
                    BeanUtils.copyProperties(childQuestion, childQuestionDTO);
                    childQuestionDTOs.add(childQuestionDTO);
                }
                questionDTO.setChildren(childQuestionDTOs);
            } else {<!-- -->
          // If the current question is not the parent question, traverse the answer collection of the current question, encapsulate them into answerDTO one by one, and add them to the answerDTOs collection. Injected into the answerDTOs attribute of the question DTO
                List<AnswerDTO> answerDTOs = new ArrayList<>();
                for (Answer answer : question.getAnswers()) {<!-- -->
                    AnswerDTO answerDTO = new AnswerDTO();
                    BeanUtils.copyProperties(answer, answerDTO);
                    answerDTOs.add(answerDTO);
                }
                questionDTO.setAnswerDTOs(answerDTOs);
            }
            questionDTOs.add(questionDTO);
        }
        return questionDTOs;
    }

Although Stack inherits Vector , its pop and peek methods add the synchronized key Word is thread-safe, but officials do not recommend continuing to use Stack.

Therefore, in a concurrent scenario, I used a thread-safe queue ConcurrentLinkedQueue to implement the function of the stack: using a queue to implement the stack. It should be noted that each time the element enters the queue, All elements except the current element need to be dequeued in sequence and then added to the queue. Then, the newly added element element goes to the head of the queue again, ensuring the first-in-last-out feature of the stack.

/**
     * Use a thread-safe queue ConcurrentLinkedQueue to implement the stack
     * @param <T> data type
     */
    static class ConcurrentStack<T>{<!-- -->
        Queue<T> queue;

        public ConcurrentStack() {<!-- -->
            this.queue = new ConcurrentLinkedQueue<>();
        }
        
        public void push(T element){<!-- -->
            queue.offer(element);
            int size = queue.size();
            while(size-- > 1){<!-- -->
                queue.offer(queue.poll());
            }
        }

        public T pop(){<!-- -->
        return queue.poll();
        }

        public boolean isEmpty(){<!-- -->
            return queue.isEmpty();
        }
    }