SpringBoot?+?MyBatis-Plus構(gòu)建樹形結(jié)構(gòu)的幾種方式
1. 樹形結(jié)構(gòu)
樹形結(jié)構(gòu),是指:數(shù)據(jù)元素之間的關(guān)系像一顆樹的數(shù)據(jù)結(jié)構(gòu)。由樹根延伸出多個樹杈
它具有以下特點:
- 每個節(jié)點都只有有限個子節(jié)點或無子節(jié)點;
- 沒有父節(jié)點的節(jié)點稱為根節(jié)點;
- 每一個非根節(jié)點有且只有一個父節(jié)點;
- 除了根節(jié)點外,每個子節(jié)點可以分為多個不相交的子樹;
- 樹里面沒有環(huán)路(cycle)
2. 常見問題
在實際開發(fā)中,很多數(shù)據(jù)都是樹形結(jié)構(gòu),例如:地區(qū)、頁面上的菜單、上下級關(guān)系的組織等等,這時就需要我們從數(shù)據(jù)源中讀取到數(shù)據(jù),通過某些方式拼成樹形結(jié)構(gòu) 然后再給前端展示。對于一些不經(jīng)常變化且使用頻繁的數(shù)據(jù),可以考慮將拼好的樹形結(jié)構(gòu)數(shù)據(jù)放入緩存,每次用的時候直接讀取出來就可以使用。
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為本機測試的ip,3306是mysql的端口號。serverTimezone是定義時區(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掃描,多個目錄用逗號或者分號分隔(告訴 Mapper 所對應(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: # 是否開啟自動駝峰命名規(guī)則映射:從數(shù)據(jù)庫列名到Java屬性駝峰命名的類似映射 map-underscore-to-camel-case: true # 如果查詢結(jié)果中包含空值的列,則 MyBatis 在映射的時候,不會映射這個字段 call-setters-on-nulls: true # 這個配置會將執(zhí)行的sql打印出來,在開發(fā)或測試的時候可以用 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
表對應(yīng)的實體類
@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; }
返回給前端的實體類
@Data public class RegionVO { private Long id; /** * 名稱 */ private String name; /** * 類型 */ private String regionType; /** * 父id */ private Long parentId; private List<RegionVO> children; }
4.實現(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); //查詢根級 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é)點的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ù)量特別大 層級不深 需要按照某個根節(jié)點查詢時,推薦使用第一、二種方式。第二種情況:當(dāng)需要查詢整個樹時,推薦使用第三種方式。
到此這篇關(guān)于SpringBoot + MyBatis-Plus構(gòu)建樹形結(jié)構(gòu)的幾種方式的文章就介紹到這了,更多相關(guān)SpringBoot MyBatisPlus樹形結(jié)構(gòu)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Easyui的combobox實現(xiàn)動態(tài)數(shù)據(jù)級聯(lián)效果
這篇文章主要介紹了Easyui的combobox實現(xiàn)動態(tài)數(shù)據(jù)級聯(lián)效果的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-06-06java對接微信小程序詳細(xì)流程(登錄&獲取用戶信息)
這篇文章主要給大家介紹了關(guān)于java對接微信小程序(登錄&獲取用戶信息)的相關(guān)資料,我們在開發(fā)微信小程序時經(jīng)常需要獲取用戶微信用戶名以及頭像信息,微信提供了專門的接口API用于返回這些信息,需要的朋友可以參考下2023-08-08Java類的繼承實例詳解(動力節(jié)點Java學(xué)院整理)
在Java開發(fā)中,我們常常用到繼承這一概念,可以說繼承是Java這類面向?qū)ο缶幊陶Z言的基石,今天小編一起和大家一起學(xué)習(xí)java類的繼承2017-04-04