51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

使用 Spring Boot3.3 与 MyBatis-Plus 联合实现多层次树结构的异步加载策略

使用 Spring Boot3.3 与 MyBatis-Plus 联合实现多层次树结构的异步加载策略

在使用 Spring Boot 和 MyBatis-Plus 实现多层次树结构的异步加载策略时,理解其基本原理和技术背景是至关重要的。本文将详细探讨如何通过 Spring Boot 的 RESTful API 和 MyBatis-Plus 的强大功能实现多层次树结构的异步加载,并展示如何使用 Thymeleaf、Bootstrap 和 JavaScript 实现前端的动态渲染。

什么是多层次树结构?

多层次树结构是许多应用场景中的常见需求,尤其是在分类管理、组织结构、权限管理等场景中。例如,电商平台中的商品分类可以有多个层级,从根类别到具体商品的详细分类,形成一个递归的树形结构。为了有效管理和展示这些数据,通常需要对其进行异步加载,即在用户需要时才加载具体的层级数据,而不是一次性加载所有数据。这不仅能够减少初始数据加载的时间,还可以提高用户体验。

异步加载的意义

在处理大型树结构时,性能是一个非常重要的考量因素。一次性加载所有层级的数据不仅可能会导致数据传输过大,还会引发前端页面的性能问题。异步加载策略通过在用户展开某个节点时,动态加载该节点下的子节点数据,有效地减少了数据传输量,提高了页面响应速度。这种方法尤其适用于需要处理大量数据并且层级较多的场景。

技术选型与实现思路

为了实现上述功能,我们将采用 Spring Boot 构建后端 API,使用 MyBatis-Plus 处理数据库操作,并通过前端的 Thymeleaf 模板、Bootstrap 进行 UI 展示。具体实现步骤包括:

  1. 数据库设计: 创建一个 category 表,包含 idparent_idname 等字段,用于存储分类的层次结构。

  2. 后端实现: 使用 Spring Boot 构建 RESTful API,通过 MyBatis-Plus 进行数据查询。后端 API 将支持根据 parent_id 查询子节点数据,提供给前端进行异步加载。

  3. 前端实现: 使用 Thymeleaf 模板引擎生成 HTML 页面,并通过 Bootstrap 提供的组件美化页面。通过 JavaScript 实现异步加载功能,当用户点击某个分类节点时,发送请求加载其子分类数据,并动态渲染到页面上。

  4. 代码示例与配置: 文章中将提供完整的代码示例,包括 Spring Boot 项目配置、MyBatis-Plus 的 Mapper 和 Service 实现,以及前端 HTML、JavaScript 代码,帮助开发者快速理解和实现多层次树结构的异步加载。

本篇文章将深入讲解如何使用 Spring Boot3.3 和 MyBatis-Plus 联合实现多层次树结构的异步加载,并提供完整的代码示例。

运行效果:

若想获取项目完整代码以及其他文章的项目源码,且在代码编写时遇到问题需要咨询交流,欢迎加入下方的知识星球。

项目结构

我们将构建一个Spring Boot项目,使用MyBatis-Plus进行数据库操作,并结合Thymeleaf模板引擎在前端展示树结构。以下是项目的基本结构:

springboot-tree-async
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com.icoderoad.treeasync
│   │   │       ├── controller
│   │   │       ├── entity
│   │   │       ├── mapper
│   │   │       ├── service
│   │   │       └── serviceImpl
│   │   ├── resources
│   │   │   ├── templates
│   │   │   ├── application.yml
│   └── test
│       └── java
│           └── com.icoderoad.treeasync
└── pom.xml

项目配置(pom.xml)

首先,我们需要配置pom.xml,包括Spring Boot和MyBatis-Plus的依赖:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.3.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.icoderoad</groupId>
	<artifactId>treeasync</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>treeasync</name>
	<description>Demo project for Spring Boot</description>
&lt;properties&gt;
	&lt;java.version&gt;17&lt;/java.version&gt;
	 &lt;mybatis-plus-boot-starter.version&gt;3.5.7&lt;/mybatis-plus-boot-starter.version&gt;

        <mybatis-spring.version>3.0.3</mybatis-spring.version> </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-thymeleaf</artifactId>         </dependency>                  <dependency>             <groupId>org.projectlombok</groupId>             <artifactId>lombok</artifactId>             <optional>true</optional>         </dependency>         <dependency>         <groupId>com.baomidou</groupId>         <artifactId>mybatis-plus-boot-starter</artifactId>         <version>${mybatis-plus-boot-starter.version}</version>     </dependency>          <dependency>             <groupId>org.mybatis</groupId>             <artifactId>mybatis-spring</artifactId>             <version>${mybatis-spring.version}</version>        </dependency>         <!-- 数据库驱动依赖 -->         <dependency>             <groupId>com.mysql</groupId>             <artifactId>mysql-connector-j</artifactId>             <scope>runtime</scope>         </dependency> </dependencies>

&lt;build&gt;
	&lt;plugins&gt;
		&lt;plugin&gt;
			&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
			&lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
		&lt;/plugin&gt;
	&lt;/plugins&gt;
&lt;/build&gt;

</project>

配置文件(application.yml)

接下来,我们配置application.yml,指定数据源及MyBatis-Plus的配置。

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/tree_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:   mapper-locations: classpath:/mapper/*.xml   global-config:     db-config:       id-type: auto

实体类与Mapper接口

我们使用一个简单的Category实体来表示树结构中的节点:

package com.icoderoad.treeasync.entity;

import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data;

@Data @TableName("category") public class Category {     @TableId(type = IdType.AUTO)     private Long id;     private Long parentId;     private String name; }

CategoryMapper接口定义了数据库操作:

package com.icoderoad.treeasync.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.icoderoad.treeasync.entity.Category; import org.apache.ibatis.annotations.Mapper;

@Mapper public interface CategoryMapper extends BaseMapper<Category> { }

Service层

在Service层,我们实现了获取树结构节点的逻辑:

package com.icoderoad.treeasync.service;

import com.icoderoad.treeasync.entity.Category; import com.baomidou.mybatisplus.extension.service.IService;

import java.util.List;

public interface CategoryService extends IService<Category> {     List<Category> getChildren(Long parentId); }

package com.icoderoad.treeasync.service.impl;

import com.icoderoad.treeasync.entity.Category; import com.icoderoad.treeasync.mapper.CategoryMapper; import com.icoderoad.treeasync.service.CategoryService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service;

import java.util.List;

@Service public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

    @Override     public List<Category> getChildren(Long parentId) {         return lambdaQuery().eq(Category::getParentId, parentId).list();     } }

控制器

控制器用来处理前端的异步请求:

package com.icoderoad.treeasync.controller;

import com.icoderoad.treeasync.entity.Category; import com.icoderoad.treeasync.service.CategoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController @RequestMapping("/category") public class CategoryController {

    @Autowired     private CategoryService categoryService;

    @GetMapping("/children/{parentId}")     public List<Category> getChildren(@PathVariable Long parentId) {         return categoryService.getChildren(parentId);     } }

前端页面与异步加载实现

前端使用Thymeleaf模板结合Bootstrap和JavaScript实现树结构的展示与异步加载:

src/main/resources/templates 目录下创建 index.html 模板文件。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>分类树结构</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.10.5/font/bootstrap-icons.min.css">
    <style>
        .tree-item {
            cursor: pointer;
            padding-left: 1rem;
        }

        .tree-icon {             margin-right: 0.5rem;         }     </style> </head> <body> <div class="container">     <h2 class="my-4">分类树结构</h2>     <ul id="categoryTree" class="list-group">         <!-- 根节点会从服务器加载并插入到这里 -->     </ul> </div>

<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> <script>     $(document).ready(function () {         // 加载根节点         loadTreeNodes(0, $('#categoryTree'));

        // 动态加载节点         function loadTreeNodes(parentId, ulElement) {             $.ajax({                 url: '/category/children/' + parentId,                 method: 'GET',                 success: function (data) {                     $.each(data, function (index, category) {                         const li = $('<li>').addClass('list-group-item tree-item');                         const icon = $('<i>').addClass('bi bi-chevron-right tree-icon');                         const span = $('<span>').text(category.name);

                        li.append(icon).append(span).data('id', category.id);                         ulElement.append(li);

                        // 点击展开或折叠                         li.on('click', function (e) {                             e.stopPropagation();                             const iconElement = $(this).find('.tree-icon');                             const childrenUl = $(this).find('ul');

                            if (childrenUl.length === 0) {                                 // 加载子节点                                 const newUl = $('<ul>').addClass('list-group ml-3');                                 loadTreeNodes($(this).data('id'), newUl);                                 $(this).append(newUl);                                 iconElement.removeClass('bi-chevron-right').addClass('bi-chevron-down');                             } else {                                 // 切换展开/折叠                                 childrenUl.toggle();                                 iconElement.toggleClass('bi-chevron-right bi-chevron-down');                             }                         });                     });                 }             });         }     }); </script> </body> </html>

数据库结构

数据库表category的SQL DDL语句如下:

CREATE TABLE `category` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `parent_id` bigint DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

初始化分类表数据:

-- 插入第一层数据(根节点)
INSERT INTO category (parent_id, name) VALUES (0, '电子产品');
INSERT INTO category (parent_id, name) VALUES (0, '家用电器');
INSERT INTO category (parent_id, name) VALUES (0, '时尚服饰');

-- 插入第二层数据 INSERT INTO category (parent_id, name) VALUES (1, '手机'); INSERT INTO category (parent_id, name) VALUES (1, '笔记本电脑'); INSERT INTO category (parent_id, name) VALUES (2, '冰箱'); INSERT INTO category (parent_id, name) VALUES (2, '洗衣机'); INSERT INTO category (parent_id, name) VALUES (3, '男装'); INSERT INTO category (parent_id, name) VALUES (3, '女装');

-- 插入第三层数据 INSERT INTO category (parent_id, name) VALUES (4, '智能手机'); INSERT INTO category (parent_id, name) VALUES (4, '功能手机'); INSERT INTO category (parent_id, name) VALUES (5, '游戏笔记本'); INSERT INTO category (parent_id, name) VALUES (5, '超极本'); INSERT INTO category (parent_id, name) VALUES (6, '双门冰箱'); INSERT INTO category (parent_id, name) VALUES (6, '单门冰箱'); INSERT INTO category (parent_id, name) VALUES (7, '滚筒洗衣机'); INSERT INTO category (parent_id, name) VALUES (7, '波轮洗衣机'); INSERT INTO category (parent_id, name) VALUES (8, '休闲装'); INSERT INTO category (parent_id, name) VALUES (8, '正装'); INSERT INTO category (parent_id, name) VALUES (9, '连衣裙'); INSERT INTO category (parent_id, name) VALUES (9, '上衣');

-- 插入第四层数据 INSERT INTO category (parent_id, name) VALUES (10, '安卓手机'); INSERT INTO category (parent_id, name) VALUES (10, '苹果手机'); INSERT INTO category (parent_id, name) VALUES (13, '变形笔记本'); INSERT INTO category (parent_id, name) VALUES (13, '传统笔记本'); INSERT INTO category (parent_id, name) VALUES (17, '办公室连衣裙'); INSERT INTO category (parent_id, name) VALUES (17, '休闲连衣裙');

总结

本文介绍了如何使用Spring Boot结合MyBatis-Plus实现多层次树结构的异步加载策略。我们通过一个简单的分类树示例展示了如何在前端逐步加载节点,避免一次性加载大量数据带来的性能问题。希望通过这篇文章,您能够对Spring Boot和MyBatis-Plus的联合使用有更深入的理解,并能够将其应用到实际项目中。


赞(7)
未经允许不得转载:工具盒子 » 使用 Spring Boot3.3 与 MyBatis-Plus 联合实现多层次树结构的异步加载策略