SpringBoot?+?MyBatis-Plus構(gòu)建樹形結(jié)構(gòu)的幾種方式
1. 樹形結(jié)構(gòu)
樹形結(jié)構(gòu),是指:數(shù)據(jù)元素之間的關(guān)系像一顆樹的數(shù)據(jù)結(jié)構(gòu)。由樹根延伸出多個(gè)樹杈
它具有以下特點(diǎn):
- 每個(gè)節(jié)點(diǎn)都只有有限個(gè)子節(jié)點(diǎn)或無子節(jié)點(diǎn);
- 沒有父節(jié)點(diǎn)的節(jié)點(diǎn)稱為根節(jié)點(diǎn);
- 每一個(gè)非根節(jié)點(diǎn)有且只有一個(gè)父節(jié)點(diǎn);
- 除了根節(jié)點(diǎn)外,每個(gè)子節(jié)點(diǎn)可以分為多個(gè)不相交的子樹;
- 樹里面沒有環(huán)路(cycle)
2. 常見問題
在實(shí)際開發(fā)中,很多數(shù)據(jù)都是樹形結(jié)構(gòu),例如:地區(qū)、頁面上的菜單、上下級(jí)關(guān)系的組織等等,這時(shí)就需要我們從數(shù)據(jù)源中讀取到數(shù)據(jù),通過某些方式拼成樹形結(jié)構(gòu) 然后再給前端展示。對(duì)于一些不經(jīng)常變化且使用頻繁的數(shù)據(jù),可以考慮將拼好的樹形結(jié)構(gòu)數(shù)據(jù)放入緩存,每次用的時(shí)候直接讀取出來就可以使用。
3. 準(zhǔn)備環(huán)境
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('堯都區(qū)', '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('朝陽區(qū)', 'district', 5); INSERT into t_region(name, region_type, parent_id) VALUES('太原市', 'city', 1); INSERT into t_region(name, region_type, parent_id) VALUES('小店區(qū)', '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: #定義數(shù)據(jù)源 #127.0.0.1為本機(jī)測(cè)試的ip,3306是mysql的端口號(hào)。serverTimezone是定義時(shí)區(qū),照抄就好,mysql高版本需要定義這些東西 #useSSL也是某些高版本mysql需要問有沒有用SSL連接 url: jdbc:mysql://192.168.1.141:3306/db_user?serverTimezone=GMT%2B8&useSSL=FALSE username: root #數(shù)據(jù)庫用戶名,root為管理員 password: 123456 #該數(shù)據(jù)庫用戶的密碼 # 使用druid數(shù)據(jù)源 type: com.alibaba.druid.pool.DruidDataSource mybatis-plus: # xml掃描,多個(gè)目錄用逗號(hào)或者分號(hào)分隔(告訴 Mapper 所對(duì)應(yīng)的 XML 文件位置) mapper-locations: classpath:mapper/*.xml # 以下配置均有默認(rèn)值,可以不設(shè)置 global-config: db-config: #主鍵類型 AUTO:"數(shù)據(jù)庫ID自增" INPUT:"用戶輸入ID",ID_WORKER:"全局唯一ID (數(shù)字類型唯一ID)", UUID:"全局唯一ID UUID"; id-type: auto #數(shù)據(jù)庫類型 db-type: MYSQL configuration: # 是否開啟自動(dòng)駝峰命名規(guī)則映射:從數(shù)據(jù)庫列名到Java屬性駝峰命名的類似映射 map-underscore-to-camel-case: true # 如果查詢結(jié)果中包含空值的列,則 MyBatis 在映射的時(shí)候,不會(huì)映射這個(gè)字段 call-setters-on-nulls: true # 這個(gè)配置會(huì)將執(zhí)行的sql打印出來,在開發(fā)或測(cè)試的時(shí)候可以用 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
表對(duì)應(yīng)的實(shí)體類
@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; }
返回給前端的實(shí)體類
@Data public class RegionVO { private Long id; /** * 名稱 */ private String name; /** * 類型 */ private String regionType; /** * 父id */ private Long parentId; private List<RegionVO> children; }
4.實(shí)現(xiàn)方式
1.基于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(); } }
這種方式按照上邊添加的數(shù)據(jù)量(8條)共執(zhí)行了9次查詢
2.LambdaQueryWrapper
RegionServiceImpl
添加如下代碼
@Override public List<RegionVO> getAllWrapper(){ LambdaQueryWrapper<Region> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Region::getParentId, 0); //查詢根級(jí) 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()); //根據(jù)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不為空,繼續(xù)往下找 if (!CollectionUtils.isEmpty(voList)) { voList.forEach(this::getChildren); } }
這種方式按照上邊添加的數(shù)據(jù)量(8條)共執(zhí)行了9次查詢
3.遞歸方法
RegionServiceImpl
添加如下代碼
@Override public List<RegionVO> build(){ //一次把所有的數(shù)據(jù)都查出來 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()); //指定根節(jié)點(diǎn)的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)) { //遞歸,自己調(diào)自己 item.setChildren(buildChildren(item.getId(), allList)); voList.add(item); } } return voList; }
這種就不必說了,一次查詢所有數(shù)據(jù)出來,一共執(zhí)行一次查詢
5.總結(jié)
查詢方式有很多,應(yīng)該使用哪種需要猿們結(jié)合具體情況選擇。
第一種情況:當(dāng)整體數(shù)據(jù)量特別大 層級(jí)不深 需要按照某個(gè)根節(jié)點(diǎn)查詢時(shí),推薦使用第一、二種方式。第二種情況:當(dāng)需要查詢整個(gè)樹時(shí),推薦使用第三種方式。
到此這篇關(guān)于SpringBoot + MyBatis-Plus構(gòu)建樹形結(jié)構(gòu)的幾種方式的文章就介紹到這了,更多相關(guān)SpringBoot MyBatisPlus樹形結(jié)構(gòu)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Maven創(chuàng)建項(xiàng)目過慢的4種解決辦法
最近經(jīng)常會(huì)遇到一個(gè)困擾,那就是用idea創(chuàng)建maven項(xiàng)目時(shí),速度很慢,本文就來介紹一下Maven創(chuàng)建項(xiàng)目過慢的4種解決辦法,感興趣的可以了解一下2021-12-12Easyui的combobox實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)級(jí)聯(lián)效果
這篇文章主要介紹了Easyui的combobox實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)級(jí)聯(lián)效果的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-06-06java對(duì)接微信小程序詳細(xì)流程(登錄&獲取用戶信息)
這篇文章主要給大家介紹了關(guān)于java對(duì)接微信小程序(登錄&獲取用戶信息)的相關(guān)資料,我們?cè)陂_發(fā)微信小程序時(shí)經(jīng)常需要獲取用戶微信用戶名以及頭像信息,微信提供了專門的接口API用于返回這些信息,需要的朋友可以參考下2023-08-08Java本地高性能緩存的幾種常見實(shí)現(xiàn)方式
在Java中緩存是一種常用的性能優(yōu)化技術(shù),用于在應(yīng)用程序中加速訪問和查詢數(shù)據(jù)的速度,下面這篇文章主要給大家介紹了關(guān)于Java本地高性能緩存的幾種常見實(shí)現(xiàn)方式,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07Java類的繼承實(shí)例詳解(動(dòng)力節(jié)點(diǎn)Java學(xué)院整理)
在Java開發(fā)中,我們常常用到繼承這一概念,可以說繼承是Java這類面向?qū)ο缶幊陶Z言的基石,今天小編一起和大家一起學(xué)習(xí)java類的繼承2017-04-04