使用 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 展示。具体实现步骤包括:
-
数据库设计: 创建一个
category
表,包含id
、parent_id
、name
等字段,用于存储分类的层次结构。 -
后端实现: 使用 Spring Boot 构建 RESTful API,通过 MyBatis-Plus 进行数据查询。后端 API 将支持根据
parent_id
查询子节点数据,提供给前端进行异步加载。 -
前端实现: 使用 Thymeleaf 模板引擎生成 HTML 页面,并通过 Bootstrap 提供的组件美化页面。通过 JavaScript 实现异步加载功能,当用户点击某个分类节点时,发送请求加载其子分类数据,并动态渲染到页面上。
-
代码示例与配置: 文章中将提供完整的代码示例,包括 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>
<properties> <java.version>17</java.version> <mybatis-plus-boot-starter.version>3.5.7</mybatis-plus-boot-starter.version>
<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>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</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的联合使用有更深入的理解,并能够将其应用到实际项目中。