最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot利用樹形結(jié)構(gòu)優(yōu)化查詢速度

 更新時(shí)間:2025年08月06日 08:19:12   作者:風(fēng)象南  
這篇文章主要為大家詳細(xì)介紹了SpringBoot利用樹形結(jié)構(gòu)優(yōu)化查詢速度,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

一個(gè)真實(shí)的性能災(zāi)難

某項(xiàng)目的首頁分類樹加載,在業(yè)務(wù)快速發(fā)展階段遇到了嚴(yán)重的性能問題:

用戶體驗(yàn):分類樹加載需要3-5秒,用戶頻繁投訴 系統(tǒng)壓力:高峰期數(shù)據(jù)庫連接池耗盡,系統(tǒng)崩潰 開發(fā)困擾:每次優(yōu)化都是治標(biāo)不治本,技術(shù)債務(wù)越來越重

根本原因:傳統(tǒng)的遞歸查詢導(dǎo)致了N+1查詢問題,15000個(gè)分類節(jié)點(diǎn)產(chǎn)生了15000次數(shù)據(jù)庫查詢。

經(jīng)過優(yōu)化后:響應(yīng)時(shí)間從3秒降到30毫秒,性能提升100倍。

如果你的系統(tǒng)也面臨類似問題,這篇文章將為你提供一套經(jīng)過生產(chǎn)驗(yàn)證的解決方案。

傳統(tǒng)方案為什么這么慢

N+1查詢?yōu)碾y

// 這段代碼看起來很簡單,實(shí)際上是性能殺手
public List<Category> getCategoryTree() {
    List<Category> roots = categoryMapper.getRootCategories(); // 1次查詢
    for (Category root : roots) {
        loadChildren(root); // 每個(gè)節(jié)點(diǎn)都觸發(fā)遞歸查詢
    }
    return roots;
}

private void loadChildren(Category parent) {
    List<Category> children = categoryMapper.getByParentId(parent.getId()); // N次查詢
    parent.setChildren(children);
    for (Category child : children) {
        loadChildren(child); // 遞歸繼續(xù)
    }
}

問題分析

  • 10000個(gè)節(jié)點(diǎn) = 10000次數(shù)據(jù)庫查詢
  • 每次查詢耗時(shí)2ms,總耗時(shí)20秒
  • 數(shù)據(jù)庫連接池很快被耗盡
  • 內(nèi)存中創(chuàng)建大量臨時(shí)對象,GC壓力巨大

性能測試數(shù)據(jù)對比

節(jié)點(diǎn)數(shù)量傳統(tǒng)遞歸方案優(yōu)化后方案性能提升
1,000800ms15ms53倍
5,0002.8s25ms112倍
10,0005.2s30ms173倍
50,000超時(shí)45ms1000倍+

核心解決方案:一次查詢 + O(n)算法

解決思路

核心理念:把N+1次數(shù)據(jù)庫查詢變成1次查詢 + 內(nèi)存中的高效樹構(gòu)建

1. 一次性批量查詢:SELECT * FROM category WHERE status = 1

2. HashMap建索引:O(1)時(shí)間復(fù)雜度查找父子關(guān)系

3. 單次遍歷構(gòu)建:O(n)時(shí)間復(fù)雜度完成整棵樹構(gòu)建

4. 緩存優(yōu)化:構(gòu)建結(jié)果緩存,避免重復(fù)計(jì)算

數(shù)據(jù)庫設(shè)計(jì)

選擇增強(qiáng)版鄰接表模型,在傳統(tǒng)parent_id基礎(chǔ)上增加level字段:

CREATE TABLE category (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    parent_id BIGINT,
    level INT NOT NULL DEFAULT 0,
    sort_order INT DEFAULT 0,
    status TINYINT DEFAULT 1,
    create_time DATETIME,
    update_time DATETIME,
    
    INDEX idx_parent_id (parent_id),
    INDEX idx_level (level),
    INDEX idx_status (status)
);

設(shè)計(jì)要點(diǎn)

  • level字段支持按層級(jí)批量查詢和優(yōu)化排序
  • 復(fù)合索引 (parent_id, sort_order) 支持排序查詢
  • status字段支持軟刪除和狀態(tài)過濾

算法復(fù)雜度分析:O(n²) → O(n) 的關(guān)鍵突破

傳統(tǒng)遞歸算法的復(fù)雜度分析

// 傳統(tǒng)方案:時(shí)間復(fù)雜度 O(n2),空間復(fù)雜度 O(n)
public List<Category> getChildren(Long parentId) {
    List<Category> children = categoryMapper.getByParentId(parentId);  // 1次查詢
    for (Category child : children) {                                 // 對每個(gè)子節(jié)點(diǎn)
        child.setChildren(getChildren(child.getId()));                // 遞歸查詢子節(jié)點(diǎn)
    }
    return children;
}

復(fù)雜度分析

  • 時(shí)間復(fù)雜度:O(n²)
    • 對于每個(gè)節(jié)點(diǎn)(n個(gè)),都要執(zhí)行一次數(shù)據(jù)庫查詢
    • 最壞情況下,每次查詢都要掃描整個(gè)表來找子節(jié)點(diǎn)
    • 總復(fù)雜度:n × n = O(n²)
  • 空間復(fù)雜度:O(n) - 遞歸調(diào)用棧深度等于樹的深度
  • 數(shù)據(jù)庫查詢次數(shù):O(n) - 每個(gè)節(jié)點(diǎn)一次查詢,共n次

性能問題根源

// 以10000個(gè)節(jié)點(diǎn)為例的執(zhí)行過程
Root Node (id=1)
├── Child1 (id=2) -> SELECT * FROM category WHERE parent_id = 2  // 第1次查詢
│   ├── Child1.1 (id=5) -> SELECT * FROM category WHERE parent_id = 5  // 第2次查詢
│   └── Child1.2 (id=6) -> SELECT * FROM category WHERE parent_id = 6  // 第3次查詢
├── Child2 (id=3) -> SELECT * FROM category WHERE parent_id = 3  // 第4次查詢
│   └── Child2.1 (id=7) -> SELECT * FROM category WHERE parent_id = 7  // 第5次查詢
└── Child3 (id=4) -> SELECT * FROM category WHERE parent_id = 4  // 第6次查詢
    └── ...繼續(xù)遞歸,總共10000次查詢

優(yōu)化后算法的復(fù)雜度分析

// 優(yōu)化方案:時(shí)間復(fù)雜度 O(n),空間復(fù)雜度 O(n)
public <T extends TreeNode<T>> List<T> buildTree(List<T> nodes, Object rootValue) {
    // 步驟1:建立HashMap索引 - O(n)
    Map<Object, T> nodeMap = nodes.stream()
        .collect(Collectors.toMap(TreeNode::getId, node -> node));
    
    List<T> rootNodes = new ArrayList<>();
    
    // 步驟2:單次遍歷建立父子關(guān)系 - O(n)
    for (T node : nodes) {                    // 遍歷n個(gè)節(jié)點(diǎn)
        Object parentId = node.getParentId();
        if (Objects.equals(parentId, rootValue)) {
            rootNodes.add(node);
        } else {
            T parent = nodeMap.get(parentId); // HashMap查找:O(1)
            if (parent != null) {
                parent.addChild(node);        // 建立父子關(guān)系:O(1)
            }
        }
    }
    
    return rootNodes;
}

復(fù)雜度分析

時(shí)間復(fù)雜度:O(n)

  • 建立HashMap:O(n)
  • 遍歷建立關(guān)系:O(n) × O(1) = O(n)
  • 總復(fù)雜度:O(n) + O(n) = O(n)

空間復(fù)雜度:O(n) - HashMap存儲(chǔ)n個(gè)節(jié)點(diǎn)

數(shù)據(jù)庫查詢次數(shù):O(1) - 只查詢一次

性能提升的關(guān)鍵

// 優(yōu)化后的執(zhí)行過程 - 以10000個(gè)節(jié)點(diǎn)為例
//Step 1: SELECT * FROM category WHERE status = 1  // 僅1次查詢獲取所有數(shù)據(jù)
//Step 2: 內(nèi)存中構(gòu)建HashMap索引 - O(n)
{
  1 -> Node{id=1, name="根節(jié)點(diǎn)", parentId=null},
  2 -> Node{id=2, name="子節(jié)點(diǎn)1", parentId=1},
  3 -> Node{id=3, name="子節(jié)點(diǎn)2", parentId=1},
  ...
  10000 -> Node{id=10000, name="葉子節(jié)點(diǎn)", parentId=9999}
}
//Step 3: 單次遍歷建立關(guān)系 - O(n)
for (Node node : allNodes) {  // 10000次循環(huán)
    Node parent = nodeMap.get(node.parentId);  // O(1)查找
    parent.addChild(node);  // O(1)添加
}

算法復(fù)雜度對比表

算法維度傳統(tǒng)遞歸方案優(yōu)化后方案性能提升
時(shí)間復(fù)雜度O(n²)O(n)線性級(jí)提升
空間復(fù)雜度O(h) 遞歸棧深度O(n) HashMap空間換時(shí)間
數(shù)據(jù)庫查詢O(n) 次查詢O(1) 次查詢n倍減少
網(wǎng)絡(luò)IOn次往返1次往返n倍減少
緩存友好性差(隨機(jī)訪問)好(順序訪問)顯著提升

O(n)高性能樹構(gòu)建算法

核心算法實(shí)現(xiàn)

@Component
public class TreeBuilder {
    
    /**
     * 高性能樹構(gòu)建算法 - O(n)時(shí)間復(fù)雜度
     * @param nodes 所有節(jié)點(diǎn)列表
     * @param rootValue 根節(jié)點(diǎn)的parent_id值(通常為null或0)
     * @return 構(gòu)建好的樹結(jié)構(gòu)
     */
    public <T extends TreeNode<T>> List<T> buildTree(List<T> nodes, Object rootValue) {
        if (CollectionUtils.isEmpty(nodes)) {
            return new ArrayList<>();
        }
        
        // 1. 建立ID->Node的快速索引,時(shí)間復(fù)雜度O(n)
        Map<Object, T> nodeMap = nodes.stream()
            .collect(Collectors.toMap(TreeNode::getId, node -> node));
        
        List<T> rootNodes = new ArrayList<>();
        
        // 2. 單次遍歷建立父子關(guān)系,時(shí)間復(fù)雜度O(n)
        for (T node : nodes) {
            Object parentId = node.getParentId();
            
            if (Objects.equals(parentId, rootValue)) {
                // 根節(jié)點(diǎn)
                rootNodes.add(node);
            } else {
                // 子節(jié)點(diǎn),建立父子關(guān)系
                T parent = nodeMap.get(parentId);
                if (parent != null) {
                    parent.addChild(node);
                }
            }
        }
        
        // 3. 遞歸排序(可選),時(shí)間復(fù)雜度O(n log n)
        sortTreeRecursively(rootNodes);
        
        return rootNodes;
    }
    
    private <T extends TreeNode<T>> void sortTreeRecursively(List<T> nodes) {
        if (CollectionUtils.isEmpty(nodes)) {
            return;
        }
        
        // 按sort_order排序
        nodes.sort(Comparator.comparing(TreeNode::getSortOrder, 
            Comparator.nullsLast(Comparator.naturalOrder())));
        
        // 遞歸排序子節(jié)點(diǎn)
        for (T node : nodes) {
            if (node.hasChildren()) {
                sortTreeRecursively(node.getChildren());
            }
        }
    }
}

樹節(jié)點(diǎn)基類設(shè)計(jì)

public interface TreeNode<T> {
    Object getId();
    Object getParentId();
    Integer getSortOrder();
    List<T> getChildren();
    void setChildren(List<T> children);
    
    default void addChild(T child) {
        if (getChildren() == null) {
            setChildren(new ArrayList<>());
        }
        getChildren().add(child);
    }
    
    default boolean hasChildren() {
        return getChildren() != null && !getChildren().isEmpty();
    }
}

完整業(yè)務(wù)實(shí)現(xiàn)

實(shí)體類設(shè)計(jì)

@Data
@TableName("category")
public class Category implements TreeNode<Category> {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String name;
    private Long parentId;
    private Integer level;
    private Integer sortOrder;
    private Integer status;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    
    // 非數(shù)據(jù)庫字段
    @TableField(exist = false)
    private List<Category> children;
    
    @Override
    public void addChild(Category child) {
        if (children == null) {
            children = new ArrayList<>();
        }
        children.add(child);
    }
}

數(shù)據(jù)訪問層

@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
    
    /**
     * 獲取所有有效分類(用于構(gòu)建樹)
     */
    @Select("SELECT id, name, parent_id, level, sort_order, status, " +
            "create_time, update_time FROM category WHERE status = 1 " +
            "ORDER BY level, parent_id, sort_order")
    List<Category> selectAllForTree();
    
    /**
     * 根據(jù)父ID查詢子分類
     */
    @Select("SELECT * FROM category WHERE parent_id = #{parentId} AND status = 1 " +
            "ORDER BY sort_order")
    List<Category> selectByParentId(@Param("parentId") Long parentId);
}

服務(wù)層實(shí)現(xiàn)

@Service
@Transactional(rollbackFor = Exception.class)
public class CategoryServiceImpl implements CategoryService {
    
    @Autowired
    private CategoryMapper categoryMapper;
    
    @Autowired
    private TreeBuilder treeBuilder;
    
    /**
     * 獲取分類樹(帶緩存)
     */
    @Cacheable(value = "category:tree", key = "'all'")
    public List<Category> getCategoryTree() {
        // 1. 一次性查詢所有數(shù)據(jù)
        List<Category> allCategories = categoryMapper.selectAllForTree();
        
        // 2. 使用高性能算法構(gòu)建樹
        return treeBuilder.buildTree(allCategories, null);
    }
    
    /**
     * 創(chuàng)建分類
     */
    @CacheEvict(value = "category:tree", allEntries = true)
    public Category createCategory(Category category) {
        // 設(shè)置層級(jí)
        if (category.getParentId() != null) {
            Category parent = categoryMapper.selectById(category.getParentId());
            category.setLevel(parent.getLevel() + 1);
        } else {
            category.setLevel(0);
        }
        
        category.setStatus(1);
        category.setCreateTime(LocalDateTime.now());
        category.setUpdateTime(LocalDateTime.now());
        
        categoryMapper.insert(category);
        return category;
    }
    
    /**
     * 更新分類
     */
    @CacheEvict(value = "category:tree", allEntries = true)
    public Category updateCategory(Category category) {
        category.setUpdateTime(LocalDateTime.now());
        categoryMapper.updateById(category);
        return category;
    }
    
    /**
     * 刪除分類(軟刪除)
     */
    @CacheEvict(value = "category:tree", allEntries = true)
    public void deleteCategory(Long id) {
        Category category = new Category();
        category.setId(id);
        category.setStatus(0);
        category.setUpdateTime(LocalDateTime.now());
        categoryMapper.updateById(category);
    }
}

控制器層

@RestController
@RequestMapping("/api/categories")
public class CategoryController {
    
    @Autowired
    private CategoryService categoryService;
    
    /**
     * 獲取分類樹
     */
    @GetMapping("/tree")
    public Result<List<Category>> getCategoryTree() {
        List<Category> tree = categoryService.getCategoryTree();
        return Result.success(tree);
    }
    
    /**
     * 創(chuàng)建分類
     */
    @PostMapping
    public Result<Category> createCategory(@RequestBody @Valid Category category) {
        Category created = categoryService.createCategory(category);
        return Result.success(created);
    }
    
    /**
     * 更新分類
     */
    @PutMapping("/{id}")
    public Result<Category> updateCategory(@PathVariable Long id, 
                                          @RequestBody @Valid Category category) {
        category.setId(id);
        Category updated = categoryService.updateCategory(category);
        return Result.success(updated);
    }
    
    /**
     * 刪除分類
     */
    @DeleteMapping("/{id}")
    public Result<Void> deleteCategory(@PathVariable Long id) {
        categoryService.deleteCategory(id);
        return Result.success();
    }
}

緩存優(yōu)化策略

多級(jí)緩存配置

方案一:自定義復(fù)合緩存管理器(推薦)

@Configuration
@EnableCaching
public class MultiLevelCacheConfig {
    
    /**
     * 多級(jí)緩存管理器:L1(Caffeine) + L2(Redis)
     */
    @Bean
    @Primary
    public CacheManager multiLevelCacheManager(RedisConnectionFactory connectionFactory) {
        return new MultiLevelCacheManager(
            createCaffeineCacheManager(),
            createRedisCacheManager(connectionFactory)
        );
    }
    
    private CaffeineCacheManager createCaffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(1000)
                .expireAfterWrite(10, TimeUnit.MINUTES) // L1緩存10分鐘過期
                .recordStats());
        return cacheManager;
    }
    
    private RedisCacheManager createRedisCacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(2)) // L2緩存2小時(shí)過期
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .disableCachingNullValues();
        
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .build();
    }
}

/**
 * 自定義多級(jí)緩存管理器
 */
public class MultiLevelCacheManager implements CacheManager {
    
    private final CacheManager l1CacheManager; // 本地緩存
    private final CacheManager l2CacheManager; // 分布式緩存
    
    public MultiLevelCacheManager(CacheManager l1CacheManager, CacheManager l2CacheManager) {
        this.l1CacheManager = l1CacheManager;
        this.l2CacheManager = l2CacheManager;
    }
    
    @Override
    public Cache getCache(String name) {
        Cache l1Cache = l1CacheManager.getCache(name);
        Cache l2Cache = l2CacheManager.getCache(name);
        return new MultiLevelCache(name, l1Cache, l2Cache);
    }
    
    @Override
    public Collection<String> getCacheNames() {
        Set<String> names = new HashSet<>();
        names.addAll(l1CacheManager.getCacheNames());
        names.addAll(l2CacheManager.getCacheNames());
        return names;
    }
}

/**
 * 多級(jí)緩存實(shí)現(xiàn)
 */
public class MultiLevelCache implements Cache {
    
    private final String name;
    private final Cache l1Cache; // 本地緩存
    private final Cache l2Cache; // 分布式緩存
    
    public MultiLevelCache(String name, Cache l1Cache, Cache l2Cache) {
        this.name = name;
        this.l1Cache = l1Cache;
        this.l2Cache = l2Cache;
    }
    
    @Override
    public String getName() {
        return name;
    }
    
    @Override
    public Object getNativeCache() {
        return this;
    }
    
    @Override
    public ValueWrapper get(Object key) {
        // 1. 先查L1緩存
        ValueWrapper l1Value = l1Cache.get(key);
        if (l1Value != null) {
            return l1Value;
        }
        
        // 2. L1未命中,查L2緩存
        ValueWrapper l2Value = l2Cache.get(key);
        if (l2Value != null) {
            // 3. L2命中,回寫到L1
            l1Cache.put(key, l2Value.get());
            return l2Value;
        }
        
        return null;
    }
    
    @Override
    public <T> T get(Object key, Class<T> type) {
        ValueWrapper wrapper = get(key);
        return wrapper != null ? (T) wrapper.get() : null;
    }
    
    @Override
    public <T> T get(Object key, Callable<T> valueLoader) {
        ValueWrapper wrapper = get(key);
        if (wrapper != null) {
            return (T) wrapper.get();
        }
        
        try {
            T value = valueLoader.call();
            put(key, value);
            return value;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    @Override
    public void put(Object key, Object value) {
        // 同時(shí)寫入L1和L2
        l1Cache.put(key, value);
        l2Cache.put(key, value);
    }
    
    @Override
    public void evict(Object key) {
        // 同時(shí)清除L1和L2
        l1Cache.evict(key);
        l2Cache.evict(key);
    }
    
    @Override
    public void clear() {
        l1Cache.clear();
        l2Cache.clear();
    }
}

方案二:手動(dòng)實(shí)現(xiàn)多級(jí)緩存(適合精細(xì)控制)

@Service
public class CategoryServiceImpl implements CategoryService {
    
    @Autowired
    private CategoryMapper categoryMapper;
    
    @Autowired
    private TreeBuilder treeBuilder;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private final Cache<String, List<Category>> localCache = Caffeine.newBuilder()
            .maximumSize(100)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build();
    
    private static final String CACHE_KEY = "category:tree:all";
    
    /**
     * 手動(dòng)實(shí)現(xiàn)多級(jí)緩存
     */
    public List<Category> getCategoryTree() {
        // 1. 查詢L1緩存(本地)
        List<Category> result = localCache.getIfPresent(CACHE_KEY);
        if (result != null) {
            log.debug("命中L1緩存");
            return result;
        }
        
        // 2. 查詢L2緩存(Redis)
        String json = redisTemplate.opsForValue().get(CACHE_KEY);
        if (StringUtils.hasText(json)) {
            try {
                result = JSON.parseArray(json, Category.class);
                // 回寫到L1緩存
                localCache.put(CACHE_KEY, result);
                log.debug("命中L2緩存,回寫L1");
                return result;
            } catch (Exception e) {
                log.warn("Redis緩存反序列化失敗", e);
            }
        }
        
        // 3. 緩存未命中,查詢數(shù)據(jù)庫
        List<Category> allCategories = categoryMapper.selectAllForTree();
        result = treeBuilder.buildTree(allCategories, null);
        
        // 4. 同時(shí)寫入L1和L2緩存
        localCache.put(CACHE_KEY, result);
        try {
            redisTemplate.opsForValue().set(CACHE_KEY, JSON.toJSONString(result), 
                    Duration.ofHours(2));
        } catch (Exception e) {
            log.warn("寫入Redis緩存失敗", e);
        }
        
        log.debug("查詢數(shù)據(jù)庫,構(gòu)建緩存");
        return result;
    }
    
    /**
     * 清除多級(jí)緩存
     */
    @Override
    public void evictCache() {
        localCache.invalidate(CACHE_KEY);
        redisTemplate.delete(CACHE_KEY);
        log.info("清除多級(jí)緩存完成");
    }
}

緩存預(yù)熱策略

@Component
public class CacheWarmUp {
    
    @Autowired
    private CategoryService categoryService;
    
    /**
     * 應(yīng)用啟動(dòng)時(shí)預(yù)熱緩存
     */
    @EventListener(ApplicationReadyEvent.class)
    public void warmUpCache() {
        log.info("開始預(yù)熱分類樹緩存...");
        try {
            categoryService.getCategoryTree();
            log.info("分類樹緩存預(yù)熱完成");
        } catch (Exception e) {
            log.error("分類樹緩存預(yù)熱失敗", e);
        }
    }
    
    /**
     * 定時(shí)刷新緩存
     */
    @Scheduled(fixedRate = 300000) // 5分鐘
    public void refreshCache() {
        try {
            categoryService.getCategoryTree();
            log.debug("定時(shí)刷新分類樹緩存完成");
        } catch (Exception e) {
            log.error("定時(shí)刷新分類樹緩存失敗", e);
        }
    }
}

性能監(jiān)控

@Component
public class TreePerformanceMonitor {
    
    private static final String TREE_BUILD_TIMER = "tree.build.time";
    private static final String TREE_SIZE_GAUGE = "tree.size";
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    /**
     * 監(jiān)控樹構(gòu)建性能
     */
    public <T> List<T> monitorTreeBuild(Supplier<List<T>> treeBuilder) {
        return Timer.Sample.start(meterRegistry)
                .stop(Timer.builder(TREE_BUILD_TIMER)
                        .description("Tree building time")
                        .register(meterRegistry))
                .recordCallable(treeBuilder::get);
    }
    
    /**
     * 記錄樹規(guī)模
     */
    public void recordTreeSize(int size) {
        Gauge.builder(TREE_SIZE_GAUGE)
                .description("Tree node count")
                .register(meterRegistry, size, s -> s);
    }
}

常見問題與解決方案

Q1: 數(shù)據(jù)更新時(shí)如何保證緩存一致性

解決方案:采用Write-Invalidate模式

@CacheEvict(value = "category:tree", allEntries = true)
@Transactional
public void updateCategory(Category category) {
    // 1. 先更新數(shù)據(jù)庫
    categoryMapper.updateById(category);
    
    // 2. 緩存自動(dòng)失效(通過@CacheEvict)
    // 3. 下次查詢時(shí)重新構(gòu)建緩存
}

// 對于高并發(fā)場景,可以使用延時(shí)雙刪
@Async
public void delayedCacheEvict() {
    try {
        Thread.sleep(500); // 延時(shí)500ms
        cacheManager.getCache("category:tree").clear();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

Q2: 樹的層級(jí)過深導(dǎo)致棧溢出怎么辦

解決方案:用迭代替代遞歸

public void sortTreeIteratively(List<TreeNode> roots) {
    Queue<TreeNode> queue = new LinkedList<>(roots);
    
    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        
        if (node.hasChildren()) {
            // 排序當(dāng)前層級(jí)
            node.getChildren().sort(Comparator.comparing(TreeNode::getSortOrder));
            // 將子節(jié)點(diǎn)加入隊(duì)列
            queue.addAll(node.getChildren());
        }
    }
}

Q3: 大數(shù)據(jù)量下內(nèi)存不足怎么辦

解決方案:分頁加載 + 懶加載

public List<Category> getCategoryTreeLazy(int maxDepth) {
    // 只加載指定深度的數(shù)據(jù)
    List<Category> categories = categoryMapper.selectByMaxLevel(maxDepth);
    return treeBuilder.buildTree(categories, null);
}

// 按需加載子節(jié)點(diǎn)
public List<Category> loadChildren(Long parentId) {
    return categoryMapper.selectByParentId(parentId);
}

總結(jié):從3秒到30毫秒的核心要點(diǎn)

三個(gè)關(guān)鍵突破

1. 算法突破:O(n²) → O(n)

  • 用HashMap建立快速索引,單次遍歷完成樹構(gòu)建
  • 避免遞歸調(diào)用和嵌套循環(huán)
  • 內(nèi)存訪問模式友好,CPU緩存命中率高

2. 數(shù)據(jù)庫突破:N+1查詢 → 1次查詢

  • 改變查詢策略,一次性獲取所有數(shù)據(jù)
  • 合理的索引設(shè)計(jì),支持高效的批量查詢
  • 利用數(shù)據(jù)庫的批量處理能力

3. 緩存突破:無緩存 → 95%命中率

  • 多級(jí)緩存架構(gòu),本地+分布式
  • 智能的緩存失效策略
  • 預(yù)熱機(jī)制,避免緩存穿透

立即行動(dòng)指南

如果你的樹形查詢耗時(shí) > 500ms,依次執(zhí)行以下步驟:

  • 復(fù)制本文的TreeBuilder代碼 → 替換現(xiàn)有遞歸邏輯
  • 添加必要的數(shù)據(jù)庫索引 → 優(yōu)化查詢性能
  • 集成Spring Cache → 添加緩存支持
  • 進(jìn)行性能測試 → 驗(yàn)證優(yōu)化效果

預(yù)期收益

  • 響應(yīng)時(shí)間降低90%+
  • 并發(fā)能力提升10倍+
  • 數(shù)據(jù)庫壓力減少80%+
  • 用戶體驗(yàn)顯著改善

最后的思考

性能優(yōu)化不是一蹴而就的,而是一個(gè)持續(xù)改進(jìn)的過程。本文提供的解決方案在多個(gè)生產(chǎn)環(huán)境中得到驗(yàn)證,但每個(gè)業(yè)務(wù)場景都有其特殊性。

記住:好的架構(gòu)不是設(shè)計(jì)出來的,而是演進(jìn)出來的。從簡單的鄰接表開始,根據(jù)業(yè)務(wù)發(fā)展逐步優(yōu)化,才是最務(wù)實(shí)的選擇。

到此這篇關(guān)于SpringBoot利用樹形結(jié)構(gòu)優(yōu)化查詢速度的文章就介紹到這了,更多相關(guān)SpringBoot樹形結(jié)構(gòu)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Intellij IDEA十大快捷鍵

    Intellij IDEA十大快捷鍵

    Intellij IDEA中有很多快捷鍵讓人愛不釋手,stackoverflow上也有一些有趣的討論.這篇文章主要介紹了Intellij IDEA十大快捷鍵,需要的朋友可以參考下
    2018-03-03
  • SpringBoot 整合MyBatis、Junit5的實(shí)踐過程

    SpringBoot 整合MyBatis、Junit5的實(shí)踐過程

    本文介紹了SpringBoot集成JUnit5和MyBatis的開發(fā)實(shí)踐,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2025-10-10
  • java中雪花算法時(shí)鐘回?fù)軉栴}解決

    java中雪花算法時(shí)鐘回?fù)軉栴}解決

    本文介紹了分布式系統(tǒng)中使用雪花算法生成唯一ID時(shí)可能遇到的時(shí)鐘回?fù)軉栴},以及解決這個(gè)問題的幾種方法,包括等待機(jī)制、擴(kuò)展位、預(yù)留時(shí)間戳或邏輯時(shí)鐘等,感興趣的可以了解一下
    2024-10-10
  • 如何使用SpringMVC的消息轉(zhuǎn)換器設(shè)置日期格式

    如何使用SpringMVC的消息轉(zhuǎn)換器設(shè)置日期格式

    這篇文章主要介紹了如何使用SpringMVC的消息轉(zhuǎn)換器設(shè)置日期格式問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • Java調(diào)用.dll文件的方法

    Java調(diào)用.dll文件的方法

    因?yàn)轫?xiàng)目的需求,要在JAVA項(xiàng)目中調(diào)用Windows的Dll(動(dòng)態(tài)鏈接庫)文件,之前用Jni調(diào)用過C寫的Dll文件,比較麻煩,這里不多說,網(wǎng)上也有很多這方面的文檔。在網(wǎng)上找到一個(gè)開源的組件JNative,使用后感覺比較方便
    2013-04-04
  • 簡述Java編程語言對象的容納

    簡述Java編程語言對象的容納

    這篇文章主要對Java編程思想中對象的容納進(jìn)行了一個(gè)總體的介紹,具有一定的參考價(jià)值,需要的朋友可以了解下。
    2017-09-09
  • 這一次搞懂Spring的Bean實(shí)例化原理操作

    這一次搞懂Spring的Bean實(shí)例化原理操作

    這篇文章主要介紹了這一次搞懂Spring的Bean實(shí)例化原理操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • Java Springboot的目的你知道嗎

    Java Springboot的目的你知道嗎

    在本篇文章中小編給大家分析了Java中Spring Boot的優(yōu)勢以及相關(guān)知識(shí)點(diǎn)內(nèi)容,興趣的朋友們可以學(xué)習(xí)參考下,希望能夠給你帶來幫助
    2021-09-09
  • 基于Java實(shí)現(xiàn)的一層簡單人工神經(jīng)網(wǎng)絡(luò)算法示例

    基于Java實(shí)現(xiàn)的一層簡單人工神經(jīng)網(wǎng)絡(luò)算法示例

    這篇文章主要介紹了基于Java實(shí)現(xiàn)的一層簡單人工神經(jīng)網(wǎng)絡(luò)算法,結(jié)合實(shí)例形式分析了java實(shí)現(xiàn)人工神經(jīng)網(wǎng)絡(luò)的具體實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2017-12-12
  • Springboot項(xiàng)目編寫測試單元完整步驟記錄

    Springboot項(xiàng)目編寫測試單元完整步驟記錄

    這篇文章主要介紹了如何使用JUnit編寫Spring?Boot項(xiàng)目中的測試單元,包括引入依賴、配置文件設(shè)置、啟動(dòng)文件創(chuàng)建以及編寫測試類的步驟,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-03-03

最新評論

柏乡县| 宁阳县| 丰台区| 诏安县| 江山市| 厦门市| 普宁市| 平乐县| 吴旗县| 封开县| 昭苏县| 股票| 巴南区| 普宁市| 安吉县| 西藏| 定陶县| 慈利县| 临汾市| 称多县| 那曲县| 吉林省| 靖西县| 长葛市| 科技| 通江县| 黑山县| 韶关市| 迁西县| 崇仁县| 彭泽县| 萨嘎县| 洪洞县| 射阳县| 天门市| 玉树县| 云霄县| 固原市| 正宁县| 珲春市| 理塘县|