使用 Spring Boot3.3 與 MyBatis-Plus 聯(lián)合實(shí)現(xiàn)多層次樹結(jié)構(gòu)的異步加載策略
在使用 Spring Boot 和 MyBatis-Plus 實(shí)現(xiàn)多層次樹結(jié)構(gòu)的異步加載策略時,理解其基本原理和技術(shù)背景是至關(guān)重要的。本文將詳細(xì)探討如何通過 Spring Boot 的 RESTful API 和 MyBatis-Plus 的強(qiáng)大功能實(shí)現(xiàn)多層次樹結(jié)構(gòu)的異步加載,并展示如何使用 Thymeleaf、Bootstrap 和 JavaScript 實(shí)現(xiàn)前端的動態(tài)渲染。
什么是多層次樹結(jié)構(gòu)?
多層次樹結(jié)構(gòu)是許多應(yīng)用場景中的常見需求,尤其是在分類管理、組織結(jié)構(gòu)、權(quán)限管理等場景中。例如,電商平臺中的商品分類可以有多個層級,從根類別到具體商品的詳細(xì)分類,形成一個遞歸的樹形結(jié)構(gòu)。為了有效管理和展示這些數(shù)據(jù),通常需要對其進(jìn)行異步加載,即在用戶需要時才加載具體的層級數(shù)據(jù),而不是一次性加載所有數(shù)據(jù)。這不僅能夠減少初始數(shù)據(jù)加載的時間,還可以提高用戶體驗。
異步加載的意義
在處理大型樹結(jié)構(gòu)時,性能是一個非常重要的考量因素。一次性加載所有層級的數(shù)據(jù)不僅可能會導(dǎo)致數(shù)據(jù)傳輸過大,還會引發(fā)前端頁面的性能問題。異步加載策略通過在用戶展開某個節(jié)點(diǎn)時,動態(tài)加載該節(jié)點(diǎn)下的子節(jié)點(diǎn)數(shù)據(jù),有效地減少了數(shù)據(jù)傳輸量,提高了頁面響應(yīng)速度。這種方法尤其適用于需要處理大量數(shù)據(jù)并且層級較多的場景。
技術(shù)選型與實(shí)現(xiàn)思路
為了實(shí)現(xiàn)上述功能,我們將采用 Spring Boot 構(gòu)建后端 API,使用 MyBatis-Plus 處理數(shù)據(jù)庫操作,并通過前端的 Thymeleaf 模板、Bootstrap 進(jìn)行 UI 展示。具體實(shí)現(xiàn)步驟包括:
- 數(shù)據(jù)庫設(shè)計: 創(chuàng)建一個 category 表,包含 id、parent_id、name 等字段,用于存儲分類的層次結(jié)構(gòu)。
- 后端實(shí)現(xiàn): 使用 Spring Boot 構(gòu)建 RESTful API,通過 MyBatis-Plus 進(jìn)行數(shù)據(jù)查詢。后端 API 將支持根據(jù) parent_id 查詢子節(jié)點(diǎn)數(shù)據(jù),提供給前端進(jìn)行異步加載。
- 前端實(shí)現(xiàn): 使用 Thymeleaf 模板引擎生成 HTML 頁面,并通過 Bootstrap 提供的組件美化頁面。通過 JavaScript 實(shí)現(xiàn)異步加載功能,當(dāng)用戶點(diǎn)擊某個分類節(jié)點(diǎn)時,發(fā)送請求加載其子分類數(shù)據(jù),并動態(tài)渲染到頁面上。
- 代碼示例與配置: 文章中將提供完整的代碼示例,包括 Spring Boot 項目配置、MyBatis-Plus 的 Mapper 和 Service 實(shí)現(xiàn),以及前端 HTML、JavaScript 代碼,幫助開發(fā)者快速理解和實(shí)現(xiàn)多層次樹結(jié)構(gòu)的異步加載。
本篇文章將深入講解如何使用 Spring Boot3.3 和 MyBatis-Plus 聯(lián)合實(shí)現(xiàn)多層次樹結(jié)構(gòu)的異步加載,并提供完整的代碼示例。
運(yùn)行效果:
圖片
若想獲取項目完整代碼以及其他文章的項目源碼,且在代碼編寫時遇到問題需要咨詢交流,歡迎加入下方的知識星球。
項目結(jié)構(gòu)
我們將構(gòu)建一個Spring Boot項目,使用MyBatis-Plus進(jìn)行數(shù)據(jù)庫操作,并結(jié)合Thymeleaf模板引擎在前端展示樹結(jié)構(gòu)。以下是項目的基本結(jié)構(gòu):
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 versinotallow="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>
<!-- 數(shù)據(jù)庫驅(qū)動依賴 -->
<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,指定數(shù)據(jù)源及MyBatis-Plus的配置。
spring:
datasource:
url: jdbc:mysql://localhost:3306/tree_db?useUnicode=true&characterEncoding=UTF-8&serverTimeznotallow=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
實(shí)體類與Mapper接口
我們使用一個簡單的Category實(shí)體來表示樹結(jié)構(gòu)中的節(jié)點(diǎn):
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接口定義了數(shù)據(jù)庫操作:
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層,我們實(shí)現(xiàn)了獲取樹結(jié)構(gòu)節(jié)點(diǎn)的邏輯:
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);
}
}
前端頁面與異步加載實(shí)現(xiàn)
前端使用Thymeleaf模板結(jié)合Bootstrap和JavaScript實(shí)現(xiàn)樹結(jié)構(gòu)的展示與異步加載:
在 src/main/resources/templates 目錄下創(chuàng)建 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>分類樹結(jié)構(gòu)</title>
<link rel="stylesheet" >
<link rel="stylesheet" >
<style>
.tree-item {
cursor: pointer;
padding-left: 1rem;
}
.tree-icon {
margin-right: 0.5rem;
}
</style>
</head>
<body>
<div class="container">
<h2 class="my-4">分類樹結(jié)構(gòu)</h2>
<ul id="categoryTree" class="list-group">
<!-- 根節(jié)點(diǎn)會從服務(wù)器加載并插入到這里 -->
</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 () {
// 加載根節(jié)點(diǎn)
loadTreeNodes(0, $('#categoryTree'));
// 動態(tài)加載節(jié)點(diǎn)
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);
// 點(diǎn)擊展開或折疊
li.on('click', function (e) {
e.stopPropagation();
const iconElement = $(this).find('.tree-icon');
const childrenUl = $(this).find('ul');
if (childrenUl.length === 0) {
// 加載子節(jié)點(diǎn)
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>
數(shù)據(jù)庫結(jié)構(gòu)
數(shù)據(jù)庫表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;
初始化分類表數(shù)據(jù):
-- 插入第一層數(shù)據(jù)(根節(jié)點(diǎn))
INSERT INTO category (parent_id, name) VALUES (0, '電子產(chǎn)品');
INSERT INTO category (parent_id, name) VALUES (0, '家用電器');
INSERT INTO category (parent_id, name) VALUES (0, '時尚服飾');
-- 插入第二層數(shù)據(jù)
INSERT INTO category (parent_id, name) VALUES (1, '手機(jī)');
INSERT INTO category (parent_id, name) VALUES (1, '筆記本電腦');
INSERT INTO category (parent_id, name) VALUES (2, '冰箱');
INSERT INTO category (parent_id, name) VALUES (2, '洗衣機(jī)');
INSERT INTO category (parent_id, name) VALUES (3, '男裝');
INSERT INTO category (parent_id, name) VALUES (3, '女裝');
-- 插入第三層數(shù)據(jù)
INSERT INTO category (parent_id, name) VALUES (4, '智能手機(jī)');
INSERT INTO category (parent_id, name) VALUES (4, '功能手機(jī)');
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, '滾筒洗衣機(jī)');
INSERT INTO category (parent_id, name) VALUES (7, '波輪洗衣機(jī)');
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, '上衣');
-- 插入第四層數(shù)據(jù)
INSERT INTO category (parent_id, name) VALUES (10, '安卓手機(jī)');
INSERT INTO category (parent_id, name) VALUES (10, '蘋果手機(jī)');
INSERT INTO category (parent_id, name) VALUES (13, '變形筆記本');
INSERT INTO category (parent_id, name) VALUES (13, '傳統(tǒng)筆記本');
INSERT INTO category (parent_id, name) VALUES (17, '辦公室連衣裙');
INSERT INTO category (parent_id, name) VALUES (17, '休閑連衣裙');
總結(jié)
本文介紹了如何使用Spring Boot結(jié)合MyBatis-Plus實(shí)現(xiàn)多層次樹結(jié)構(gòu)的異步加載策略。我們通過一個簡單的分類樹示例展示了如何在前端逐步加載節(jié)點(diǎn),避免一次性加載大量數(shù)據(jù)帶來的性能問題。希望通過這篇文章,您能夠?qū)pring Boot和MyBatis-Plus的聯(lián)合使用有更深入的理解,并能夠?qū)⑵鋺?yīng)用到實(shí)際項目中。