SpringBoot + MyBatis-Plus several ways to build a tree structure

1. Tree structure

A tree structure refers to a data structure in which the relationship between data elements is like a tree. Multiple branches extending from the root of the tree

Insert image description here

It has the following characteristics:

  • Each node has only a limited number of child nodes or no child nodes;
  • A node without a parent node is called a root node;
  • Each non-root node has one and only one parent node;
  • In addition to the root node, each child node can be divided into multiple disjoint subtrees;
  • There are no cycles in the tree

2. FAQ

 In actual development, a lot of data is in a tree structure, such as regions, menus on pages, organizations with superior-subordinate relationships, etc. At this time, we need to read the data from the data source and put it together in some way. The tree structure is then displayed to the front end. For some data that does not change frequently and is used frequently, you can consider putting the assembled tree structure data into the cache, and read it directly every time it is used.

3. Prepare the environment

springboot: 2.6.0
mysql: 5.7

CREATE TABLE `t_region` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `region_type` varchar(255) DEFAULT NULL,
  `parent_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT into t_region(name, region_type, parent_id) VALUES('山西省', 'province', 0);
INSERT into t_region(name, region_type, parent_id) VALUES('临汾市', 'city', 1);
INSERT into t_region(name, region_type, parent_id) VALUES('尧都区', 'district', 2);

INSERT into t_region(name, region_type, parent_id) VALUES('北京', 'province', 0);
INSERT into t_region(name, region_type, parent_id) VALUES('北京市', 'city', 4);
INSERT into t_region(name, region_type, parent_id) VALUES('朝阳区', 'district', 5);


INSERT into t_region(name, region_type, parent_id) VALUES('太原市', 'city', 1);
INSERT into t_region(name, region_type, parent_id) VALUES('小店区', 'district', 7);

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.0</version>
        <relativePath/>
    </parent>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- druid依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.0</version>
        </dependency>
    </dependencies>
</project>
spring:
  main:
    allow-circular-references: true
  datasource: #定义数据源
    #127.0.0.1为本机测试的ip,3306是mysql的端口号。serverTimezone是定义时区,照抄就好,mysql高版本需要定义这些东西
    #useSSL也是某些高版本mysql需要问有没有用SSL连接
    url: jdbc:mysql://192.168.1.141:3306/db_user?serverTimezone=GMT%2B8&useSSL=FALSE
    username: root  #数据库用户名,root为管理员
    password: 123456 #该数据库用户的密码
    # 使用druid数据源
    type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:
  # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
  mapper-locations: classpath:mapper/*.xml
  # 以下配置均有默认值,可以不设置
  global-config:
    db-config:
      #主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
      id-type: auto
      #数据库类型
      db-type: MYSQL
  configuration:
    # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
    map-underscore-to-camel-case: true
    # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
    call-setters-on-nulls: true
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

Entity class corresponding to the table

@Data
@TableName(value = "t_region")
public class Region {
    
    

    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 名称
     */
    private String name;
    /**
     * 类型
     */
    private String regionType;

    /**
     * 父id
     */
    private Long parentId;
}

The entity class returned to the front end

@Data
public class RegionVO {
    
    

    private Long id;
    /**
     * 名称
     */
    private String name;
    /**
     * 类型
     */
    private String regionType;

    /**
     * 父id
     */
    private Long parentId;

    private List<RegionVO> children;
}

4. Implementation method

1. Based on xml

RegionMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mapper.RegionMapper">

    <resultMap id="regionMap" type="org.example.dto.RegionVO">
        <id property="id" column="id"></id>
        <result property="name" column="name"></result>
        <result property="regionType" column="region_type"></result>
        <result property="parentId" column="parent_id"></result>
        <collection property="children" ofType="org.example.dto.RegionVO" javaType="java.util.List"
                    column="id" select="getById">
        </collection>
    </resultMap>

    <select id="getById" resultMap="regionMap" parameterType="map">
        SELECT
            *
        FROM
            t_region
        where parent_id=#{id}
    </select>

    <select id="getAll" resultMap="regionMap">
        SELECT
            *
        FROM
            t_region where parent_id = 0
    </select>

</mapper>

RegionMapper

@Mapper
public interface RegionMapper extends BaseMapper<Region> {
    
    

    List<RegionVO> getAll();
}

RegionService

public interface RegionService extends IService<Region> {
    
    

    List<RegionVO> getAll();
}

RegionServiceImpl

@Service
public class RegionServiceImpl extends ServiceImpl<RegionMapper, Region> implements RegionService {
    
    

    @Override
    public List<RegionVO> getAll(){
    
    
        return baseMapper.getAll();
    }
}

In this way, a total of 9 queries were executed according to the amount of data added above (8 items).

Insert image description here

2.LambdaQueryWrapper

RegionServiceImplAdd the following code

 @Override
    public List<RegionVO> getAllWrapper(){
    
    
        LambdaQueryWrapper<Region> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Region::getParentId, 0);
        //查询根级
        List<Region> regions = baseMapper.selectList(wrapper);

        List<RegionVO> list = regions.stream().map(p -> {
    
    
            RegionVO obj = new RegionVO();
            BeanUtils.copyProperties(p, obj);
            return obj;
        }).collect(Collectors.toList());

        list.forEach(this::getChildren);
        return list;
    }

    private void getChildren(RegionVO item){
    
    
        LambdaQueryWrapper<Region> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Region::getParentId, item.getId());
        //根据parentId查询
        List<Region> list = baseMapper.selectList(wrapper);

        List<RegionVO> voList = list.stream().map(p -> {
    
    
            RegionVO vo = new RegionVO();
            BeanUtils.copyProperties(p, vo);
            return vo;
        }).collect(Collectors.toList());
        //写入到children
        item.setChildren(voList);

        //如果children不为空,继续往下找
        if (!CollectionUtils.isEmpty(voList)) {
    
    
            voList.forEach(this::getChildren);
        }
    }

In this way, a total of 9 queries were executed according to the amount of data added above (8 items).

Insert image description here

3. Recursive method

RegionServiceImplAdd the following code

 @Override
public  List<RegionVO> build(){
    
    
    //一次把所有的数据都查出来
    List<Region> regions = baseMapper.selectList(null);

    List<RegionVO> allList = regions.stream().map(p -> {
    
    
        RegionVO vo = new RegionVO();
        BeanUtils.copyProperties(p, vo);
        return vo;
    }).collect(Collectors.toList());
    //指定根节点的parentId
    return buildChildren(0L, allList);
}

private List<RegionVO> buildChildren(Long parentId, List<RegionVO> allList){
    
    
    List<RegionVO> voList = new ArrayList<>();
    for (RegionVO item : allList) {
    
    
        //如果相等
        if (Objects.equals(item.getParentId(), parentId)) {
    
    
            //递归,自己调自己
            item.setChildren(buildChildren(item.getId(), allList));
            voList.add(item);
        }
    }
    return voList;
}

Needless to say, all the data is queried at once, and a query is executed in total.

Insert image description here

4. Summary

 There are many query methods, and which one should be used needs to be selected according to the specific situation.

The first case: when the overall data volume is particularly large and the level is not deep and needs to be queried according to a certain root node, it is recommended to use the first and second methods.
The second case: when the entire tree needs to be queried, the third method is recommended.

Guess you like

Origin blog.csdn.net/weixin_43847283/article/details/132396680