Springboot生成树工具类,可通过 id/code 编码生成 2.0版本
- 优化工具类中,查询父级时便利多次的问题
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.mutable.MutableLong;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;/*** 树结构工具类** @author* @date 2024/7/16 下午4:58* @description 提供树结构的构建、查询、转换等功能*/
@SuppressWarnings("unused")
public class TreeUtil {/*** 使用 ParentId 构建树结构,适用于大数据量,避免使用递归,提高性能。** @param list 所有节点的列表* @param getId 获取节点 ID 的函数* @param getParentId 获取父节点 ID 的函数* @param comparator 同级节点排序的比较器(可选)* @param setSub 设置子节点列表的函数* @param <T> 节点类型* @param <I> 节点 ID 类型* @return 树的根节点列表*/public static <T, I> List<T> buildByParentId(@NonNull List<T> list,@NonNull Function<T, I> getId,@NonNull Function<T, I> getParentId,@Nullable Comparator<T> comparator,@NonNull BiConsumer<T, List<T>> setSub) {// 1. 构建 ID 到节点的映射,方便快速查找节点Map<I, T> idNodeMap = list.stream().collect(Collectors.toMap(getId, Function.identity(), (existing, replacement) -> existing));// 2. 构建父 ID 到子节点列表的映射Map<I, List<T>> parentIdMap = new HashMap<>();for (T node : list) {I parentId = getParentId.apply(node);parentIdMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(node);}// 3. 设置每个节点的子节点列表for (T node : list) {I id = getId.apply(node);List<T> children = parentIdMap.get(id);if (children != null) {// 对子节点进行排序(如果需要)sortList(children, comparator);// 设置子节点列表setSub.accept(node, children);}}// 4. 提取根节点(父 ID 为 null 或者父 ID 不存在于节点映射中的节点)List<T> roots = list.stream().filter(node -> {I parentId = getParentId.apply(node);return parentId == null || !idNodeMap.containsKey(parentId);}).collect(Collectors.toList());// 对根节点进行排序(如果需要)sortList(roots, comparator);return roots;}/*** 对列表进行排序** @param list 要排序的列表* @param comparator 比较器(可选)* @param <T> 列表元素类型*/private static <T> void sortList(List<T> list, Comparator<T> comparator) {if (comparator != null && list != null && !list.isEmpty()) {list.sort(comparator);}}/*** 编码形式的树构建,当节点的编码不以任何其他节点编码为前缀时,该节点为根节点。* 所有节点的子节点列表必须不为 null。** @param list 所有节点的列表* @param getCode 获取节点编码的函数* @param comparator 同级节点排序的比较器(可选)* @param getSub 获取子节点列表的函数* @param setSub 设置子节点列表的函数* @param <T> 节点类型* @param <C> 节点编码类型(必须是 String 或其子类)* @return 树的根节点列表*/public static <T, C extends String> List<T> buildByCode(@NonNull List<T> list,@NonNull Function<T, C> getCode,@Nullable Comparator<T> comparator,@NonNull Function<T, List<T>> getSub,@NonNull BiConsumer<T, List<T>> setSub) {// 按照编码排序,将节点分组List<T> sortedCodeList = list.stream().sorted(Comparator.comparing(getCode)).collect(Collectors.toList());Map<C, List<T>> codeGroupMap = new HashMap<>();C flagCode = null;for (T item : sortedCodeList) {C currentCode = getCode.apply(item);if (flagCode == null || !currentCode.startsWith(flagCode)) {flagCode = currentCode;}codeGroupMap.computeIfAbsent(flagCode, k -> new ArrayList<>()).add(item);}// 构建树List<T> tree = new ArrayList<>();codeGroupMap.forEach((k, v) -> tree.add(buildNodeByCode(v, getCode, getSub, setSub)));sortTree(tree, comparator, getSub);return tree;}/*** 构建节点(编码形式),用于辅助 buildByCode 方法** @param subList 子节点列表* @param getCode 获取编码的函数* @param getSub 获取子节点列表的函数* @param setSub 设置子节点列表的函数* @param <T> 节点类型* @param <C> 编码类型* @return 构建好的节点*/private static <T, C extends String> T buildNodeByCode(List<T> subList,Function<T, C> getCode,Function<T, List<T>> getSub,BiConsumer<T, List<T>> setSub) {if (subList.isEmpty()) {throw new IllegalStateException("树构建异常:子节点列表为空");}// 反转列表,方便子节点找父节点Collections.reverse(subList);for (int i = 0; i < subList.size() - 1; i++) {T child = subList.get(i);T parent = findParentByCode(child, subList.subList(i + 1, subList.size()), getCode);List<T> children = getSub.apply(parent);if (children == null) {children = new ArrayList<>();setSub.accept(parent, children);}children.add(child);}return subList.get(subList.size() - 1);}/*** 根据编码查找父节点** @param currentNode 当前节点* @param subList 子节点列表* @param getCode 获取编码的函数* @param <T> 节点类型* @param <C> 编码类型* @return 父节点*/private static <T, C extends String> T findParentByCode(T currentNode,List<T> subList,Function<T, C> getCode) {C currentCode = getCode.apply(currentNode);for (T node : subList) {C parentCode = getCode.apply(node);if (currentCode.startsWith(parentCode) && !currentCode.equals(parentCode)) {return node;}}throw new IllegalStateException("构建异常:未找到父节点");}/*** 对树进行排序** @param tree 树的根节点列表* @param comparator 比较器* @param getSub 获取子节点列表的函数* @param <T> 节点类型*/private static <T> void sortTree(List<T> tree,Comparator<T> comparator,Function<T, List<T>> getSub) {sortList(tree, comparator);for (T node : tree) {List<T> sub = getSub.apply(node);if (sub != null && !sub.isEmpty()) {sortTree(sub, comparator, getSub);}}}/*** 获取指定节点的所有父节点** @param list 节点列表* @param ids 目标节点 ID 列表* @param idExtractor 获取节点 ID 的函数* @param parentIdExtractor 获取父节点 ID 的函数* @param containSelf 是否包含自身* @param <T> 节点类型* @param <R> ID 类型* @return 父节点列表*/public static <T, R> List<T> getParent(List<T> list,List<R> ids,Function<? super T, ? extends R> idExtractor,Function<? super T, ? extends R> parentIdExtractor,boolean containSelf) {if (CollectionUtils.isEmpty(list) || CollectionUtils.isEmpty(ids)) {return new ArrayList<>();}// 构建 ID -> 节点的映射,避免重复查找Map<R, T> idNodeMap = list.stream().collect(Collectors.toMap(idExtractor, Function.identity()));Set<R> parentIds = new HashSet<>();Deque<R> stack = new LinkedList<>(ids);while (!stack.isEmpty()) {R currentId = stack.pop();if (!parentIds.contains(currentId)) {parentIds.add(currentId);T node = idNodeMap.get(currentId);if (node != null) {R parentId = parentIdExtractor.apply(node);if (parentId != null && !parentIds.contains(parentId)) {stack.push(parentId);}}}}return list.stream().filter(node -> parentIds.contains(idExtractor.apply(node))).collect(Collectors.toList());}/*** 获取指定节点的所有子节点** @param list 节点列表* @param ids 目标节点 ID 列表* @param idExtractor 获取节点 ID 的函数* @param parentIdExtractor 获取父节点 ID 的函数* @param containSelf 是否包含自身* @param <T> 节点类型* @param <R> ID 类型* @return 子节点列表*/public static <T, R> List<T> getChildren(List<T> list,List<R> ids,Function<? super T, ? extends R> idExtractor,Function<? super T, ? extends R> parentIdExtractor,boolean containSelf) {if (CollectionUtils.isEmpty(list) || CollectionUtils.isEmpty(ids)) {return new ArrayList<>();}Map<R, T> idNodeMap = list.stream().collect(Collectors.toMap(idExtractor, Function.identity(), (existing, replacement) -> existing));Map<R, List<T>> parentIdMap = list.stream().collect(Collectors.groupingBy(parentIdExtractor));Set<R> resultIds = new HashSet<>();if (containSelf) {resultIds.addAll(ids);}Queue<R> queue = new LinkedList<>(ids);while (!queue.isEmpty()) {R parentId = queue.poll();List<T> children = parentIdMap.get(parentId);if (children != null) {for (T child : children) {R childId = idExtractor.apply(child);if (!resultIds.contains(childId)) {resultIds.add(childId);queue.add(childId);}}}}return list.stream().filter(node -> resultIds.contains(idExtractor.apply(node))).collect(Collectors.toList());}/*** 在树中搜索所有符合条件的节点** @param tree 树的根节点列表* @param getKey 获取节点属性的函数* @param getSub 获取子节点列表的函数* @param key 要匹配的属性值* @param <T> 节点类型* @param <I> 属性值类型* @return 符合条件的节点列表*/public static <T, I> List<T> searchTree4All(@NonNull List<T> tree,@NonNull Function<T, I> getKey,@NonNull Function<T, List<T>> getSub,@NonNull I key) {List<T> matched = new ArrayList<>();Queue<T> queue = new LinkedList<>(tree);while (!queue.isEmpty()) {T node = queue.poll();I nodeKey = getKey.apply(node);if (nodeKey != null && nodeKey.equals(key)) {matched.add(node);}List<T> sub = getSub.apply(node);if (sub != null && !sub.isEmpty()) {queue.addAll(sub);}}return matched;}/*** 在树中搜索第一个符合条件的节点** @param tree 树的根节点列表* @param getKey 获取节点属性的函数* @param getSub 获取子节点列表的函数* @param key 要匹配的属性值* @param <T> 节点类型* @param <I> 属性值类型* @return 符合条件的节点(Optional)*/public static <T, I> Optional<T> searchTree4One(@NonNull List<T> tree,@NonNull Function<T, I> getKey,@NonNull Function<T, List<T>> getSub,@NonNull I key) {Queue<T> queue = new LinkedList<>(tree);while (!queue.isEmpty()) {T node = queue.poll();I nodeKey = getKey.apply(node);if (nodeKey != null && nodeKey.equals(key)) {return Optional.of(node);}List<T> sub = getSub.apply(node);if (sub != null && !sub.isEmpty()) {queue.addAll(sub);}}return Optional.empty();}/*** 将树转换为列表** @param tree 树的根节点列表* @param getSub 获取子节点列表的函数* @param <T> 节点类型* @return 展开的节点列表*/public static <T> List<T> tree2List(@NonNull List<T> tree,@NonNull Function<T, List<T>> getSub) {List<T> list = new ArrayList<>();Queue<T> queue = new LinkedList<>(tree);while (!queue.isEmpty()) {T node = queue.poll();list.add(node);List<T> sub = getSub.apply(node);if (sub != null && !sub.isEmpty()) {queue.addAll(sub);}}return list;}/*** 为树节点添加随机 ID** @param tree 树的根节点列表* @param getSub 获取子节点列表的函数* @param setId 设置节点 ID 的函数* @param setParentId 设置父节点 ID 的函数* @param parentId 初始父节点 ID(根节点的父 ID,一般为 0 或 null)* @param idCounter ID 计数器(可选)* @param <T> 节点类型*/public static <T> void addRandomId(@NonNull List<T> tree,@NonNull Function<T, List<T>> getSub,@NonNull BiConsumer<T, Long> setId,@NonNull BiConsumer<T, Long> setParentId,@Nullable Long parentId,@Nullable MutableLong idCounter) {if (idCounter == null) {idCounter = new MutableLong(1L);}if (parentId == null) {parentId = 0L;}Queue<T> queue = new LinkedList<>(tree);Map<T, Long> parentMap = new HashMap<>();while (!queue.isEmpty()) {T node = queue.poll();long id = idCounter.longValue();idCounter.increment();setId.accept(node, id);setParentId.accept(node, parentMap.getOrDefault(node, parentId));List<T> sub = getSub.apply(node);if (sub != null && !sub.isEmpty()) {for (T child : sub) {parentMap.put(child, id);queue.add(child);}}}}/*** 根据名称过滤树节点** @param tree 树的根节点列表* @param getSub 获取子节点列表的函数* @param getName 获取节点名称的函数* @param searchName 要搜索的名称* @param reserveChild 父节点匹配时是否保留所有子节点* @param <T> 节点类型*/public static <T> void filterTreeByName(@NonNull List<T> tree,@NonNull Function<T, List<T>> getSub,@NonNull Function<T, String> getName,@NonNull String searchName,@NonNull Boolean reserveChild) {if (!StringUtils.hasLength(searchName)) {return;}Queue<T> queue = new LinkedList<>(tree);while (!queue.isEmpty()) {T node = queue.poll();String name = getName.apply(node);List<T> sub = getSub.apply(node);if (reserveChild && StringUtils.hasLength(name) && name.contains(searchName)) {continue;}if (sub != null && !sub.isEmpty()) {filterTreeByName(sub, getSub, getName, searchName, reserveChild);}if ((sub == null || sub.isEmpty()) && (name == null || !name.contains(searchName))) {tree.remove(node);}}}/*** 根据 ID 过滤树节点** @param tree 树的根节点列表* @param getSub 获取子节点列表的函数* @param getId 获取节点 ID 的函数* @param searchId 要搜索的 ID* @param reserveChild 父节点匹配时是否保留所有子节点* @param <T> 节点类型*/public static <T> void filterTreeById(@NonNull List<T> tree,@NonNull Function<T, List<T>> getSub,@NonNull Function<T, Long> getId,@NonNull Long searchId,@NonNull Boolean reserveChild) {Queue<T> queue = new LinkedList<>(tree);while (!queue.isEmpty()) {T node = queue.poll();Long id = getId.apply(node);List<T> sub = getSub.apply(node);if (reserveChild && id != null && id.equals(searchId)) {continue;}if (sub != null && !sub.isEmpty()) {filterTreeById(sub, getSub, getId, searchId, reserveChild);}if ((sub == null || sub.isEmpty()) && (id == null || !id.equals(searchId))) {tree.remove(node);}}}
}
相关文章:
Springboot生成树工具类,可通过 id/code 编码生成 2.0版本
优化工具类中,查询父级时便利多次的问题 import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.mutable.MutableLong; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.spri…...
17、CPU缓存架构详解高性能内存队列Disruptor实战
1.CPU缓存架构详解 1.1 CPU高速缓存概念 CPU缓存即高速缓冲存储器,是位于CPU与主内存间的一种容量较小但速度很高的存储器。CPU高速缓存可以分为一级缓存,二级缓存,部分高端CPU还具有三级缓存,每一级缓存中所储存的全部数据都是…...
算法训练营打卡Day18
目录 二叉搜索树的最小绝对差二叉搜索树中的众数二叉树的最近公共祖先额外练手题目 题目1、二叉搜索树的最小绝对差 力扣题目链接(opens new window) 给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。 示例: 思…...
【leetcode】169.多数元素
boyer-moore算法最简单理解方法: 假设你在投票选人 如果你和候选人(利益)相同,你就会给他投一票(count1),如果不同,你就会踩他一下(count-1)当候选人票数为0&…...
MyBatis<foreach>标签的用法与实践
foreach标签简介 实践 demo1 简单的一个批量更新,这里传入了一个List类型的集合作为参数,拼接到 in 的后面 ,来实现一个简单的批量更新 <update id"updateVislxble" parameterType"java.util.List">update model…...
R语言Shiny包新手教程
R语言Shiny包新手教程 1. 简介 Shiny 是一个 R 包,用于创建交互式网页应用。它非常适合展示数据分析结果和可视化效果。 2. 环境准备 安装R和RStudio 确保你的计算机上安装了 R 和 RStudio。你可以从 CRAN 下载 R,或从 RStudio 官网 下载 RStudio。…...
[大象快讯]:PostgreSQL 17 重磅发布!
家人们,数据库界的大新闻来了!📣 PostgreSQL 17 正式发布,全球开发者社区的心血结晶,带来了一系列令人兴奋的新特性和性能提升。 发版通告全文如下 PostgreSQL 全球开发小组今天(2024-09-26)宣布…...
CHI trans--Home节点发起的操作
总目录: CHI协议简读汇总-CSDN博客https://blog.csdn.net/zhangshangjie1/article/details/131877216 Home节点能够发起的操作,包含如下几类: Home to Subordinate Read transactionsHome to Subordinate Write transactionsHome to Subor…...
Rust和Go谁会更胜一筹
在国内,我认为Go语言会成为未来的主流,因为国内程序员号称码农,比较适合搬砖,而Rust对心智要求太高了,不适合搬砖。 就个人经验来看,Go语言简单,下限低,没有什么心智成本,…...
记HttpURLConnection下载图片
目录 一、示例代码1 二、示例代码2 一、示例代码1 import java.io.*; import java.net.HttpURLConnection; import java.net.URL;public class Test {/*** 下载图片*/public void getNetImg() {InputStream inStream null;FileOutputStream fOutStream null;try {// URL 统…...
物联网实训室建设的必要性
物联网实训室建设的必要性 一、物联网发展的背景 物联网(IoT)是指通过信息传感设备,按照约定的协议,将任何物品与互联网连接起来,进行信息交换和通信,以实现智能化识别、定位、跟踪、监控和管理的一种网络…...
初识C语言(四)
目录 前言 十一、常见关键字(补充) (1)register —寄存器 (2)typedef类型重命名 (3)static静态的 1、修饰局部变量 2、修饰全局变量 3、修饰函数 十二、#define定义常量和宏…...
产品架构图:从概念到实践
在当今快速发展的科技时代,产品架构图已成为产品经理和设计师不可或缺的工具。它不仅帮助我们理解复杂的产品体系,还能指导我们进行有效的产品设计和开发。本文将深入探讨产品架构图的概念、重要性以及绘制方法。 整个内容框架分为三个部分,…...
smartctl 命令:查看硬盘健康状态
一、命令简介 smartctl 命令用于获取硬盘的 SMART 信息。 介绍硬盘SMART 硬盘的 SMART (Self-Monitoring, Analysis, and Reporting Technology) 技术用于监控硬盘的健康状态,并能提供一些潜在故障的预警信息。通过查看 SMART 数据,用户可以了解硬…...
BBR 为什么没有替代 CUBIC 成为 Linux 内核缺省算法
自 2017 年底 bbr 发布以来,随着媒体的宣讲,各大站点陆续部署 bbr,很多网友不禁问,bbr 这么好,为什么不替代 cubic 成为 linux 的缺省算法。仅仅因为它尚未标准化?这么好的算法又为什么没被标准化ÿ…...
Git忽略规则原理和.gitignore文件不生效的原因和解决办法
在使用Git进行版本控制时,.gitignore文件扮演着至关重要的角色。它允许我们指定哪些文件或目录应该被Git忽略,从而避免将不必要的文件(如日志文件、编译产物等)纳入版本控制中。然而,在实际使用过程中,有时…...
MySQL-数据库设计
1.范式 数据库的范式是⼀组规则。在设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数 据库,这些不同的规范要求被称为不同的范式。 关系数据库有六种范式:第⼀范式(1NF)、第⼆范式(…...
Unity开发绘画板——04.笔刷大小调节
笔刷大小调节 上面的代码中其实我们已经提供了笔刷大小的字段,即brushSize,现在只需要将该字段和界面中的Slider绑定即可,Slider值的范围我们设置为1~20 代码中只需要做如下改动: public Slider brushSizeSlider; //控制笔刷大…...
./mnt/container_run_medium.sh
#!/bin/bash# 清理旧的日志文件 rm -f *.log rm -f nohup.out rm -f cssd.dat# 启动 pwbox_simu 和 MediumBoxBase nohup /mnt/simutools/pwbox_simu /mnt/simutools/pw_box.conf > /dev/null 2>&1 & nohup /mnt/mediumSimu/MediumBoxBase /mnt/mediumSimu/hynn_…...
数学建模研赛总结
目录 前言进度问题四分析问题五分析数模论文经验分享总结 前言 本文为博主数学建模比赛第五天的内容记录,希望所写的一些内容能够对大家有所帮助,不足之处欢迎大家批评指正🤝🤝🤝 进度 今天已经是最后一天了…...
通信工程学习:什么是TCF技术控制设施
TCF(Technical Control Facility):技术控制设施 首先,需要明确的是,通信工程是一门涉及电子科学与技术、信息与通信工程和光学工程学科领域的基础理论、工程设计及系统实现技术的学科。它主要关注通信过程中的信息传输…...
stm32 bootloader跳转程序设计
文章目录 1、bootloader跳转程序设计(1)跳转程序(2)、app程序中需要注意<1>、在keil中ROM起始地址和分配的空间大小<2>、在system_stm32f4xx.c中设置VECT_TAB_OFFSET为需要偏移的地址<3>、main函数中使能中断 总…...
科技赋能环保:静电与光解技术在油烟净化中的卓越应用
我最近分析了餐饮市场的油烟净化器等产品报告,解决了餐饮业厨房油腻的难题,更加方便了在餐饮业和商业场所有需求的小伙伴们。 随着环保政策的不断升级,餐饮行业的油烟治理成为重要课题。油烟净化器的技术革新不仅提升了净化效率,…...
FCA-FineBI试卷答案
1、【判断题】FineBI数据加工建模中只支持文本、数值、日期三种数据类型。 正确答案:A A. 正确 B. 错误 2、【判断题】Excel 支持批量导入,可以一次导入多个 sheet 或 Excel? 正确答案:A A.正确 B. 错误 3、【判断题】FineBI V6.…...
Spring - @Import注解
文章目录 基本用法源码分析ConfigurationClassPostProcessorConfigurationClass SourceClassgetImportsprocessImports处理 ImportSelectorImportSelector 接口DeferredImportSelector 处理 ImportBeanDefinitionRegistrarImportBeanDefinitionRegistrar 接口 处理Configuratio…...
新能源汽车储充机器人:能源高效与智能调度
新能源汽车储充机器人:开启能源高效利用与智能调度的未来之门 随着全球能源危机的日益加剧和环境污染问题的不断恶化,新能源汽车成为了未来交通领域的重要发展方向。然而,新能源汽车的普及不仅需要解决电池技术的瓶颈,还需要构建一…...
【Linux网络】详解TCP协议(2)
🎉博主首页: 有趣的中国人 🎉专栏首页: Linux网络 🎉其它专栏: C初阶 | C进阶 | 初阶数据结构 小伙伴们大家好,本片文章将会讲解 TCP协议的三次握手和四次挥手 的相关内容。 如果看到最后您觉得…...
STM32DMA学习日记
STM32 DMA学习日记 写于2024/9/28晚 文章目录 STM32 DMA学习日记1. DMA简介2. I/O方式2.1 程序查询方式2.2 程序中断方式2.3 DMA方式 3.DMA框图4. 相关寄存器4.1 DMA中断状态寄存器(DMA_ISR)4.2 DMA中断标志清除寄存器(DMA_IFCR)…...
【高性能内存池】page cache 5
page cache 1 page cache的框架2 central cache从page cache申请n页span的过程3 page cache 的结构3.1 page cache类框架3.2 central cache向page cache申请span3.3 获取k页的span page cache的结构和central cache是一样的,都是哈希桶的结构,并且挂载的…...
Vue 3 魔法揭秘:CSS 解析与 scoped 背后的奇幻之旅
文章目录 一、背景二、源码分析transformMain 返回值transformStyle 方法compileStyleAsync 方法scopedPlugin 方法template 添加 __scopeId 三、总结 一、背景 Vue 3 文件编译流程详解与 Babel 的使用 上文分析了 vue3 的编译过程,但是在对其中样式的解析遗留了一…...
网站建设如何不被忽悠/各大网站收录查询
1.Intent:一个Intent就是对一次将要执行的操作的抽象描述 2.拨打电话,发送短信 3.启动新的Activity,传递参数和返回参数...
亿网通官网/seo排名点击 seo查询
https://blog.csdn.net/DepressedPrince/article/details/80909636 https://blog.csdn.net/chjunjun/article/details/80698710转载于:https://www.cnblogs.com/jianxingjianyuan/p/10815550.html...
网站设计费用志/网络营销的几种模式
域名泛解析什么意思 在域名前添加任何子域名,均可访问到所指向的网站。也就是客户的域名yfi6.com之下所设的*.yfi6.com全部域名均可访问。 域名泛解析怎么设置 泛域名解析是指将*.域名解析到同一IP。 泛域名解析和域名解析有何不同? 泛域名解析是指&a…...
宁晋网站建设/个人主页网页设计模板
SpringBoot 提及SpringBoot,想必大家脑海中第一时间浮现的应该是它“约定大于配置”的特性。作为Spring亲儿子的它整合了很多可插拔的组件(框架),内嵌了使用工具(比如内嵌了Tomcat、Jetty等),非…...
虎丘网站建设/优化方案怎么写
JPA是什么 Java Persistence API:用于对象持久化的API Java EE 5.0平台标准的ORM规范,使得应用程序以统一的方式访问持久层。 JPA和Hibernate的关系 JPA是Hibernate的一个抽象(就像JDBC和JDBC驱动的关系): 1)JPA是规范…...
网站建设的客户需求分析调研表/郑州搜狗关键词优化顾问
出品 | 51Testing软件测试网Robotframework框架是一个通用的测试框架,他的其中的一个特性是功能全面,能够支持进行Web自动化测试(通过导入selenium相关测试库)、GUI测试、接口测试等。本文主要介绍,在Robotframework测试框架上,实…...