SpringBoot 自研運行時 SQL 調用樹,3 分鐘定位慢 SQL!

在復雜的業務系統中,一個接口往往會執行多條SQL,如何直觀地看到這些SQL的調用關系和執行情況?

本文將使用SpringBoot + MyBatis攔截器構建一個SQL調用樹可視化系統。

圖片

圖片

項目背景

在日常開發中,我們經常遇到這樣的場景:

復雜查詢鏈路:一個用戶詳情接口可能涉及用戶基本信息、訂單列表、訂單詳情等多個查詢性能問題排查:系統響應慢,需要快速定位是哪個SQL影響了性能開發調試需求:希望能直觀地看到SQL的執行順序和層次關系

基于這些需求,實現了一個基于SpringBoot + MyBatis的SQL調用樹可視化系統。

系統功能特性

該系統具有以下核心功能:

核心功能

MyBatis攔截器:通過攔截器機制捕獲SQL執行過程,無需修改業務代碼調用樹構建:自動構建SQL調用的層次關系可視化展示:使用D3.js實現樹形結構的可視化展示性能監控:記錄SQL執行時間,自動標識慢SQL統計分析:提供SQL執行統計信息和性能分析數據管理:支持數據的查詢、清理和導出

技術實現

后端技術:Spring Boot 3.4.5 + MyBatis 3.0.3 + H2數據庫前端技術:HTML5 + Tailwind CSS + D3.js v7配置管理:支持動態配置慢SQL閾值等參數

項目結構

技術棧

后端技術棧

Spring Boot 3.4.5:應用框架MyBatis 3.0.3:數據訪問層和攔截器H2 Database:內存數據庫(演示用)Lombok:簡化代碼編寫Jackson:JSON序列化

前端技術棧

HTML5 + Tailwind CSS:頁面結構和樣式D3.js v7:數據可視化Font Awesome:圖標庫原生JavaScript:前端交互邏輯

項目目錄結構
springboot-sql-tree/
├── src/main/java/com/example/sqltree/
│ ? ├── SqlTreeApplication.java ? ? ? ? ?## 啟動類
│ ? ├── SqlInterceptor.java ? ? ? ? ? ? ?## MyBatis攔截器
│ ? ├── SqlCallTreeContext.java ? ? ? ? ?## 調用樹上下文管理
│ ? ├── SqlNode.java ? ? ? ? ? ? ? ? ? ??## SQL節點數據模型
│ ? ├── SqlTreeController.java ? ? ? ? ??## REST API控制器
│ ? ├── DemoController.java ? ? ? ? ? ? ?## 演示API
│ ? ├── UserService.java ? ? ? ? ? ? ? ??## 用戶服務(演示用)
│ ? ├── UserMapper.java ? ? ? ? ? ? ? ? ?## 用戶數據訪問
│ ? └── OrderMapper.java ? ? ? ? ? ? ? ??## 訂單數據訪問
├── src/main/resources/
│ ? ├── application.yml ? ? ? ? ? ? ? ? ?## 應用配置
│ ? ├── schema.sql ? ? ? ? ? ? ? ? ? ? ??## 數據庫表結構
│ ? ├── data.sql ? ? ? ? ? ? ? ? ? ? ? ??## 示例數據
│ ? └── static/
│ ? ? ? ├── index.html ? ? ? ? ? ? ? ? ??## 前端頁面
│ ? ? ? └── sql-tree.js ? ? ? ? ? ? ? ? ?## 前端JavaScript
└── pom.xml ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?## Maven配置

這或許是一個對你有用的開源項目,mall項目是一套基于?SpringBoot3?+ Vue 的電商系統(Github標星60K),后端支持多模塊和?2024最新微服務架構?,采用Docker和K8S部署。包括前臺商城項目和后臺管理系統,能支持完整的訂單流程!涵蓋商品、訂單、購物車、權限、優惠券、會員、支付等功能!

  • Boot項目:https://github.com/macrozheng/mall

  • Cloud項目:https://github.com/macrozheng/mall-swarm

  • 教程網站:https://www.macrozheng.com

項目演示:

圖片

核心實現詳解

1. MyBatis攔截器:零侵入的核心

這是整個系統的核心組件,通過MyBatis的插件機制實現SQL執行的無感知攔截:

@Component
@Intercepts({@Signature(type = Executor.class,?method?=?"query", args = {MappedStatement.class,?Object.class,?RowBounds.class,?ResultHandler.class}),@Signature(type?= Executor.class,?method?=?"update", args = {MappedStatement.class,?Object.class})
})
public?class?SqlInterceptor?implements?Interceptor?{@Autowiredprivate?SqlCallTreeContext sqlCallTreeContext;@Overridepublic?Object?intercept(Invocation invocation)?throws?Throwable?{// 檢查是否啟用追蹤if?(!sqlCallTreeContext.isTraceEnabled()) {return?invocation.proceed();}long?startTime = System.currentTimeMillis();Object[] args = invocation.getArgs();MappedStatement mappedStatement = (MappedStatement) args[0];Object parameter = args[1];// 獲取SQL信息BoundSql boundSql = mappedStatement.getBoundSql(parameter);String sql = boundSql.getSql();String sqlType = mappedStatement.getSqlCommandType().name();// 獲取調用棧信息StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();String serviceName = extractServiceName(stackTrace);String methodName = extractMethodName(stackTrace);// 創建SQL節點SqlNode sqlNode = SqlNode.builder().nodeId(UUID.randomUUID().toString()).sql(formatSql(sql)).sqlType(sqlType).threadName(Thread.currentThread().getName()).serviceName(serviceName).methodName(methodName).startTime(LocalDateTime.now()).parameters(extractParameters(boundSql, parameter)).depth(sqlCallTreeContext.getCurrentDepth() +?1).build();// 進入SQL調用sqlCallTreeContext.enter(sqlNode);try?{// 執行SQLObject result = invocation.proceed();// 記錄執行結果long?executionTime = System.currentTimeMillis() - startTime;int?affectedRows = calculateAffectedRows(result, sqlType);sqlCallTreeContext.exit(sqlNode, affectedRows,?null);return?result;}?catch?(Exception e) {// 記錄異常信息sqlCallTreeContext.exit(sqlNode,?0, e.getMessage());throw?e;}}private?String?extractServiceName(StackTraceElement[] stackTrace)?{for?(StackTraceElement element : stackTrace) {String className = element.getClassName();if?(className.contains("Service") && !className.contains("$")) {return?className.substring(className.lastIndexOf('.') +?1);}}return"Unknown";}private?String?extractMethodName(StackTraceElement[] stackTrace)?{for?(StackTraceElement element : stackTrace) {if?(element.getClassName().contains("Service")) {return?element.getMethodName();}}return"unknown";}private?int?calculateAffectedRows(Object result, String sqlType)?{if?("SELECT".equals(sqlType) && result?instanceof?List) {return?((List<?>) result).size();}?elseif?(result?instanceof?Integer) {return?(Integer) result;}return0;}
}

關鍵特性

🎯?精準攔截:同時攔截查詢和更新操作 🔒?異常安全:確保業務邏輯不受監控影響 📊?豐富信息:自動提取Service調用信息和執行統計

2. 調用樹上下文管理器:線程安全的數據管理

SqlCallTreeContext負責管理SQL調用樹的構建和存儲,采用線程安全的設計:

@Component
publicclass?SqlCallTreeContext?{// 線程本地存儲privatefinal?ThreadLocal<Stack<SqlNode>> callStack =?new?ThreadLocal<Stack<SqlNode>>() {@Overrideprotected?Stack<SqlNode>?initialValue()?{returnnew?Stack<>();}};privatefinal?ThreadLocal<List<SqlNode>> rootNodes =?new?ThreadLocal<List<SqlNode>>() {@Overrideprotected?List<SqlNode>?initialValue()?{returnnew?ArrayList<>();}};// 全局會話存儲privatefinal?Map<String, List<SqlNode>> globalSessions =?new?ConcurrentHashMap<>();// 統計信息privatefinal?AtomicLong totalSqlCount =?new?AtomicLong(0);privatefinal?AtomicLong slowSqlCount =?new?AtomicLong(0);privatefinal?AtomicLong errorSqlCount =?new?AtomicLong(0);privatefinal?AtomicLong totalExecutionTime =?new?AtomicLong(0);// 配置參數privatevolatilelong?slowSqlThreshold =?1000;?// 慢SQL閾值(毫秒)privatevolatileboolean?traceEnabled =?true;?// 追蹤開關/*** 進入SQL調用*/public?SqlNode?enter(SqlNode sqlNode)?{if?(!traceEnabled) {return?sqlNode;}Stack<SqlNode> stack = callStack.get();// 設置深度sqlNode.setDepth(stack.size() +?1);// 建立父子關系if?(!stack.isEmpty()) {SqlNode parent = stack.peek();parent.addChild(sqlNode);sqlNode.setParentId(parent.getNodeId());}?else?{// 根節點rootNodes.get().add(sqlNode);}// 壓入棧stack.push(sqlNode);return?sqlNode;}/*** 退出SQL調用*/public?void?exit(SqlNode sqlNode,?int?affectedRows, String errorMessage)?{if?(!traceEnabled) {return;}// 設置結束時間和結果sqlNode.setEndTime(LocalDateTime.now());sqlNode.setAffectedRows(affectedRows);sqlNode.setErrorMessage(errorMessage);// 計算執行時間long?executionTime = Duration.between(sqlNode.getStartTime(), sqlNode.getEndTime()).toMillis();sqlNode.setExecutionTime(executionTime);// 標記慢SQLif?(executionTime > slowSqlThreshold) {sqlNode.setSlowSql(true);slowSqlCount.incrementAndGet();}// 標記錯誤SQLif?(errorMessage !=?null) {errorSqlCount.incrementAndGet();}// 更新統計totalSqlCount.incrementAndGet();totalExecutionTime.addAndGet(executionTime);// 彈出棧Stack<SqlNode> stack = callStack.get();if?(!stack.isEmpty()) {stack.pop();// 如果棧為空,說明調用樹完成,保存到全局會話if?(stack.isEmpty()) {String sessionKey = generateSessionKey();globalSessions.put(sessionKey,?new?ArrayList<>(rootNodes.get()));rootNodes.get().clear();}}}/*** 獲取當前調用深度*/public?int?getCurrentDepth()?{return?callStack.get().size();}/*** 獲取當前線程的根節點*/public?List<SqlNode>?getRootNodes()?{returnnew?ArrayList<>(rootNodes.get());}/*** 獲取所有會話*/public?Map<String, List<SqlNode>> getAllSessions() {returnnew?HashMap<>(globalSessions);}/*** 清理會話數據*/public?void?clearSessions()?{globalSessions.clear();rootNodes.get().clear();callStack.get().clear();}/*** 生成會話鍵*/private?String?generateSessionKey()?{return?Thread.currentThread().getName() +?"_"?+ System.currentTimeMillis();}/*** 獲取統計信息*/public?SqlStatistics?getStatistics()?{return?SqlStatistics.builder().totalSqlCount(totalSqlCount.get()).slowSqlCount(slowSqlCount.get()).errorSqlCount(errorSqlCount.get()).averageExecutionTime(totalSqlCount.get() >?0???totalExecutionTime.get() / totalSqlCount.get() :?0).build();}// Getter和Setter方法public?boolean?isTraceEnabled()?{return?traceEnabled;}public?void?setTraceEnabled(boolean?traceEnabled)?{this.traceEnabled = traceEnabled;}public?long?getSlowSqlThreshold()?{return?slowSqlThreshold;}public?void?setSlowSqlThreshold(long?slowSqlThreshold)?{this.slowSqlThreshold = slowSqlThreshold;}
}

線程安全:使用ThreadLocal確保多線程環境下的數據隔離智能建樹:自動識別父子關系,構建完整調用樹實時統計:同步更新性能統計信息

3. 數據模型:完整的SQL節點信息
@Data
publicclass?SqlNode?{private?String nodeId; ? ? ? ? ? ? ?// 節點唯一標識private?String sql; ? ? ? ? ? ? ? ??// SQL語句private?String formattedSql; ? ? ? ?// 格式化后的SQLprivate?String sqlType; ? ? ? ? ? ??// SQL類型privateint?depth; ? ? ? ? ? ? ? ? ?// 調用深度private?String threadName; ? ? ? ? ?// 線程名稱private?String serviceName; ? ? ? ??// Service類名private?String methodName; ? ? ? ? ?// Service方法名private?LocalDateTime startTime; ? ?// 開始時間private?LocalDateTime endTime; ? ? ?// 結束時間privatelong?executionTime; ? ? ? ??// 執行耗時privateboolean?slowSql; ? ? ? ? ? ?// 是否為慢SQLprivateint?affectedRows; ? ? ? ? ??// 影響行數private?String errorMessage; ? ? ? ?// 錯誤信息private?List<Object> parameters; ? ?// SQL參數private?List<SqlNode> children; ? ??// 子節點// 智能分析方法public?boolean?isSlowSql(long?threshold)?{return?executionTime > threshold;}public?int?getTotalNodeCount()?{return1?+ children.stream().mapToInt(SqlNode::getTotalNodeCount).sum();}public?int?getMaxDepth()?{return?children.isEmpty() ? depth :?children.stream().mapToInt(SqlNode::getMaxDepth).max().orElse(depth);}
}
4. RESTful API:完整的數據接口

SqlTreeController提供完整的REST API接口,支持數據查詢、配置管理和系統監控:

@RestController
@RequestMapping("/api/sql-tree")
publicclass?SqlTreeController?{@Autowiredprivate?SqlCallTreeContext sqlCallTreeContext;/*** 獲取當前線程的SQL調用樹*/@GetMapping("/current")public?ResponseEntity<List<SqlNode>> getCurrentTree() {List<SqlNode> rootNodes = sqlCallTreeContext.getRootNodes();return?ResponseEntity.ok(rootNodes);}/*** 獲取所有會話的SQL調用樹*/@GetMapping("/sessions")public?ResponseEntity<Map<String, List<SqlNode>>> getAllSessions() {Map<String, List<SqlNode>> sessions = sqlCallTreeContext.getAllSessions();return?ResponseEntity.ok(sessions);}/*** 獲取指定會話的SQL調用樹*/@GetMapping("/session/{sessionKey}")public?ResponseEntity<List<SqlNode>> getSessionTree(@PathVariable?String sessionKey) {Map<String, List<SqlNode>> sessions = sqlCallTreeContext.getAllSessions();List<SqlNode> sessionTree = sessions.get(sessionKey);if?(sessionTree !=?null) {return?ResponseEntity.ok(sessionTree);}?else?{return?ResponseEntity.notFound().build();}}/*** 清理所有調用樹數據*/@DeleteMapping("/clear")public?ResponseEntity<Map<String, Object>> clearAllTrees() {sqlCallTreeContext.clearSessions();Map<String, Object> response =?new?HashMap<>();response.put("success",?true);response.put("message",?"All SQL trees cleared successfully");response.put("timestamp", LocalDateTime.now());return?ResponseEntity.ok(response);}/*** 獲取統計信息*/@GetMapping("/statistics")public?ResponseEntity<Map<String, Object>> getStatistics() {SqlStatistics stats = sqlCallTreeContext.getStatistics();Map<String, Object> response =?new?HashMap<>();response.put("totalSqlCount", stats.getTotalSqlCount());response.put("slowSqlCount", stats.getSlowSqlCount());response.put("errorSqlCount", stats.getErrorSqlCount());response.put("averageExecutionTime", stats.getAverageExecutionTime());response.put("slowSqlThreshold", sqlCallTreeContext.getSlowSqlThreshold());response.put("traceEnabled", sqlCallTreeContext.isTraceEnabled());return?ResponseEntity.ok(response);}/*** 配置追蹤參數*/@PostMapping("/config")public?ResponseEntity<Map<String, Object>> updateConfig(@RequestBody?Map<String, Object> config) {Map<String, Object> response =?new?HashMap<>();if?(config.containsKey("slowSqlThreshold")) {long?threshold = ((Number) config.get("slowSqlThreshold")).longValue();sqlCallTreeContext.setSlowSqlThreshold(threshold);response.put("slowSqlThreshold", threshold);}if?(config.containsKey("traceEnabled")) {boolean?enabled = (Boolean) config.get("traceEnabled");sqlCallTreeContext.setTraceEnabled(enabled);response.put("traceEnabled", enabled);}response.put("success",?true);response.put("message",?"Configuration updated successfully");return?ResponseEntity.ok(response);}/*** 分析慢SQL*/@GetMapping("/analysis/slow-sql")public?ResponseEntity<List<SqlNode>> getSlowSqlAnalysis() {Map<String, List<SqlNode>> sessions = sqlCallTreeContext.getAllSessions();List<SqlNode> slowSqlNodes =?new?ArrayList<>();for?(List<SqlNode> sessionNodes : sessions.values()) {collectSlowSqlNodes(sessionNodes, slowSqlNodes);}// 按執行時間降序排序slowSqlNodes.sort((a, b) -> Long.compare(b.getExecutionTime(), a.getExecutionTime()));return?ResponseEntity.ok(slowSqlNodes);}/*** 導出數據*/@GetMapping("/export")public?ResponseEntity<Map<String, Object>> exportData() {Map<String, Object> exportData =?new?HashMap<>();exportData.put("sessions", sqlCallTreeContext.getAllSessions());exportData.put("statistics", sqlCallTreeContext.getStatistics());exportData.put("exportTime", LocalDateTime.now());exportData.put("version",?"1.0");return?ResponseEntity.ok(exportData);}/*** 系統狀態檢查*/@GetMapping("/health")public?ResponseEntity<Map<String, Object>> healthCheck() {Map<String, Object> health =?new?HashMap<>();health.put("status",?"UP");health.put("traceEnabled", sqlCallTreeContext.isTraceEnabled());health.put("slowSqlThreshold", sqlCallTreeContext.getSlowSqlThreshold());health.put("timestamp", LocalDateTime.now());return?ResponseEntity.ok(health);}/*** 遞歸收集慢SQL節點*/private?void?collectSlowSqlNodes(List<SqlNode> nodes, List<SqlNode> slowSqlNodes)?{for?(SqlNode node : nodes) {if?(node.isSlowSql()) {slowSqlNodes.add(node);}if?(node.getChildren() !=?null?&& !node.getChildren().isEmpty()) {collectSlowSqlNodes(node.getChildren(), slowSqlNodes);}}}
}
5. 前端可視化實現

前端使用D3.js實現交互式的SQL調用樹可視化,主要包含以下功能:

// sql-tree.js - 主要的可視化邏輯
class?SqlTreeVisualizer?{constructor() {this.width =?1200;this.height =?800;this.margin = {?transform: translateY(?50,?right:?150,?bottom:?50,?left:?150?};// 初始化SVG容器this.svg = d3.select('#tree-container').append('svg').attr('width',?this.width).attr('height',?this.height);this.g =?this.svg.append('g').attr('transform',?`translate(${this.margin.left},${this.margin.top})`);// 配置樹布局this.tree = d3.tree().size([this.height -?this.margin.top -?this.margin.bottom,?this.width -?this.margin.left -?this.margin.right]);// 初始化工具提示this.tooltip = d3.select('body').append('div').attr('class',?'tooltip').style('opacity',?0);}/*** 渲染SQL調用樹*/render(sessions) {this.g.selectAll('*').rem)ove();if?(!sessions ||?Object.keys(sessions).length ===?0) {this.showEmptyState();return;}// 選擇第一個會話進行展示const?sessionKey =?Object.keys(sessions)[0];const?rootNodes = sessions[sessionKey];if?(rootNodes && rootNodes.length >?0) {this.renderTree(rootNodes[0]);}}/*** 渲染單個調用樹*/renderTree(rootNode) {// 構建D3層次結構const?root = d3.hierarchy(rootNode, d => d.children);// 計算節點位置this.tree(root);// 繪制連接線const?links =?this.g.selectAll('.link').data(root.links()).enter().append('path').attr('class',?'link').attr('d', d3.linkHorizontal().x(d?=>?d.y).y(d?=>?d.x)).style('fill',?'none').style('stroke',?'#94a3b8').style('stroke-width',?'2px').style('stroke-opacity',?0.6);// 繪制節點組const?nodes =?this.g.selectAll('.node').data(root.descendants()).enter().append('g').attr('class',?'node').attr('transform', d =>?`translate(${d.y},${d.x})`);// 繪制節點圓圈nodes.append('circle').attr('r',?10).style('fill', d =>?this.getNodeColor(d.data)).style('stroke',?'#1e293b').style('stroke-width',?'2px').style('cursor',?'pointer');// 添加節點文本nodes.append('text').attr('dy',?'.35em').attr('x', d => d.children ??-15?:?15).style('text-anchor', d => d.children ??'end'?:?'start').style('font-size',?'12px').style('font-weight',?'500').style('fill',?'#1e293b').text(d?=>this.getNodeLabel(d.data));// 添加交互事件nodes.on('mouseover', (event, d) =>?this.showTooltip(event, d.data)).on('mouseout', () =>?this.hideTooltip()).on('click', (event, d) =>?this.showNodeDetails(d.data));}/*** 獲取節點顏色*/getNodeColor(data) {if?(data.errorMessage) {return'#ef4444';?// 錯誤:紅色}if?(data.slowSql) {return'#f59e0b';?// 慢SQL:橙色}switch?(data.sqlType) {case'SELECT':return'#10b981';?// 查詢:綠色case'INSERT':return'#3b82f6';?// 插入:藍色case'UPDATE':return'#8b5cf6';?// 更新:紫色case'DELETE':return'#ef4444';?// 刪除:紅色default:return'#6b7280';?// 默認:灰色}}/*** 獲取節點標簽*/getNodeLabel(data) {const?time = data.executionTime ||?0;return`${data.sqlType}?(${time}ms)`;}/*** 顯示工具提示*/showTooltip(event, data) {const?tooltipContent =?`<div class="font-semibold text-gray-900">${data.sqlType}?操作</div><div class="text-sm text-gray-600 mt-1"><div>執行時間:?${data.executionTime ||?0}ms</div><div>影響行數:?${data.affectedRows ||?0}</div><div>服務:?${data.serviceName ||?'Unknown'}</div><div>方法:?${data.methodName ||?'unknown'}</div>${data.errorMessage ??`<div class="text-red-600">錯誤:?${data.errorMessage}</div>`?:?''}</div>`;this.tooltip.transition().duration(200).style('opacity',?.9);this.tooltip.html(tooltipContent).style('left', (event.pageX +?10) +?'px').style('top', (event.pageY -?28) +?'px');}/*** 隱藏工具提示*/hideTooltip() {this.tooltip.transition().duration(500).style('opacity',?0);}/*** 顯示空狀態*/showEmptyState() {this.g.append('text').attr('x', (this.width -?this.margin.left -?this.margin.right) /?2).attr('y', (this.height -?this.margin.top -?this.margin.bottom) /?2).attr('text-anchor',?'middle').style('font-size',?'18px').style('fill',?'#6b7280').text('暫無SQL調用數據');}/*** 顯示節點詳情*/showNodeDetails(data) {// 在側邊欄顯示詳細信息const?detailsPanel =?document.getElementById('node-details');if?(detailsPanel) {detailsPanel.innerHTML =?`<h3 class="text-lg font-semibold mb-4">SQL詳情</h3><div class="space-y-2"><div><span class="font-medium">類型:</span>?${data.sqlType}</div><div><span class="font-medium">執行時間:</span>?${data.executionTime ||?0}ms</div><div><span class="font-medium">影響行數:</span>?${data.affectedRows ||?0}</div><div><span class="font-medium">服務:</span>?${data.serviceName ||?'Unknown'}</div><div><span class="font-medium">方法:</span>?${data.methodName ||?'unknown'}</div><div><span class="font-medium">線程:</span>?${data.threadName ||?'unknown'}</div>${data.sql ??`<div><span class="font-medium">SQL:</span><pre class="mt-1 p-2 bg-gray-100 rounded text-sm">${data.sql}</pre></div>`?:?''}${data.parameters ??`<div><span class="font-medium">參數:</span><pre class="mt-1 p-2 bg-gray-100 rounded text-sm">${data.parameters}</pre></div>`?:?''}${data.errorMessage ??`<div><span class="font-medium text-red-600">錯誤:</span><div class="mt-1 p-2 bg-red-50 rounded text-sm text-red-700">${data.errorMessage}</div></div>`?:?''}</div>`;}}
}

核心特性

  • 🌳?樹形布局:清晰展示SQL調用層次關系

  • 🎨?顏色編碼:綠色(正常)、紅色(慢SQL)

  • 🖱??交互操作:點擊節點查看詳情,懸停顯示提示

  • 🔍?智能篩選:支持按執行時間、SQL類型等條件篩選

  • 📊?實時刷新:支持自動/手動刷新數據

快速開始

環境要求

Java 21+Maven 3.6+現代瀏覽器(支持ES6+)

訪問系統

啟動成功后,可以通過以下地址訪問:

可視化界面:http://localhost:8080/index.htmlH2數據庫控制臺:http://localhost:8080/h2-console

JDBC URL: `jdbc:h2:mem:testdb`
用戶名: `sa`
密碼: (空)
項目配置

核心依賴(pom.xml)

<dependencies><!-- Spring Boot 3.4.5 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>3.4.5</version></dependency><!-- MyBatis 3.0.3 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><!-- H2 Database --><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
</dependencies>

應用配置(application.yml)

server:port:8080spring:
application:name:springboot-sql-tree
datasource:url:jdbc:h2:mem:testdbdriver-class-name:org.h2.Driverusername:sapassword:schema:classpath:schema.sqldata:classpath:data.sql
h2:console:enabled:truepath:/h2-consolemybatis:
mapper-locations:classpath:mapper/*.xml
type-aliases-package:com.example.sqltree.entity
configuration:map-underscore-to-camel-case:truelazy-loading-enabled:truecache-enabled:truelog-impl:org.apache.ibatis.logging.slf4j.Slf4jImpl

實際應用場景

開發調試場景

場景1:復雜查詢性能分析

當調用?/api/demo/user/1/detail?接口時,系統會自動捕獲以下SQL調用鏈:

UserService.getUserDetailWithOrders()
├── SELECT * FROM users WHERE id = ? (2ms)
└── SELECT * FROM orders WHERE user_id = ? (15ms)└── SELECT * FROM order_items WHERE order_id IN (...) (45ms)

通過可視化界面可以清晰看到:

  • 總執行時間:62ms

  • SQL調用深度:2層

  • 性能瓶頸:order_items查詢耗時最長

場景2:慢SQL識別

系統自動標識執行時間超過閾值(默認1000ms)的SQL:

{"nodeId":?"uuid-123","sql":?"SELECT * FROM orders o JOIN users u ON o.user_id = u.id WHERE o.status = ?","executionTime":?1250,"slowSql":?true,"serviceName":?"OrderService","methodName":?"getOrdersWithUserInfo"
}
數據監控

統計信息示例

{"totalSqlCount":?1247,"slowSqlCount":?23,"errorSqlCount":?5,"averageExecutionTime":?35.6,"slowSqlThreshold":?1000,"traceEnabled":?true
}

慢SQL分析報告

系統提供按執行時間排序的慢SQL列表:

[{"sql":?"SELECT COUNT(*) FROM orders WHERE created_at BETWEEN ? AND ?","executionTime":?2150,"serviceName":?"ReportService","methodName":?"generateDailyReport","affectedRows":?1},{"sql":?"UPDATE users SET last_login = ? WHERE id IN (...)","executionTime":?1890,"serviceName":?"UserService","methodName":?"batchUpdateLastLogin","affectedRows":?156}
]

總結

這個項目展示了如何結合Spring Boot生態和前端技術,構建一個實用的SQL監控工具,為日常開發和性能優化提供有力支持。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/920919.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/920919.shtml
英文地址,請注明出處:http://en.pswp.cn/news/920919.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

部署 Go 項目的 N 種方法

Go 語言&#xff08;Golang&#xff09;以其簡單、高效和易于部署的特點&#xff0c;成為了很多企業開發和部署服務的首選語言。無論是微服務架構&#xff0c;還是命令行工具&#xff0c;Go 的編譯方式和標準庫使得部署變得更加輕松。本文將介紹部署 Go 語言項目的幾種常見方法…

【ARM】MDK工程切換高版本的編譯器后出現error: A1167E\A1159E\A1137E\A1517E\A1150E報錯

1、 文檔目標解決工程從Compiler 5切換到Compiler 6進行編譯時出現一些非語法問題上的報錯。2、 問題場景對于一些使用Compiler 5進行編譯的工程&#xff0c;要切換到Compiler 6進行編譯的時候&#xff0c;原本無任何報錯警告信息的工程在使用Compiler 6進行編譯后出現了一些非…

AtCoder Beginner Contest 421

文章目錄A MisdeliveryB Fibonacci ReversedC AlternatedD RLE MovingE YachtF Erase between X and YG Increase to make it IncreasingAtCoder Beginner Contest 421A Misdelivery Mansion AtCoder has N rooms numbered from room 1 to room N. Each room i is inhabited b…

數據結構:冒泡排序 (Bubble Sort)

目錄 從最簡單的操作開始 如何利用這個原子操作實現一個具體的小目標&#xff1f; 我們來手動模擬一下&#xff1a; 如何從一個小目標擴展到最終目標&#xff1f; 代碼的逐步完善 第一階段&#xff1a;定義函數框架和我們需要的“原子操作” 第二階段&#xff1a;實現“…

教育項目管理工具新趨勢:可視化與自動化如何提升效率?

課程項目不同于普通商業項目&#xff0c;它涉及 “教研設計→內容開發→師資準備→市場推廣→學員服務” 全鏈路&#xff0c;環節多、角色雜、周期跨度大。傳統的 Excel 表格、口頭溝通不僅難以追蹤進度&#xff0c;更易造成信息斷層。而看板工具憑借 “可視化流程、輕量化協作…

計算兩個二值圖像的交集計算交點數量的基礎上,進一步使用 DBSCAN 算法對交點進行聚

好的&#xff0c;如果你需要在計算交點數量的基礎上&#xff0c;進一步使用 DBSCAN 算法對交點進行聚類&#xff0c;以合并距離較近的點&#xff0c;可以按照以下步驟實現&#xff1a; 計算交點&#xff1a;使用 cv2.bitwise_and 計算兩個二值圖像的交集&#xff0c;并提取交點…

Linux中的IP命令詳解

華子目錄 1.ip命令是什么1.1ip命令的由來1.2ip命令的安裝包1.2ip選項&#xff08;基本不用&#xff09; 2.查看網絡信息2.1顯示全部網絡接口信息2.2顯示單個網絡接口信息2.3顯示單個接口狀態2.4查看路由表2.5查看arp緩存 3.設置網卡ip地址3.1啟用或停用網卡3.2設置默認網關3.3新…

如何解決pip安裝報錯ModuleNotFoundError: No module named ‘tox’問題

【Python系列Bug修復PyCharm控制臺pip install報錯】如何解決pip安裝報錯ModuleNotFoundError: No module named ‘tox’問題 摘要 在使用 PyCharm 2025 控制臺執行 pip install 命令時&#xff0c;開發者經常會遇到如下錯誤&#xff1a; ModuleNotFoundError: No module nam…

拆分TypeScript項目的學習收獲:處理編譯緩存和包緩存,引用本地項目,使用相對路徑

最近需要將工作中的一個TS包拆出一部分代碼&#xff0c;以便在多個團隊和項目中共享。原以為這會是一項特別簡單的工作&#xff0c;但是也花了兩天才大致拆成功。因此記錄一下&#xff0c;也給有類似需求的同學一點經驗。 所拆項目的大致功能&#xff1a;整個項目的結構大致分為…

瑞芯微RK3576平臺FFmpeg硬件編解碼移植及性能測試實戰攻略

本文介紹瑞芯微RK3576平臺&#xff0c;FFmpeg硬件編解碼移植及性能測試方法。 FFmpeg簡介與實測數據 FFmpeg簡介 FFmpeg是一套多媒體框架&#xff0c;能夠解碼、編碼、轉碼、復用、解復用、流、過濾和播放數字音頻、視頻&#xff0c;提供了錄制、轉換以及流化音視頻的完整解…

【網絡安全入門基礎教程】網絡安全零基礎學習方向及需要掌握的技能

最近總有同學問我&#xff0c;0基礎怎么學網絡安全&#xff1f;0基礎可以轉行做網絡安全嗎&#xff1f;網絡安全有哪些學習方向&#xff1f;每個方向需要掌握哪些技能&#xff1f;今天給大家簡單寫一下。 我的回答是先了解&#xff0c;再入行。 具體怎么做呢&#xff1f; 首…

Altium Designer中的Net-Tie:解決多網絡合并與電氣隔離的利器

Altium Designer中的Net-Tie:解決多網絡合并與電氣隔離的利器 在復雜的PCB設計中,我們常常會遇到一些特殊的電氣連接需求。例如,需要將兩個或多個邏輯上獨立但物理上需要連接的網絡(如不同電源域的GND)在特定點進行連接(單點連接),同時又要保持其網絡標識的獨立性。 …

計算機畢設項目 基于Python與機器學習的B站視頻熱度分析與預測系統 基于隨機森林算法的B站視頻內容熱度預測系統

&#x1f495;&#x1f495;作者&#xff1a;計算機源碼社 &#x1f495;&#x1f495;個人簡介&#xff1a;本人八年開發經驗&#xff0c;擅長Java、Python、PHP、.NET、Node.js、Spark、hadoop、Android、微信小程序、爬蟲、大數據、機器學習等&#xff0c;大家有這一塊的問題…

百勝軟件×OceanBase深度合作,賦能品牌零售數字化實踐降本增效

8月28日&#xff0c;由OceanBase主辦的“2025零售數據底座創新大會”在上海舉行。大會重磅發布了由愛分析、OceanBase攜手王歆、沈剛兩位行業專家聯合編制的《零售一體化云數據庫白皮書》。白皮書系統梳理了從“大促流量應對”到“AI應用落地”的全流程方法論&#xff0c;并為不…

2025年Java在中國開發語言排名分析報告

引言 在軟件定義世界的2025年&#xff0c;編程語言的戰略價值已超越工具屬性&#xff0c;成為產業數字化轉型的核心支撐與開發者思維模式的延伸載體。TIOBE指數作為全球技術市場變化的重要晴雨表&#xff0c;通過追蹤工程師分布、課程設置、供應商動態及搜索引擎數據&#xff0…

TDengine 日期時間函數 DAYOFWEEK 使用手冊

DAYOFWEEK 函數使用手冊 函數描述 DAYOFWEEK 函數用于返回指定日期是一周中的第幾天。該函數遵循標準的星期編號約定&#xff0c;返回值范圍為 1-7&#xff0c;其中&#xff1a; 1 星期日 (Sunday)2 星期一 (Monday)3 星期二 (Tuesday)4 星期三 (Wednesday)5 星期四 (T…

從RNN到BERT

目錄 序列模型簡介RNN循環神經網絡LSTM長短期記憶網絡Transformer架構BERT模型詳解實踐項目 序列模型簡介 什么是序列數據&#xff1f; 序列數據是按照特定順序排列的數據&#xff0c;其中元素的順序包含重要信息。常見的序列數據包括&#xff1a; 文本&#xff1a;單詞或字…

橢圓曲線的數學基礎

一、引言 橢圓曲線密碼學&#xff08;Elliptic Curve Cryptography, ECC&#xff09;是現代公鑰密碼學的核心工具之一。 相比傳統的 RSA&#xff0c;ECC 可以用 更短的密鑰長度 提供 同等甚至更高的安全性&#xff0c;因此被廣泛應用于區塊鏈、TLS、移動設備加密等場景。 要理解…

從能耗黑洞到精準智控:ASCB2智慧空開重構高校宿舍用電能效模型

隨著智慧校園建設不斷推進&#xff0c;校園宿舍的用電管理面臨著安全性、智能化與可視化的多重挑戰。傳統用電監控手段在數據采集、實時控制和故障響應方面存在明顯不足。安科瑞ASCB2系列物聯網斷路器通過集成多種智能感知、保護控制與通信手段&#xff0c;為高校宿舍提供了一種…

前端學習——JavaScript基礎

前面我們已經學習了前端代碼的骨架——HTML和前端美化工具——CSS。但是作為界面與客戶進行交互我們還需要一個語言工具——JavaScript。 因此實際上HTML、CSS、JavaScript三者是這樣的關系&#xff1a; HTML: 網頁的結構(骨) CSS: 網頁的表現(皮) JavaScript: 網頁的行為(魂) …