实现复杂树结构返回(不含子树), 并且结点间建立关联

💡 一句话结:
实现传感器和深度及采集的数值动态对应,将不规则的数据转变成固定列头的一行行数据。

🔑 关键信息点:

  1. 通过传感器编号和深度将传感器对应的数值与时间建立关联。
  2. 使用SpringBoot+MyBatis框架实现动态查询不同参数条件下的传感器数据。
  3. 通过动态SQL实现各级数据项的对应查询,动态生成不同条件对应的字段。
  4. 利用HashMap和Set等数据结构处理数据并建立传感器编号和深度的对应关系。
  5. 提供两个版本的代码实现,一个使用Map,一个使用Set,实现对应关系和数据处理。

对应关系分析(掺杂业务逻辑)

如图 简明来讲就是实现传感器和深度及采集的数值动态对应
因为硬件设备的限制, 在数据采集及通过MQ到库中时数据是一个传感器对应一个深度对应一个该传感器的采集值, 我们要做的是列出所有传感器, 以时间为索引, 时间和传感器对应上一个值简单来说就是把一列列不规则的数据, 转变成固定列头的一行行数据
在这里插入图片描述
数据字段返回(简单以json格式展示)
在这里插入图片描述
:这样就对应上标题了tableData有多组, 但是每一组的数值还和日期时间树的结点能对应上, 可以变向理解为tabledata中含有子树(截图比较抽象)

实现

思路: 使用springboot+mybatis框架
mybatis可以动态接收不同参数 (传感器点位,传感器深度, 传感器类型[温湿度]等) 筛选后的传感器编号,并查询匹配传感器对应的数据
返回格式的转化以及查询后格式的完善在业务层完成
效果比对: 上面为原格式没有明显对应关系,下面为转换后的
在这里插入图片描述

Dao层(mapper)

首先是先从SQL实现各级数据项对应, 并实现使用mybatis框架动态接收符合条件的各组传感器
注意使用的resultType不是直接使用的resultMap
这儿需要使用动态SQL, 先使用传感器编号查询接口, 查询出符合条件的传感器编号, 然后动态赋值到case…when中的相关字段, 动态生成不同条件对应的case…when字段进行第二次具体数值的获取查询

  <select id="" resultType="map">
    select
      to_char(detection_time , 'yyyy/MM/dd') as 日期,
      to_char(detection_time , 'hh24:mi:ss') as 时间
      <choose>
        <when test="sensorCodes != null and sensorCodes.size() != 0">
          <foreach item="sensorCode" collection="sensorCodes" separator="">
          <if test=" sensorCode != null and sensorCode != ''">
            ,max(case when sensor_code = '${sensorCode}' then humidity   end) as "${sensorCode}"
          </if>

          </foreach>
        </when>
        <otherwise>
            ,'没有与之对应的传感器编号'
        </otherwise>
      </choose>
      FROM structure_internal_humidity_info
    WHERE 1=1
    <if test="startDate != null">
      AND date_format(detection_time, '%Y-%m-%d %H:%i:%s') <![CDATA[ >= ]]> ${startDate}
    </if>
    <if test="endDate != null">
      AND date_format(detection_time, '%Y-%m-%d %H:%i:%s') <![CDATA[ <= ]]> ${endDate}
    </if>
    <if test="observationPointId != null">
      AND observation_point_id = ${observationPointId}
    </if>
    <if test="sensorCodes != null and sensorCodes.size() > 0">
      AND sensor_code IN
      <foreach item="sensorCode" collection="sensorCodes" open="(" separator="," close=")">
        '${sensorCode}'
      </foreach>
    </if>
    GROUP BY
    detection_time
    ORDER BY
    detection_time
    <!--<if test=" pageSize != null and pageSize != ''">-->
    <!--  limit #{pageSize}-->
    <!--</if>-->
    <!--<if test="pageIndex != null and pageIndex != ''">-->
    <!--  offset #{pageIndex}-->
    <!--</if>-->
  </select>

为什么注掉了offset分页, 因为业务代码中使用了mybatis分页, 这样更灵活的解决了分页, 但是不可避免的可能会有Map类或者ArrayList类中会有方法触发意料之外的bug, 留存注释备用! [ 稳妥起见还是不注释掉使用数据库原生的分页不容易踩坑 ]

这儿牵扯到一个子接口根据参数动态查询对应传感器sensorCode 要实现的就是根据参数动态匹配相关的传感器数据, 可以理解为根据不同的位置坐标作为查询条件, 获取不同位置的一组传感器编号

业务实现
分两个版本分别使用java的Map和Set作为主要数据结构进行数据处理hashMap返回, 逻辑图及各数据结构之间的转换后续出图传过来, 时间有限先码上
在这里插入图片描述

版本一

未使用Set

{
	//public CommonPage<Map<String, Object>> queryNewStandardlist(CommonQuery query) {
		List<SensorDeviceInfoPO> sensorCodes = new ArrayList<SensorDeviceInfoPO>();
		if (!(query.getMonitoringDataTypes().equals("STRUCTURE_INTERNAL_HUMIDITY_INFO"))){
			sensorCodes = sensorDeviceInfoMapper.querySensorCodeByDeviceid( query);
		}else {
			throw new BizException(BizCodeEnum.BIZ_FAILURE, "无效的类型");
		}

		List<String> sensorsList = new ArrayList<String>();
		List<BigDecimal> depthList = new ArrayList<BigDecimal>();

		//声明一个变量存储深度和传感器编号,key为传感器编号,value为传感器对应的深度
		Map<String,BigDecimal> sensorDepthMap = new HashMap<String,BigDecimal>();

		for (SensorDeviceInfoPO sensor : sensorCodes) {
			String sensorCode = sensor.getSensorCode();
			BigDecimal depth = sensor.getDepth();
			// 因为有传感器编号为空的情况所以添加非空判断去除编号为空的传感器从而对齐横列和纵列表格数据(后续 如果数据库更新后没有此情况可删除此段代码中的判断)
			if ( ! (sensorCode.isEmpty() || sensorCode.isBlank() ) && ! (depth == null || depth.compareTo(BigDecimal.ZERO) == 0) ){
				//用于结果集查询
				sensorsList.add(sensorCode);
				depthList.add(depth);
				//用于列头传感器和深度值匹配处理
				sensorDepthMap.put(sensorCode,depth);
			}

		}

		query.setSensorCodes(sensorsList);
		int total = 0;
		//TODO 另一种逻辑如果,没有分页条件则不查询总条数(需要优化)
		if (query.getPageIndex() != null || query.getPageSize() != null) {
			QueryBackUp backupQuery = new QueryBackUp(query.getPageIndex(), query.getPageSize()); // 使用Query类的拷贝构造函数创建备份对象
			query.setPageIndex(null);
			query.setPageSize(null);
			total = structureInternalHumidityInfoMapper.queryStandardColumn(query).size();
			query.setPageIndex(backupQuery.getPageIndex());
			query.setPageSize(backupQuery.getPageSize());
		}


		query.setStartDate(
				Optional.ofNullable(query.getStartDate()).orElse(Date.from(LocalDate.now().minusDays(2).atStartOfDay().toInstant(ZoneOffset.ofHours(8)))));
		query.setEndDate(
				Optional.ofNullable(query.getEndDate()).orElse(Date.from(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.ofHours(8)))));
		query.setPageSize(Optional.ofNullable(query.getPageSize()).orElse(16));
		query.setPageIndex(Optional.ofNullable(query.getPageIndex()).orElse(1));
// Page<Map<String, String>> standardResult = PageHelper.startPage(query.getPageIndex(), query.getPageSize()).doSelectPage(() -> {
// Page<Map<String, String>> standardResult = PageHelper.startPage(query.getPageIndex(), query.getPageSize()).doSelectPage(
	// 	new PageRowBounds<Map<String, String>>() {
	// @Override
	// public List<Map<String, String>> doResult(List<Map<String, String>> list) {
	// 	return list != null ? list : Collections.emptyList();
	// }
    //        });

    // List<Map<String, String>> mapperResult =

		// Page<Map<String, String>> standardResult = PageHelper.startPage(query.getPageIndex(), query.getPageSize()).doSelectPage(() ->
		// 	structureInternalHumidityInfoMapper.queryStandardColumn(query));

			// structureInternalHumidityInfoMapper.queryStandardColumn(query);
//     if (mapperResult == null) {
//         mapperResult = Collections.emptyList();
//     }
// });
    		List<Map<String, String>>  standardResult = structureInternalHumidityInfoMapper.queryStandardColumn(query);

		//表格内容用于返回
		// standardResult.getResult();
		List<String> columnSensorCode = new ArrayList<String>();
		List<BigDecimal> columnDepth = new ArrayList<>();
		List<List<String>> tableData = new ArrayList<List<String>>();

		if (!standardResult.isEmpty()) {
			/* 列头排序及传感器与深度动态匹配及传感器与深度动态匹配及传感器与深度动态匹配BEGIN */
			//列头处理, 以及处理列头因为数据结构差异y引发的列头汉字有时候不是排在第一个的问题
			Map<String, String> columnCodeMap = standardResult.get(0);
			//做到编号与深度匹配
			for (Entry<String, String> columnCode : columnCodeMap.entrySet()) {
				//传感器编号
				Object columnCodeValue = columnCode.getKey();
				//传感器深度
				String columnDepthValue = String.valueOf(sensorDepthMap.get(columnCode.getKey()));

				if ( ! (columnDepthValue.isEmpty() || columnDepthValue.isBlank() || columnDepthValue.equals("") || columnDepthValue == null
					|| columnCodeValue.equals("") || columnCodeValue == null) ){
					columnSensorCode.add(String.valueOf(columnCodeValue));

					//sensorDepthMap.put(columnCodeValue,columnDepthValue);
				}
			}
			columnSensorCode.sort(new Comparator<String>() {
				@Override
				public int compare(String o1, String o2) {
					return o1.hashCode ()- o2.hashCode();
				}
			});
			// Check if the first element is not Chinese
			if (! (columnSensorCode.get(0).equals("日期"))) {
				// Reverse the list
				Collections.reverse(columnSensorCode);

				// Swap the first and second elements
				String temp = columnSensorCode.get(0);
				columnSensorCode.set(0, columnSensorCode.get(1));
				columnSensorCode.set(1, temp);
			}
			/* 列头排序及传感器与深度动态匹配及传感器与深度动态匹配及传感器与深度动态匹配END */

			for (Map<String, String> rowDataMap : standardResult)  {

				//遍历结果中的每一行Map
				//rowDataMap  = standardResult.get(i);
				//声明一个List存储每一行Map中的目标值
					List<String> rowData = new ArrayList<String>();

				//循环遍历Map     根据列名动态  取得目标值使值与列名匹配
				for (Object columnName : columnSensorCode) {
					//todo finish   bigDecimal  处理
					Object rowDataValue = rowDataMap.get(columnName);
					//Object depthValue = rowDataMap.get()

					rowData.add(String.valueOf(rowDataValue));
					if (0 == tableData.size()){

						columnDepth.add(sensorDepthMap.get(columnName));
					}

				}
				//用于返回塞值
				tableData.add(rowData);
			}



		Map<String, Object> result = new HashMap<String, Object>();
		result.put("columnSensorCode", columnSensorCode);
		result.put("columnDepth", columnDepth);
		result.put("tableData", tableData);

		CommonPage<Map<String, Object>>  standardResultPage = new CommonPage<Map<String, Object>>();
		standardResultPage.setPageIndex(query.getPageIndex());
		standardResultPage.setPageSize(query.getPageSize());
		standardResultPage.setTotal(total);
		standardResultPage.setData(result);
//System.out.println("result = " + result);
			return standardResultPage;
		}else {
			throw new BizException(BizCodeEnum.BIZ_FAILURE, "无效的类型, 湿度结果为空");
		}

	}

版本二

使用了Set

{

	//public Map<String, Object> queryStandardTableList(CommonQuery query) {
		CommonPage<Map<String, Object>>  resultPage = new CommonPage<>();


		List<SensorDeviceInfoPO> sensorCodes = sensorDeviceInfoMapper.querySensorCodeByDeviceid(query);

		//去除List中的空字符串
		List<String>  sensorCodeList = new ArrayList<String>();
		Map<String,BigDecimal>  sensorCodeDepMap = new HashMap<>();
		for(SensorDeviceInfoPO sensorCode:sensorCodes) {
			String  sensorCodeStr = sensorCode.getSensorCode();
			BigDecimal  sensorCodeDep = sensorCode.getDepth();
			if (!(sensorCodeStr.isEmpty() && String.valueOf(sensorCodeDep).isEmpty() && sensorCodeDep == null && sensorCodeStr == null) ){
				sensorCodeList.add(sensorCodeStr);
				sensorCodeDepMap.put(sensorCodeStr,sensorCodeDep);
			}
		}

		// 使用临时变量保存原始的分页参数,避免影响查询结果
		int originPageIndex = Optional.ofNullable(query.getPageIndex()).orElse(1);
		int originPageSize = Optional.ofNullable(query.getPageSize()).orElse(18);
		
		// 设置传感器代码
		query.setSensorCodes(sensorCodeList);
		
		// 获取结果集总数
		int total = structureInternalTemperatureInfoMapper.queryStandardTableTempData(query).size();
		
		// 设置查询参数的默认值
		query.setPageIndex(originPageIndex);
		query.setPageSize(originPageSize);
		
		// 设置部分参数默认值
		query.setStartDate(
			Optional.ofNullable(query.getStartDate())
				.orElseGet(() -> Date.from(LocalDate.now().minusDays(2).atStartOfDay().toInstant(ZoneOffset.ofHours(8))))
		);
		query.setPageSize(Optional.ofNullable(query.getPageSize() ).orElse(18));
		query.setPageIndex(Optional.ofNullable(query.getPageIndex()).orElse(1));

		 //查询结果集
		 List<Map<String, String>> dynamicResults = structureInternalTemperatureInfoMapper.queryStandardTableTempData(query);

		 //使用mybatis 分页插件
		// 	AtomicReference<List<Map<String, String>>> dynamicResultsMapper = null;
		// Page<Map<String, String>> dynamicResults = PageHelper.startPage(query.getPageIndex(), query.getPageSize()).doSelectPage( () ->{
		// 			dynamicResultsMapper.set(structureInternalTemperatureInfoMapper.queryStandardTableTempData(query));
		// });

		//动态处理映射属性中的动态列名
		List<String> columnHeaders = new ArrayList<>();
		List<BigDecimal> depths = new ArrayList<>();
		List<List<String>> tableData = new ArrayList<>();
		Set<String> uniqueSensorCodeSet = new HashSet<>();

		if (!dynamicResults.isEmpty()) {
			//动态列头
			// 拼接dynamicResults.get(0)和dynamicResults.get(1)的去重值
			uniqueSensorCodeSet.addAll(dynamicResults.get(0).keySet());
			uniqueSensorCodeSet.addAll(dynamicResults.get(1).keySet());

			/*列头处理begin*/
			/* 根据传感器编码动态匹配目标值    动态结果集列头 */
			// 拼接dynamicResults.get(0)和get(1)中的不重复值作为列头, 将去重后的值作为sensorCodeRow的键
			Map<String, String> newSensorCodeRow = new HashMap<>();
			for (String uniqueKey : uniqueSensorCodeSet) {
				newSensorCodeRow.put(uniqueKey, uniqueKey);
			}

			for (Entry<String, String> entry : newSensorCodeRow.entrySet()) {
				String columnName = entry.getKey();
				// if (columnName.contains((CharSequence) dynamicResults.get(0)) || columnName.contains((CharSequence) dynamicResults.get(1))) {
					uniqueSensorCodeSet.add(columnName);
				// }
			}

			// 将不重复的列名添加到columnHeaders中
			columnHeaders.addAll(uniqueSensorCodeSet);

			//对列头进行排序从而对tableData进行排序
			// columnHeaders.sort(new Comparator<String>() {
			// 	@Override
			// 	public int compare(String o1, String o2) {
			// 		return o1.hashCode() - o2.hashCode();
			// 	}
			// });
			// 使用jdk11的排序三目运算符简洁写法但是有时会把汉字排到最后, 和上面两种排序方法哪种生效使用哪一个
			columnHeaders.sort(String::compareTo);
			 // 解决上述汉字后排问题,使用上面这种写法有时候会导致汉字排到后面面,所以使用jdk8的排序方法针对汉字进行排序让日期排第一个时间排第二个
			if (! (columnHeaders.get(0).equals("日期"))) {
				// Reverse the list
				Collections.reverse(columnHeaders);

				// Swap the first and second elements
				String temp = columnHeaders.get(0);
				columnHeaders.set(0, columnHeaders.get(1));
				columnHeaders.set(1, temp);
			}

			//根据排序后的传感器编号取得深度值的数据
			for (String senorCode : columnHeaders) {
				depths.add(sensorCodeDepMap.get(senorCode));
			}
			/*列头处理end*/

			//表格内容
			for (Map<String, String> rowDataMap : dynamicResults) {
				List<String> rowData = new ArrayList<>();

				//按照列头具体数据动态抽取表格数据
				for (String columnName : columnHeaders) {

					String cellData = rowDataMap.get(columnName);
					if (cellData == null) {
						rowData.add(""); // 如果当前行数据中没有对应的列数据,添加空字符串
					} else {
						rowData.add(cellData);
					}
				}
				tableData.add(rowData);
			}


			Map<String , Object> result = new HashMap<>();
			result.put("columnHeaders", columnHeaders);
			result.put("depth",depths);
			result.put("tableData", tableData);

			resultPage.setPageIndex(query.getPageIndex());
			resultPage.setPageSize(query.getPageSize());
			resultPage.setTotal(total);
			resultPage.setData(result);

			//return structureInternalTemperatureInfoMapper.queryStandardTableTempData(sensorCodes,startDate,endDate,observationPointId);
			// return (CommonPage<List<StructureInternalTemperatureInfoPO>>)
			return resultPage;
		}else{
			throw new BizException(BizCodeEnum.BIZ_FAILURE,"暂无内部温度数据");
		}
	}

析: 这两段代码都处理了参数筛选传感器将传感器编号传给SQL , 接受SQL返回的数据并将列头 ( 动态的传感器编号组和传感器对应的深度 ) 数据: (传感器检测的数值 ) 包装成不同的树结点返回给接收方
上面提到的有没有用到Set的使用与否, 主要处理的就是列头传感器与传感器深度的对应这儿的列头与数据看成Excel表格中的表头和表头对应的数据

个人目前感觉Set和其他数据集混用的好一点, 浅显一点的理解就是可以少声明一个变量

其他总结

取代分支判断的if : 由于上代码逻辑有一点复杂在多处使用了各种判断来预防编码过程中出现的和预防的错误, 比如空指针, 这样写法不可避免的就使用了多个if-else判断, 这在工程代码中是比较恶心的, 如果使用的是jdk1.8或者11可以接着往下看

[原图Markdown地址], 外链转存后有可能会丢图或者图片变糊,放个保命图(!%5B%5D%28https://s2.loli.net/2024/05/24/brygOMA3165qvCF.png%29)

这个是jdk自带的uitil库中的Optional中的一种判空方法, 结合上面的图对比来看, 就是比分支判断要看起来好很多, 逻辑也更清晰了

关于lambda : 操作符为" -> " ,被称为 Lambda 操作符或箭头操作符;
将 Lambda 分为两个部分:

左侧:指定了 Lambda 表达式需要的参数列表;
右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。
但是就像这段代码的分页(在注释里的部分), 虽然用到了但是并没有严格按照这个格式, 结合项目的代码让GPT给个明示

在这里插入图片描述
这种就是比较中规中矩的写法po为变量箭头后是函数体用花括号包起来可以直接在里面写逻辑, 这个lambda外面的forEach不是必须的哈, 这是一个导入Excel数据的部分代码, 这个的foreach的作用是给每一个对象都做同样的处理 , 这儿使用lambda也是为了节省代码篇幅使代码表一明确

End

  • 11
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值