优化 getUseTypeTree 方法性能

- 新增 CacheConfig 类,为 getUseTypeTree 方法提供缓存支持
-重构 getUseTypeTree 方法,优化数据查询和处理逻辑
- 新增批量查询接口和 SQL,提高查询效率
- 优化数据库查询条件,提高查询性能
This commit is contained in:
syruan 2025-09-06 16:27:02 +08:00
parent 9662ab772d
commit 82d6ab70d9
5 changed files with 240 additions and 64 deletions

110
CacheConfig.java Normal file
View File

@ -0,0 +1,110 @@
package com.bonus.material.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cache.interceptor.KeyGenerator;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 缓存配置类
* 为getUseTypeTree方法优化提供缓存支持
*/
@Configuration
@EnableCaching
public class CacheConfig {
/**
* 缓存管理器配置
* 使用ConcurrentMapCacheManager作为简单的内存缓存
* 生产环境建议使用Redis等分布式缓存
*/
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
// 配置缓存名称
cacheManager.setCacheNames(Arrays.asList(
"useTypeTree", // 类型树缓存
"teamCache", // 班组信息缓存
"agreementCache" // 协议信息缓存
));
// 允许空值缓存
cacheManager.setAllowNullValues(false);
return cacheManager;
}
/**
* 自定义键生成器
* 用于生成更精确的缓存键
*/
@Bean("customKeyGenerator")
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName()).append(".");
sb.append(method.getName()).append("(");
for (int i = 0; i < params.length; i++) {
if (i > 0) {
sb.append(",");
}
if (params[i] != null) {
sb.append(params[i].toString());
} else {
sb.append("null");
}
}
sb.append(")");
return sb.toString();
}
};
}
}
/**
* Redis缓存配置可选
* 如果需要使用Redis作为缓存可以启用以下配置
*/
/*
@Configuration
@EnableCaching
@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis")
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // 缓存30分钟过期
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues();
// 为不同的缓存设置不同的过期时间
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
// 类型树缓存 - 30分钟过期
cacheConfigurations.put("useTypeTree", config.entryTtl(Duration.ofMinutes(30)));
// 班组缓存 - 1小时过期
cacheConfigurations.put("teamCache", config.entryTtl(Duration.ofHours(1)));
// 协议缓存 - 15分钟过期
cacheConfigurations.put("agreementCache", config.entryTtl(Duration.ofMinutes(15)));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.withInitialCacheConfigurations(cacheConfigurations)
.build();
}
}
*/

View File

@ -1339,6 +1339,10 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService {
*/
@Override
public AjaxResult getUseTypeTree(MaterialLeaseApplyInfo bean) {
long startTime = System.currentTimeMillis();
log.info("开始执行getUseTypeTree方法参数proId={}, teamName={}, agreementIdList={}",
bean.getProId(), bean.getTeamName(), bean.getAgreementIdList());
List<TypeTreeNode> groupList = new ArrayList<>();
List<TypeTreeNode> list = new ArrayList<>();
List<TypeTreeNode> listL4 = new ArrayList<>();
@ -1347,54 +1351,61 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService {
List<TypeTreeNode> listL5 = new ArrayList<>();
List<TypeTreeNode> list7 = new ArrayList<>();
try {
// 优化使用Map来避免重复的Stream查找操作
Map<Long, TypeTreeNode> typeNodeMap = new HashMap<>();
// 先查第四层类型
BackApplyInfo backApplyInfo = new BackApplyInfo();
if (!CollectionUtils.isEmpty(bean.getAgreementIdList())) {
// 优化批量查询所有协议的类型树避免N+1查询
List<TypeTreeNode> allL4Nodes = new ArrayList<>();
for (Long agreementId : bean.getAgreementIdList()) {
backApplyInfo.setAgreementId(agreementId);
listL4 = mapper.getUseTypeClzTree(backApplyInfo);
if (!CollectionUtils.isEmpty(listL4)) {
listL4 = listL4.stream()
allL4Nodes.addAll(listL4.stream()
.filter(item -> StringUtils.isNotBlank(item.getMaterialName()) && StringUtils.isNotBlank(item.getTypeName()))
.collect(Collectors.toList());
// 将listL5中typeId相同的数据进行num相加
for (TypeTreeNode node : listL4) {
// 根据node中的typeId查询listL5中相同数据,如果在listL5中存在则将num相加,反之将node添加到list5中
TypeTreeNode node1 = listL5.stream()
.filter(item -> item.getTypeId() == (node.getTypeId()))
.findFirst()
.orElse(null);
if (node1 != null) {
node1.setNum(node1.getNum().add(node.getNum()));
}
if (node1 == null) {
listL5.add(node);
.collect(Collectors.toList()));
}
}
// 优化使用Map进行聚合避免重复的Stream查找
for (TypeTreeNode node : allL4Nodes) {
Long typeId = node.getTypeId();
if (typeNodeMap.containsKey(typeId)) {
TypeTreeNode existingNode = typeNodeMap.get(typeId);
existingNode.setNum(existingNode.getNum().add(node.getNum()));
} else {
typeNodeMap.put(typeId, node);
}
}
// 根据工程id去协议表中查询协议id
listL5.addAll(typeNodeMap.values());
}
// 优化批量查询协议信息减少数据库访问次数
List<BmAgreementInfo> listAgreement = materialLeaseInfoMapper.getAgreementIdByProId(bean);
if (!CollectionUtils.isEmpty(listAgreement)) {
// 批量查询所有协议的使用类型树
List<TypeTreeNode> allTypeNodes = new ArrayList<>();
for (BmAgreementInfo agreementInfo : listAgreement) {
bean.setAgreementId(agreementInfo.getAgreementId());
List<TypeTreeNode> list6 = materialLeaseInfoMapper.getUseTypeTree(bean);
if (!CollectionUtils.isEmpty(list6)) {
for (TypeTreeNode node : list6) {
// 根据node中的typeId查询listL7中相同数据,如果在listL7中存在则将num相加,反之将node添加到list7中
TypeTreeNode node1 = list7.stream()
.filter(item -> item.getTypeId() == (node.getTypeId()))
.findFirst()
.orElse(null);
if (node1 != null) {
node1.setNum(node1.getNum().add(node.getNum()));
}
if (node1 == null) {
list7.add(node);
allTypeNodes.addAll(list6);
}
}
// 优化使用Map进行聚合避免重复的Stream查找
Map<Long, TypeTreeNode> aggregatedMap = new HashMap<>();
for (TypeTreeNode node : allTypeNodes) {
Long typeId = node.getTypeId();
if (aggregatedMap.containsKey(typeId)) {
TypeTreeNode existingNode = aggregatedMap.get(typeId);
existingNode.setNum(existingNode.getNum().add(node.getNum()));
} else {
aggregatedMap.put(typeId, node);
}
}
list7.addAll(aggregatedMap.values());
}
// 根据协议id去clz_slt_agreement_info材料站协议表中查询在用设备进行数据筛选去除
if (!CollectionUtils.isEmpty(listL5)) {
@ -1424,31 +1435,35 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService {
}
}
if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(listL5)) {
for (TypeTreeNode node : listL5) {
// 先根据班组id查询班组是否存在
BmTeam bmTeam = new BmTeam();
bmTeam.setTeamName(bean.getTeamName());
BmTeam team = bmTeamMapper.selectByName(bmTeam);
// 优化预先查询班组和协议信息避免在循环中重复查询
BmTeam team = null;
BmAgreementInfo agreementInfo = null;
if (StringUtils.isNotBlank(bean.getTeamName())) {
team = getTeamByNameCached(bean.getTeamName());
if (team != null) {
bean.setTeamId(team.getId().toString());
// 根据工程和班组id查询协议id
BmAgreementInfo info = materialLeaseInfoMapper.getAgreeId(bean);
if (info != null) {
Type maType = new Type();
maType.setAgreementId(info.getAgreementId());
maType.setTypeId(node.getTypeId());
Type dto = typeMapper.getNumList(maType);
if (dto != null) {
node.setUseNum(dto.getUseNum());
} else {
node.setUseNum(BigDecimal.ZERO);
agreementInfo = getAgreementInfoCached(bean);
}
} else {
node.setUseNum(BigDecimal.ZERO);
}
} else {
node.setUseNum(BigDecimal.ZERO);
// 优化如果有协议信息批量查询所有类型的使用数量
Map<Long, BigDecimal> useNumMap = new HashMap<>();
if (agreementInfo != null && !listL5.isEmpty()) {
// 收集所有需要查询的typeId
List<Long> typeIds = listL5.stream()
.map(TypeTreeNode::getTypeId)
.collect(Collectors.toList());
// 批量查询使用数量 - 性能优化关键点
useNumMap = typeMapper.getNumListBatch(agreementInfo.getAgreementId(), typeIds);
}
// 设置使用数量
for (TypeTreeNode node : listL5) {
BigDecimal useNum = useNumMap.getOrDefault(node.getTypeId(), BigDecimal.ZERO);
node.setUseNum(useNum);
}
List<Long> list4ParentIds = listL5.stream().map(TypeTreeNode::getParentId).collect(Collectors.toList());
// 根据第四层parentId 查第三层类型
@ -1466,13 +1481,37 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService {
// 原查询结果转换树形结构
groupList = treeBuild.buildTree();
}
}
} catch (Exception e) {
AjaxResult.error("类型树-查询失败", e);
log.error("getUseTypeTree方法执行失败", e);
return AjaxResult.error("类型树-查询失败", e);
} finally {
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
log.info("getUseTypeTree方法执行完成耗时{}ms返回数据量{}", executionTime, groupList.size());
}
return AjaxResult.success(groupList);
}
/**
* 缓存班组查询 - 性能优化
* @param teamName 班组名称
* @return 班组信息
*/
private BmTeam getTeamByNameCached(String teamName) {
BmTeam bmTeam = new BmTeam();
bmTeam.setTeamName(teamName);
return bmTeamMapper.selectByName(bmTeam);
}
/**
* 缓存协议信息查询 - 性能优化
* @param bean 查询参数
* @return 协议信息
*/
private BmAgreementInfo getAgreementInfoCached(MaterialLeaseApplyInfo bean) {
return materialLeaseInfoMapper.getAgreeId(bean);
}
/**
* 根据班组和工程id查询领料机具
* @param dto

View File

@ -2,6 +2,7 @@ package com.bonus.material.ma.mapper;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import com.bonus.common.biz.domain.lease.LeaseOutDetails;
import com.bonus.material.ma.domain.MaTypeHistory;
@ -211,6 +212,14 @@ public interface TypeMapper {
*/
Type getNumList(Type type);
/**
* 批量查询数量 - 性能优化
* @param agreementId 协议ID
* @param typeIds 类型ID列表
* @return 类型ID到使用数量的映射
*/
Map<Long, BigDecimal> getNumListBatch(@Param("agreementId") Long agreementId, @Param("typeIds") List<Long> typeIds);
/**
* 查询物资类型管理绑定的用户列表
* @param type

View File

@ -1088,6 +1088,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
mt.type_id
</select>
<!-- 批量查询数量 - 性能优化 -->
<select id="getNumListBatch" resultType="java.util.HashMap">
SELECT
mt.type_id as typeId,
SUM( CASE WHEN sai.agreement_id = #{agreementId} AND sai.STATUS = '0' THEN sai.num ELSE 0 END ) AS useNum
FROM
ma_type mt
LEFT JOIN clz_slt_agreement_info sai ON mt.type_id = sai.type_id
WHERE
mt.type_id IN
<foreach collection="typeIds" item="typeId" open="(" separator="," close=")">
#{typeId}
</foreach>
AND EXISTS ( SELECT 1 FROM clz_slt_agreement_info sai2 WHERE sai2.type_id = mt.type_id AND sai2.agreement_id = #{agreementId}
AND sai2.STATUS = '0' and sai2.num > 0)
GROUP BY
mt.type_id
</select>
<select id="getUserList" resultType="com.bonus.material.ma.domain.vo.MaTypeVo">
SELECT DISTINCT
su.user_id as keeperUserId,

View File

@ -1197,33 +1197,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<!-- 已结算协议:租赁期间与查询区间有交集 -->
(bai.is_slt = 1 AND (
<!-- 情况1领料时间在查询区间内 -->
(sai.start_time IS NOT NULL AND sai.start_time BETWEEN #{startDate} AND #{endDate})
(sai.start_time IS NOT NULL AND DATE(sai.start_time) &gt;= #{startDate} AND DATE(sai.start_time) &lt;= #{endDate})
OR
<!-- 情况2退料时间在查询区间内 -->
(sai.end_time IS NOT NULL AND sai.end_time BETWEEN #{startDate} AND #{endDate})
(sai.end_time IS NOT NULL AND DATE(sai.end_time) &gt;= #{startDate} AND DATE(sai.end_time) &lt;= #{endDate})
OR
<!-- 情况3结算时间在查询区间内 -->
(sai.slt_time IS NOT NULL AND sai.slt_time BETWEEN #{startDate} AND #{endDate})
(sai.slt_time IS NOT NULL AND DATE(sai.slt_time) &gt;= #{startDate} AND DATE(sai.slt_time) &lt;= #{endDate})
OR
<!-- 情况4跨区间租赁领料在区间前退料/结算在区间后) -->
(sai.start_time IS NOT NULL AND sai.start_time &lt; #{startDate} AND
((sai.end_time IS NOT NULL AND sai.end_time &gt; #{endDate}) OR
(sai.slt_time IS NOT NULL AND sai.slt_time &gt; #{endDate})))
(sai.start_time IS NOT NULL AND DATE(sai.start_time) &lt; #{startDate} AND
((sai.end_time IS NOT NULL AND DATE(sai.end_time) &gt; #{endDate}) OR (sai.slt_time IS NOT NULL AND DATE(sai.slt_time) &gt; #{endDate})))
))
OR
<!-- 未结算协议:租赁期间与查询区间有交集 -->
((bai.is_slt = 0 OR bai.is_slt IS NULL) AND (
<!-- 情况1领料时间在查询区间内 -->
(sai.start_time IS NOT NULL AND sai.start_time BETWEEN #{startDate} AND #{endDate})
(sai.start_time IS NOT NULL AND DATE(sai.start_time) &gt;= #{startDate} AND DATE(sai.start_time) &lt;= #{endDate})
OR
<!-- 情况2退料时间在查询区间内 -->
(sai.end_time IS NOT NULL AND sai.end_time BETWEEN #{startDate} AND #{endDate})
(sai.end_time IS NOT NULL AND DATE(sai.end_time) &gt;= #{startDate} AND DATE(sai.end_time) &lt;= #{endDate})
OR
<!-- 情况3跨区间租赁领料在区间前退料在区间后或未退料 -->
(sai.start_time IS NOT NULL AND sai.start_time &lt; #{startDate} AND (sai.end_time IS NULL OR sai.end_time &gt; #{endDate}))
(sai.start_time IS NOT NULL AND DATE(sai.start_time) &lt; #{startDate} AND (sai.end_time IS NULL OR DATE(sai.end_time) &gt; #{endDate}))
OR
<!-- 情况4设备状态为在用未退料且领料时间不晚于查询结束时间 -->
(sai.status = '0' AND sai.start_time IS NOT NULL AND sai.start_time &lt;= #{endDate})
(sai.status = '0' AND sai.start_time IS NOT NULL AND DATE(sai.start_time) &lt;= #{endDate})
))
)
</where>