码上焚香

Yahocen

Activiti7 简易教程

24
2024-05-10

工作流相关介绍

BPM(Business Process Management),业务流程管理

BPMN(Business Process Model And Notation),业务流程模型和符号

具体含义自行搜索了解

Activity 核心机制

业务流程图要规范化,需要遵守一套标准。

业务流程图本质上就是一个XML文件,而XML可以存放所要的数据。

读取业务流程图的过程就是解析XML文件的过程。

读取一个业务流程图的结点就相当于解析一个XML的结点,进一步将数据插入到MySQL表中,形成一条记录。

将一个业务流程图的所有节点都读取并存入到MySQL表中。

后面只要读取MySQL表中的记录就相当于读取业务流程图的一个节点。

业务流程的推进,后面就转换为读取表中的数据,并且处理数据,结束的时候这一行数据就可以删除了。

Activiti 使用流程

  1. 引入相应jar包并初始化数据库或者使用maven等管理工具引入相关依赖  

  2. 通过工具绘画流程图,使用 activiti 流程建模工具(activity-designer)定义业务流程(.bpmn 文件) 。.bpmn 文件就是业务流程定义文件,通过 xml 定义业务流程。

  3. 流程定义部署,向 activiti 部署业务流程定义(.bpmn 文件),使用 activiti 提供的 api 向 activiti 中部署.bpmn 文件

  4. 启动一个流程实例(ProcessInstance),启动一个流程实例表示开始一次业务流程的运行,比如员工请假流程部署完成,如果张三要请假就可以启动一个流程实例,如果李四要请假也启动一个流程实例,两个流程的执行互相不影响,就好比定义一个 java 类,实例化两个对象一样,部署的流程就好比 java 类,启动一个流程实例就好比 new 一个 java 对象

  5. 用户查询待办任务(Task),因为现在系统的业务流程已经交给 activiti 管理,通过 activiti 就可以查询当前流程执行到哪了,当前用户需要办理什么任务了,这些 activiti帮我们管理了。实际上我们学习activiti也只是学习它的API怎么使用,因为很多功能activiti都已经封装好了,我们会调用就行了~

  6. 用户办理任务,用户查询待办任务后,就可以办理某个任务,如果这个任务办理完成还需要其它用户办理,比如采购单创建后由部门经理审核,这个过程也是由 activiti 帮我们完成了,不需要我们在代码中硬编码指定下一个任务办理人了

  7. 流程结束,当任务办理完成没有下一个任务/结点了,这个流程实例就完成了。  

初始化数据库

/**
 * 配置Activiti数据源
 * 多种数据源配置方式详见源码中重载的方法
 */
ProcessEngineConfiguration config = StandaloneProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
config.setJdbcDriver("com.mysql.cj.jdbc.Driver");
//此处注意增加了 nullCatalogMeansCurrent=true 参数,不然mysql不允许建表
config.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/activitidemo?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true");
config.setJdbcUsername("root");
config.setJdbcPassword("123456");
//数据库更更新策略
//flase:默认值,引擎启动时,自动检查数据库里是否已有表,或者表版本是否匹配,如果无表或者表版本不对,则抛出异常。(常用在生产环境);
//true:若表不存在,自动更新;若存在,而表有改动,则自动更新表,若表存在以及表无更新,则该策略不会做任何操作。(一般用在开发环境);
//create_drop:启动时自动建表,关闭时就删除表,有一种临时表的感觉。(需手动关闭,才会起作用);
//drop-create:启动时删除旧表,再重新建表。(无需手动关闭就能起作用);
config.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
/**
 * 初始化数据库表
 */
//获取流程引擎,并初始化数据库
config.buildProcessEngine();

数据库表简单解析

Activiti的后台是有数据库的支持,所有的表都以ACT_开头。 第二部分是表示表的用途的两个字母标识。 用途也和服务的API对应。

ACT_RE_*: 'RE'表示repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。

ACT_RU_*: 'RU'表示runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。

ACT_ID_*: 'ID'表示identity。 这些表包含身份信息,比如用户,组等等。

ACT_HI_*: 'HI'表示history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。

ACT_GE_*: 通用数据, 用于不同场景下,如存放资源文件。

流程引擎ProcessEngine

public interface ProcessEngine {

  /**
   * 资源管理类,提供了管理和控制流程发布包和流程定义的操作。<br>
   * 使用工作流建模工具设计的业务流程图需要使用此service将流程定义文件的内容部署到计算机。<br>
   * 查询引擎中的发布包和流程定义。<br>
   * 暂停或激活发布包,对应全部和特定流程定义。 暂停意味着它们不能再执行任何操作了,激活是对应的反向操作。获得多种资源,像是包含在发布包里的文件, 或引擎自动生成的流程图。<br>
   * 获得流程定义的pojo版本, 可以用来通过java解析流程,而不必通过xml。<br>
   */
  RepositoryService getRepositoryService();

  /**
   * 流程运行管理类。可以从这个服务类中获取很多关于流程执行相关的信息
   */
  RuntimeService getRuntimeService();

  /**
   * 任务管理类。可以从这个类中获取任务的信息。
   */
  TaskService getTaskService();

  /**
   * 历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据(根据配置),比如流程实例启动时间,任务的参与者, 完成任务的时间,每个流程实例的执行路径,等等。 
   * 这个服务主要通过查询功能来获得这些数据。
   */
  HistoryService getHistoryService();

  /**
   * 引擎管理类,提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护。
   */
  ManagementService getManagementService();

  /**
   * 流程定义和部署存储库的访问的服务
   */
  DynamicBpmnService getDynamicBpmnService();

}

绘制Activiti流程图

所有支持GPMN2.0的流程图绘制工具都可以

IDEA插件工具actiBPM(新版idea无法使用),可自行搜索可用版本

前端绘图插件 bpmn-js,推荐相关中文开源项目 bpmn-process-designer

流程定义部署

ps:ActivitiUtils类是我自定义的和PubFundsDeclare.bpmn到文末附件中获取

//解析bpmn图此处非必要步骤
Document xmlDoc = new SAXReader().read(Thread.currentThread().getContextClassLoader().getResource("PubFundsDeclare.bpmn"));
//因为Activiti7中取出了表单相关内容但是某些需求需要表单的参与所有这里自行解析
xmlDoc.getRootElement().element("process").elementIterator("userTask").forEachRemaining(task -> {
    if(!StringUtils.isEmpty(task.attributeValue("formKey"))){
        task.element("extensionElements").element("formData").elements("formField").forEach(field -> {
            System.out.println(field.attributeValue("id"));
            System.out.println(field.attributeValue("label"));
            System.out.println(field.attributeValue("type"));
        });
    }
});
//转换流程图属性前缀,某些流程图绘制工具导出的xml文件前缀不是activiti,所以此处进行一个替换
String bpmn = xmlDoc.asXML().replaceAll("flowable:", "activiti:");
//部署流程
RepositoryService repositoryService = ActivitiUtils.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
    //流程标识
    .key(processDefinitionKey)
    //添加bpmn资源
    //.addClasspathResource("PubFundsDeclare.bpmn")
    .addString("PubFundsDeclare.bpmn", bpmn)
    //添加png资源
    .addClasspathResource("PubFundsDeclare.png")
    .name("出差申请流程")
    .deploy();
System.out.println("流程部署id:" + deployment.getKey());
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());

自行观察数据库表数据变化

启动流程实例

RuntimeService runtimeService = ActivitiUtils.getRuntimeService();
//启动指定标识的标识
Map<String, Object> variables = new HashMap<String, Object>();
//设置流程变量 原理是因为startProcessInstanceByKey存在重载方法
variables.put("employee", "张三");
variables.put("deptManager","李四");
variables.put("generalManager","王五");
ProcessInstance instance = runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variables);
System.out.println("流程定义id:" + instance.getProcessDefinitionId());
System.out.println("流程实例id:" + instance.getId());
System.out.println("当前活动Id:" + instance.getActivityId());

自行观察数据库表数据变化

查询待办任务

//创建TaskService
TaskService taskService = ActivitiUtils.getTaskService();
//根据流程key 和 任务负责人 查询任务
List<Task> list = taskService.createTaskQuery()
    //流程Keyg
    .processDefinitionKey(processDefinitionKey)
    //只查询该任务负责人的任务
    //.taskAssignee("user1")
    .list();
for (Task task : list) {
    if(task instanceof TaskEntityImpl){
        taskService.getIdentityLinksForTask(task.getId()).forEach(il -> {
            if(StringUtils.isEmpty(il.getUserId())){
                System.out.println(il.getGroupId());
            }else{
                System.out.println(il.getUserId());
            }
        });
        System.out.println("表单标识:" + task.getFormKey());
        System.out.println("指定的用户标识:" + task.getAssignee());
        System.out.println("具体的业务标识:" + task.getBusinessKey());
        System.out.println("流程实例id:" + task.getProcessInstanceId());
        System.out.println("任务id:" + task.getId());
        System.out.println("任务负责人:" + task.getAssignee());
        System.out.println("任务名称:" + task.getName());
    }
}

自行观察数据库表数据变化

结束

ActivitiUtils.java

import org.activiti.engine.*;
import org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.impl.transformer.Identity;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.Test;

import java.util.List;

/**
 * 创建Activiti工作流主要包含以下几步:<br>
 * 1、定义流程,按照BPMN的规范,使用流程定义工具,用流程符号把整个流程描述出来<br>
 * 2、部署流程,把画好的流程定义文件,加载到数据库中,生成表的数据<br>
 * 3、启动流程,使用java代码来操作数据库表中的内容<br>
 * @Author Yahocen
 * @Date 2021/8/5 9:13
 * @Version 1.0
 * @Describe
 */
public class ActivitiUtils {

    /**
     * 获取流程引擎
     * 自动建表
     * @return
     */
    public static ProcessEngine createProcessEngine() {
        /**
         * 配置Activiti数据源
         */
        ProcessEngineConfiguration config = StandaloneProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
        config.setJdbcDriver("com.mysql.cj.jdbc.Driver");
        //此处注意增加了 nullCatalogMeansCurrent=true 参数,不然mysql不允许建表
        config.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/activitidemo?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true");
        config.setJdbcUsername("root");
        config.setJdbcPassword("123456");
        //数据库更更新策略
        //flase:默认值,引擎启动时,自动检查数据库里是否已有表,或者表版本是否匹配,如果无表或者表版本不对,则抛出异常。(常用在生产环境);
        //true:若表不存在,自动更新;若存在,而表有改动,则自动更新表,若表存在以及表无更新,则该策略不会做任何操作。(一般用在开发环境);
        //create_drop:启动时自动建表,关闭时就删除表,有一种临时表的感觉。(需手动关闭,才会起作用);
        //drop-create:启动时删除旧表,再重新建表。(无需手动关闭就能起作用);
        config.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        /**
         * 初始化数据库表
         */
        //获取流程引擎
        return config.buildProcessEngine();
    }


    /**
     * 引擎管理类,提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护。
     * @return
     */
    public static ManagementService getManagementService() {
        return createProcessEngine().getManagementService();
    }

    /**
     * 流程定义和部署存储库的访问的服务
     * @return
     */
    public static DynamicBpmnService getDynamicBpmnService() {
        return createProcessEngine().getDynamicBpmnService();
    }

    /**
     * 资源管理类,提供了管理和控制流程发布包和流程定义的操作。<br>
     * 使用工作流建模工具设计的业务流程图需要使用此service将流程定义文件的内容部署到计算机。<br>
     * 查询引擎中的发布包和流程定义。<br>
     * 暂停或激活发布包,对应全部和特定流程定义。 暂停意味着它们不能再执行任何操作了,激活是对应的反向操作。获得多种资源,像是包含在发布包里的文件, 或引擎自动生成的流程图。<br>
     * 获得流程定义的pojo版本, 可以用来通过java解析流程,而不必通过xml。<br>
     * @return
     */
    public static RepositoryService getRepositoryService() {
        return createProcessEngine().getRepositoryService();
    }

    /**
     * 流程运行管理类。可以从这个服务类中获取很多关于流程执行相关的信息
     * @return
     */
    public static RuntimeService getRuntimeService() {
        return createProcessEngine().getRuntimeService();
    }

    /**
     * 任务管理类。可以从这个类中获取任务的信息。
     * @return
     */
    public static TaskService  getTaskService () {
        return createProcessEngine().getTaskService();
    }

    /**
     * 历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据(根据配置),比如流程实例启动时间,任务的参与者, 完成任务的时间,每个流程实例的执行路径,等等。 这个服务主要通过查询功能来获得这些数据。
     * @return
     */
    public static HistoryService  getHistoryService () {
        return createProcessEngine().getHistoryService();
    }

}

PubFundsDeclare.bpmn

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/bpmn" xmlns:activiti="http://activiti.org/bpmn" xmlns:tns="http://sourceforge.net/bpmn/definitions/_1628130247702" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:yaoqiang="http://bpmn.sourceforge.net" id="_1628130247702" name="" targetNamespace="http://sourceforge.net/bpmn/definitions/_1628130247702">
  <process id="PROCESS_1" processType="None" isClosed="false" isExecutable="true">
    <extensionElements>
      <yaoqiang:description />
      <yaoqiang:pageFormat height="841.8897637795276" imageableHeight="831.8897637795276" imageableWidth="588.1102362204724" imageableX="5.0" imageableY="5.0" orientation="0" width="598.1102362204724" />
      <yaoqiang:page background="#FFFFFF" horizontalCount="1" verticalCount="1" />
    </extensionElements>
    <userTask id="_3" name="提交公款申请" implementation="##unspecified" flowable:formKey="from1" flowable:assignee="user1" flowable:candidateUsers="user4,user2,user3" flowable:candidateGroups="group1,group2,group3">
      <extensionElements>
        <flowable:formProperty>
          <flowable:childField id="child1" name="1" readable="true" />
          <flowable:childField id="child2" name="2" type="string" required="true" />
        </flowable:formProperty>
        <flowable:formData>
          <flowable:formField id="name" label="姓名" type="string" />
          <flowable:formField id="jine" label="金额" type="double" />
        </flowable:formData>
      </extensionElements>
    </userTask>
    <userTask id="_4" name="部门领导审批" implementation="##unspecified">
      <extensionElements>
        <flowable:formProperty>
          <flowable:childField id="child1" name="1" readable="true" />
          <flowable:childField id="child2" name="2" type="string" required="true" />
        </flowable:formProperty>
        <flowable:formData />
      </extensionElements>
    </userTask>
    <userTask id="_5" name="机构领导审批" implementation="##unspecified">
      <extensionElements>
        <flowable:formProperty>
          <flowable:childField id="child1" name="1" readable="true" />
          <flowable:childField id="child2" name="2" type="string" required="true" />
        </flowable:formProperty>
      </extensionElements>
    </userTask>
    <userTask id="_6" name="财务部门拨款" implementation="##unspecified">
      <extensionElements>
        <flowable:formProperty>
          <flowable:childField id="child1" name="1" readable="true" />
          <flowable:childField id="child2" name="2" type="string" required="true" />
        </flowable:formProperty>
      </extensionElements>
    </userTask>
    <endEvent id="_7" />
    <startEvent id="_2">
      <extensionElements>
        <yaoqiang:label offset-x="-1.0" offset-y="21.0" />
      </extensionElements>
    </startEvent>
    <sequenceFlow id="_8" sourceRef="_2" targetRef="_3" />
    <sequenceFlow id="_9" sourceRef="_3" targetRef="_4" />
    <sequenceFlow id="_10" sourceRef="_4" targetRef="_5" />
    <sequenceFlow id="_11" sourceRef="_5" targetRef="_6" />
    <sequenceFlow id="_12" sourceRef="_6" targetRef="_7" />
  </process>
  <bpmndi:BPMNDiagram id="Diagram-_1" name="未锟斤拷锟斤拷图" documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0">
    <bpmndi:BPMNPlane bpmnElement="PROCESS_1">
      <bpmndi:BPMNEdge id="BPMNEdge__12" bpmnElement="_12" sourceElement="_6" targetElement="_7">
        <di:waypoint x="513.0078747831291" y="563" />
        <di:waypoint x="513.0078747831291" y="628.0000019378816" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="510.01" y="584.99" width="6" height="21.02" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="BPMNEdge__11" bpmnElement="_11" sourceElement="_5" targetElement="_6">
        <di:waypoint x="513.7696869578226" y="453" />
        <di:waypoint x="513.7696869578226" y="508" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="510.77" y="469.99" width="6" height="21.02" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="BPMNEdge__10" bpmnElement="_10" sourceElement="_4" targetElement="_5">
        <di:waypoint x="514.2775617409518" y="344" />
        <di:waypoint x="514.2775617409518" y="398" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="511.28" y="360.49" width="6" height="21.02" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="BPMNEdge__9" bpmnElement="_9" sourceElement="_3" targetElement="_4">
        <di:waypoint x="514.7854365240808" y="235" />
        <di:waypoint x="514.7854365240808" y="289" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="511.79" y="251.49" width="6" height="21.02" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="BPMNEdge__8" bpmnElement="_8" sourceElement="_2" targetElement="_3">
        <di:waypoint x="512.5" y="113.99218559171948" />
        <di:waypoint x="512.5" y="180" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="509.5" y="136.48" width="6" height="21.02" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="Shape-_3" bpmnElement="_3">
        <dc:Bounds x="472.53937391564534" y="179.98993334763895" width="85" height="55" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_4" bpmnElement="_4">
        <dc:Bounds x="472.0314991325163" y="289.22986669527796" width="85" height="55" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_5" bpmnElement="_5">
        <dc:Bounds x="471.5236243493872" y="398.469800042917" width="85" height="55" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_6" bpmnElement="_6">
        <dc:Bounds x="471.01574956625814" y="507.7097333905559" width="85" height="55" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_7" bpmnElement="_7">
        <dc:Bounds x="497.00787478312907" y="628.4496667381949" width="32" height="32" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="32" height="32" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_2" bpmnElement="_2">
        <dc:Bounds x="496.5" y="82.24999999999994" width="32" height="32" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="178" y="103" width="44" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

pom.xml  

    <properties>
        <slf4j.version>1.6.6</slf4j.version>
        <log4j.version>1.2.12</log4j.version>
        <activiti.version>7.1.0.M6</activiti.version>
        <!--<activiti.version>6.0.0</activiti.version>-->
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-engine</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!--<dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring</artifactId>
            <version>${activiti.version}</version>
        </dependency>-->
        <!-- bpmn 模型处理 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-model</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn 转换 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-converter</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn json数据转换 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-json-converter</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- bpmn 布局 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-layout</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- activiti 云支持 -->
        <!--<dependency>
            <groupId>org.activiti.cloud</groupId>
            <artifactId>activiti-cloud-services-api</artifactId>
            <version>${activiti.version}</version>
        </dependency>-->
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <!-- 链接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
        </dependency>
        <!-- 测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!-- log start -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

        <!-- dom4j -->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
    </dependencies>