SpringBoot 自研运行时 SQL 调用树,3 分钟定位慢 SQL
这个项目展示了如何结合Spring Boot生态和前端技术,构建一个实用的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配置
核心实现详解
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
})
})
publicclassSqlInterceptorimplementsInterceptor {
@Autowired
private SqlCallTreeContext sqlCallTreeContext;
@Override
public Object intercept(Invocation invocation)throws Throwable {
// 检查是否启用追踪
if (!sqlCallTreeContext.isTraceEnabled()) {
return invocation.proceed();
}
longstartTime= System.currentTimeMillis();
Object[] args = invocation.getArgs();
MappedStatementmappedStatement= (MappedStatement) args[0];
Objectparameter= args[1];
// 获取SQL信息
BoundSqlboundSql= mappedStatement.getBoundSql(parameter);
Stringsql= boundSql.getSql();
StringsqlType= mappedStatement.getSqlCommandType().name();
// 获取调用栈信息
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
StringserviceName= extractServiceName(stackTrace);
StringmethodName= extractMethodName(stackTrace);
// 创建SQL节点
SqlNodesqlNode= 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 {
// 执行SQL
Objectresult= invocation.proceed();
// 记录执行结果
longexecutionTime= System.currentTimeMillis() - startTime;
intaffectedRows= 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) {
StringclassName= 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";
}
privateintcalculateAffectedRows(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
publicclassSqlCallTreeContext {
// 线程本地存储
privatefinal ThreadLocal<Stack<SqlNode>> callStack = newThreadLocal<Stack<SqlNode>>() {
@Override
protected Stack<SqlNode> initialValue() {
returnnewStack<>();
}
};
privatefinal ThreadLocal<List<SqlNode>> rootNodes = newThreadLocal<List<SqlNode>>() {
@Override
protected List<SqlNode> initialValue() {
returnnewArrayList<>();
}
};
// 全局会话存储
privatefinal Map<String, List<SqlNode>> globalSessions = newConcurrentHashMap<>();
// 统计信息
privatefinalAtomicLongtotalSqlCount=newAtomicLong(0);
privatefinalAtomicLongslowSqlCount=newAtomicLong(0);
privatefinalAtomicLongerrorSqlCount=newAtomicLong(0);
privatefinalAtomicLongtotalExecutionTime=newAtomicLong(0);
// 配置参数
privatevolatilelongslowSqlThreshold=1000; // 慢SQL阈值(毫秒)
privatevolatilebooleantraceEnabled=true; // 追踪开关
/**
* 进入SQL调用
*/
public SqlNode enter(SqlNode sqlNode) {
if (!traceEnabled) {
return sqlNode;
}
Stack<SqlNode> stack = callStack.get();
// 设置深度
sqlNode.setDepth(stack.size() + 1);
// 建立父子关系
if (!stack.isEmpty()) {
SqlNodeparent= stack.peek();
parent.addChild(sqlNode);
sqlNode.setParentId(parent.getNodeId());
} else {
// 根节点
rootNodes.get().add(sqlNode);
}
// 压入栈
stack.push(sqlNode);
return sqlNode;
}
/**
* 退出SQL调用
*/
publicvoidexit(SqlNode sqlNode, int affectedRows, String errorMessage) {
if (!traceEnabled) {
return;
}
// 设置结束时间和结果
sqlNode.setEndTime(LocalDateTime.now());
sqlNode.setAffectedRows(affectedRows);
sqlNode.setErrorMessage(errorMessage);
// 计算执行时间
longexecutionTime= Duration.between(sqlNode.getStartTime(), sqlNode.getEndTime()).toMillis();
sqlNode.setExecutionTime(executionTime);
// 标记慢SQL
if (executionTime > slowSqlThreshold) {
sqlNode.setSlowSql(true);
slowSqlCount.incrementAndGet();
}
// 标记错误SQL
if (errorMessage != null) {
errorSqlCount.incrementAndGet();
}
// 更新统计
totalSqlCount.incrementAndGet();
totalExecutionTime.addAndGet(executionTime);
// 弹出栈
Stack<SqlNode> stack = callStack.get();
if (!stack.isEmpty()) {
stack.pop();
// 如果栈为空,说明调用树完成,保存到全局会话
if (stack.isEmpty()) {
StringsessionKey= generateSessionKey();
globalSessions.put(sessionKey, newArrayList<>(rootNodes.get()));
rootNodes.get().clear();
}
}
}
/**
* 获取当前调用深度
*/
publicintgetCurrentDepth() {
return callStack.get().size();
}
/**
* 获取当前线程的根节点
*/
public List<SqlNode> getRootNodes() {
returnnewArrayList<>(rootNodes.get());
}
/**
* 获取所有会话
*/
public Map<String, List<SqlNode>> getAllSessions() {
returnnewHashMap<>(globalSessions);
}
/**
* 清理会话数据
*/
publicvoidclearSessions() {
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方法
publicbooleanisTraceEnabled() {
return traceEnabled;
}
publicvoidsetTraceEnabled(boolean traceEnabled) {
this.traceEnabled = traceEnabled;
}
publiclonggetSlowSqlThreshold() {
return slowSqlThreshold;
}
publicvoidsetSlowSqlThreshold(long slowSqlThreshold) {
this.slowSqlThreshold = slowSqlThreshold;
}
}
-
• 线程安全: 使用ThreadLocal确保多线程环境下的数据隔离
-
• 智能建树: 自动识别父子关系,构建完整调用树
-
• 实时统计: 同步更新性能统计信息
3. 数据模型:完整的SQL节点信息
@Data
publicclassSqlNode {
private String nodeId; // 节点唯一标识
private String sql; // SQL语句
private String formattedSql; // 格式化后的SQL
private 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; // 是否为慢SQL
privateint affectedRows; // 影响行数
private String errorMessage; // 错误信息
private List<Object> parameters; // SQL参数
private List<SqlNode> children; // 子节点
// 智能分析方法
publicbooleanisSlowSql(long threshold) {
return executionTime > threshold;
}
publicintgetTotalNodeCount() {
return1 + children.stream().mapToInt(SqlNode::getTotalNodeCount).sum();
}
publicintgetMaxDepth() {
return children.isEmpty() ? depth :
children.stream().mapToInt(SqlNode::getMaxDepth).max().orElse(depth);
}
}
4. RESTful API:完整的数据接口
SqlTreeController
提供完整的REST API接口,支持数据查询、配置管理和系统监控:
@RestController
@RequestMapping("/api/sql-tree")
publicclassSqlTreeController {
@Autowired
private 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 = newHashMap<>();
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() {
SqlStatisticsstats= sqlCallTreeContext.getStatistics();
Map<String, Object> response = newHashMap<>();
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 = newHashMap<>();
if (config.containsKey("slowSqlThreshold")) {
longthreshold= ((Number) config.get("slowSqlThreshold")).longValue();
sqlCallTreeContext.setSlowSqlThreshold(threshold);
response.put("slowSqlThreshold", threshold);
}
if (config.containsKey("traceEnabled")) {
booleanenabled= (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 = newArrayList<>();
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 = newHashMap<>();
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 = newHashMap<>();
health.put("status", "UP");
health.put("traceEnabled", sqlCallTreeContext.isTraceEnabled());
health.put("slowSqlThreshold", sqlCallTreeContext.getSlowSqlThreshold());
health.put("timestamp", LocalDateTime.now());
return ResponseEntity.ok(health);
}
/**
* 递归收集慢SQL节点
*/
privatevoidcollectSlowSqlNodes(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 - 主要的可视化逻辑
classSqlTreeVisualizer {
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.html
-
• H2数据库控制台:
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:8080
spring:
application:
name:springboot-sql-tree
datasource:
url:jdbc:h2:mem:testdb
driver-class-name:org.h2.Driver
username:sa
password:
schema:classpath:schema.sql
data:classpath:data.sql
h2:
console:
enabled:true
path:/h2-console
mybatis:
mapper-locations:classpath:mapper/*.xml
type-aliases-package:com.example.sqltree.entity
configuration:
map-underscore-to-camel-case:true
lazy-loading-enabled:true
cache-enabled:true
log-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监控工具,为日常开发和性能优化提供有力支持。
更多推荐
所有评论(0)