news 2026/5/8 16:58:41

Java 调用大模型实现智能报表分析:自然语言 → SQL → 图表全链路实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 调用大模型实现智能报表分析:自然语言 → SQL → 图表全链路实战

摘要:本文手把手实现"输入一句话,自动生成图表"的智能报表系统。完整覆盖 Text2SQL 原理、Java 后端实现、前端 ECharts 动态渲染、SQL 安全防注入五大模块,附可直接运行的 Spring Boot 完整代码。关键词:Text2SQL、自然语言转SQL、Java大模型、智能报表、ECharts、Spring Boot AI。


一、Text2SQL 是什么?适合哪些场景?

Text2SQL 让用户用自然语言查询数据库,无需写 SQL:

用户输入:去年每个月的销售额是多少? 系统生成:SELECT DATE_FORMAT(order_date,'%Y-%m')as month, SUM(amount)as total FROM orders WHERE YEAR(order_date)=2024GROUP BY month ORDER BY month

适用场景

场景传统方案Text2SQL 方案
运营看数找 BI 工程师手写报表(1-3天)自然语言提问,秒级出图
管理层查询依赖固定报表模板灵活临时查询,自助分析
数据探查需要 SQL 能力业务人员自助

不适用场景:超复杂多表嵌套子查询(>5张表关联)、数据写入操作(只做查询)。


二、整体系统架构

前端(Vue3 + ECharts) ↕ HTTP / SSE Spring Boot 后端 ├── NL 接收层:接收用户自然语言输入 ├── Schema 注入层:从数据库读取表结构 ├── LLM 调用层:调用 DeepSeek 生成 SQL ├── SQL 安全层:白名单校验 + 危险语句拦截 ├── JDBC 执行层:执行 SQL,返回结构化数据 └── 图表推荐层:根据数据结构推荐图表类型

技术栈:

  • 后端:Spring Boot 3.x + MyBatis + HikariCP
  • 大模型:DeepSeek V4 API(兼容 OpenAI SDK)
  • 数据库:MySQL 8.0
  • 前端:Vue3 + ECharts 5.x

三、数据库 Schema 注入 Prompt 的正确姿势

Schema 信息是 Text2SQL 的核心。大模型需要知道有哪些表、哪些字段、字段含义,才能生成正确的 SQL。

3.1 Schema 自动提取工具

// SchemaExtractor.java@ComponentpublicclassSchemaExtractor{@AutowiredprivateDataSourcedataSource;/** * 提取指定数据库的表结构,格式化为 Prompt 友好的文本 */publicStringextractSchema(String...tableNames)throwsSQLException{StringBuildersb=newStringBuilder();sb.append("【数据库表结构】\n");try(Connectionconn=dataSource.getConnection()){DatabaseMetaDatameta=conn.getMetaData();for(StringtableName:tableNames){sb.append("\n表名:").append(tableName).append("\n");// 获取列信息ResultSetcolumns=meta.getColumns(null,null,tableName,null);sb.append("字段列表:\n");while(columns.next()){StringcolName=columns.getString("COLUMN_NAME");StringcolType=columns.getString("TYPE_NAME");Stringremarks=columns.getString("REMARKS");// 注释sb.append(String.format(" - %s (%s)%s\n",colName,colType,remarks!=null&&!remarks.isEmpty()?" // "+remarks:""));}}}returnsb.toString();}}

3.2 Schema 示例(业务含义注释很关键)

【数据库表结构】 表名:orders 字段列表: -id(BIGINT)// 订单ID - order_no(VARCHAR)// 订单编号 - user_id(BIGINT)// 用户ID - amount(DECIMAL)// 订单金额(元) - status(INT)// 订单状态:0待付款1已付款2已发货3已完成4已取消 - order_date(DATETIME)// 下单时间 - city(VARCHAR)// 下单城市 表名:products 字段列表: -id(BIGINT)// 商品ID - name(VARCHAR)// 商品名称 - category(VARCHAR)// 商品分类 - price(DECIMAL)// 售价(元)

关键经验:字段的业务含义注释(如status各值代表什么)是生成正确 SQL 的最重要因素,不要省略!


四、Java 后端实现:完整链路代码

4.1 核心 Service 层

// Text2SqlService.java@Service@Slf4jpublicclassText2SqlService{@AutowiredprivateSchemaExtractorschemaExtractor;@AutowiredprivateSqlSecurityCheckersqlSecurityChecker;@AutowiredprivateJdbcTemplatejdbcTemplate;// DeepSeek API 配置(兼容 OpenAI SDK)privatefinalOpenAiApiopenAiApi;publicText2SqlService(@Value("${deepseek.api-key}")StringapiKey){this.openAiApi=newOpenAiApi("https://api.deepseek.com",apiKey);}/** * 主流程:自然语言 → SQL → 查询结果 */publicQueryResultquery(StringnaturalLanguage)throwsException{// 1. 提取 SchemaStringschema=schemaExtractor.extractSchema("orders","products","users");// 2. 调用 LLM 生成 SQLStringsql=generateSql(naturalLanguage,schema);log.info("生成 SQL: {}",sql);// 3. 安全检查sqlSecurityChecker.check(sql);// 4. 执行查询List<Map<String,Object>>data=jdbcTemplate.queryForList(sql);// 5. 推荐图表类型StringchartType=recommendChartType(naturalLanguage,data);returnQueryResult.builder().sql(sql).data(data).chartType(chartType).build();}privateStringgenerateSql(Stringquestion,Stringschema){StringsystemPrompt=""" 你是一个专业的数据库工程师,精通 MySQL。 根据用户的自然语言问题和提供的数据库表结构,生成一条正确的 MySQL 查询 SQL。 【要求】 1. 只返回 SQL 语句本身,不要有任何解释、注释或 markdown 标记 2. SQL 必须是 SELECT 语句,严禁生成 INSERT/UPDATE/DELETE/DROP 等写操作 3. 使用标准 MySQL 语法 4. 对于时间类查询,使用 DATE_FORMAT、YEAR、MONTH 等函数 5. 涉及金额的字段保留两位小数:ROUND(amount, 2) 6. 结果集超过 1000 行时自动加 LIMIT 1000 """;StringuserPrompt=schema+"\n\n【用户问题】\n"+question;ChatCompletionRequestrequest=ChatCompletionRequest.builder().model("deepseek-chat").messages(List.of(newChatCompletionMessage(systemPrompt,Role.SYSTEM),newChatCompletionMessage(userPrompt,Role.USER))).temperature(0.1)// 低温度,确保SQL稳定.maxTokens(1024).build();ChatCompletionresponse=openAiApi.chatCompletion(request).block();StringrawSql=response.choices().get(0).message().content();// 清理可能的 markdown 代码块标记returncleanSql(rawSql);}privateStringcleanSql(Stringraw){returnraw.replaceAll("```sql","").replaceAll("```","").trim();}privateStringrecommendChartType(Stringquestion,List<Map<String,Object>>data){if(data.isEmpty())return"table";// 简单规则:根据关键词和数据结构推断Stringq=question.toLowerCase();intcols=data.get(0).size();if(q.contains("趋势")||q.contains("每月")||q.contains("每天")){return"line";// 折线图}elseif(q.contains("占比")||q.contains("比例")||q.contains("分布")){return"pie";// 饼图}elseif(cols==2){return"bar";// 柱状图}else{return"table";// 数据表格}}}

4.2 SQL 安全防注入检查器

// SqlSecurityChecker.java@ComponentpublicclassSqlSecurityChecker{// 危险关键词(黑名单)privatestaticfinalList<String>DANGEROUS_KEYWORDS=Arrays.asList("INSERT","UPDATE","DELETE","DROP","TRUNCATE","ALTER","CREATE","GRANT","REVOKE","EXEC","EXECUTE","UNION","--","/*","xp_");// 允许的 SQL 起始词(白名单)privatestaticfinalList<String>ALLOWED_STARTS=Arrays.asList("SELECT","WITH");publicvoidcheck(Stringsql){if(sql==null||sql.trim().isEmpty()){thrownewIllegalArgumentException("SQL 不能为空");}StringupperSql=sql.trim().toUpperCase();// 白名单:必须以 SELECT 或 WITH 开头booleanvalidStart=ALLOWED_STARTS.stream().anyMatch(upperSql::startsWith);if(!validStart){thrownewSecurityException("只允许 SELECT 查询,拒绝执行: "+sql);}// 黑名单:检测危险关键词for(Stringkeyword:DANGEROUS_KEYWORDS){// 使用词边界匹配,避免误判(如 "selector" 误报 "SELECT")Stringpattern="\\b"+keyword+"\\b";if(upperSql.matches(".*"+pattern+".*")){thrownewSecurityException("SQL 包含危险操作: "+keyword);}}// 长度限制if(sql.length()>5000){thrownewIllegalArgumentException("SQL 过长,拒绝执行");}}}

4.3 Controller 层(支持 SSE 流式)

// QueryController.java@RestController@RequestMapping("/api/query")@CrossOriginpublicclassQueryController{@AutowiredprivateText2SqlServicetext2SqlService;@PostMapping("/ask")publicResponseEntity<QueryResult>ask(@RequestBodyQueryRequestrequest){try{QueryResultresult=text2SqlService.query(request.getQuestion());returnResponseEntity.ok(result);}catch(SecurityExceptione){returnResponseEntity.badRequest().body(QueryResult.error("安全检查未通过: "+e.getMessage()));}catch(Exceptione){log.error("查询失败",e);returnResponseEntity.internalServerError().body(QueryResult.error("查询失败,请换个说法重试"));}}}// 请求/响应 DTO@Data@BuilderpublicclassQueryResult{privateStringsql;privateList<Map<String,Object>>data;privateStringchartType;privateStringerror;publicstaticQueryResulterror(Stringmsg){returnQueryResult.builder().error(msg).build();}}

五、前端 ECharts 动态渲染

根据后端返回的chartTypedata,前端自动选择图表类型:

// ChartRenderer.vue<template><div><divclass="query-input"><el-input v-model="question"placeholder="请输入问题,如:去年每月的销售额是多少?"@keyup.enter="query"/><el-button type="primary"@click="query":loading="loading">查询</el-button></div><!--生成的SQL展示--><div v-if="result.sql"class="sql-preview"><code>{{result.sql}}</code></div><!--动态图表--><div ref="chartRef"style="width: 100%; height: 400px;"v-show="showChart"/><!--数据表格降级--><el-table v-if="result.chartType === 'table'":data="result.data"border/></div></template><script setup>import*asechartsfrom'echarts'import{ref,onMounted,watch}from'vue'importaxiosfrom'axios'constquestion=ref('')constresult=ref({})constloading=ref(false)constchartRef=ref(null)letchartInstance=nullonMounted(()=>{chartInstance=echarts.init(chartRef.value)})constquery=async()=>{loading.value=truetry{const{data}=awaitaxios.post('/api/query/ask',{question:question.value})result.value=dataif(data.chartType!=='table'){renderChart(data)}}finally{loading.value=false}}constrenderChart=(result)=>{constrows=result.dataif(!rows||rows.length===0)returnconstkeys=Object.keys(rows[0])constxKey=keys[0]// 第一列为 X 轴(时间/类别)constyKey=keys[1]// 第二列为 Y 轴(数值)constxData=rows.map(r=>r[xKey])constyData=rows.map(r=>Number(r[yKey]))constoptionMap={bar:{xAxis:{type:'category',data:xData},yAxis:{type:'value'},series:[{type:'bar',data:yData,label:{show:true}}]},line:{xAxis:{type:'category',data:xData},yAxis:{type:'value'},series:[{type:'line',data:yData,smooth:true,areaStyle:{}}]},pie:{series:[{type:'pie',data:rows.map(r=>({name:r[xKey],value:r[yKey]})),radius:'60%'}]}}chartInstance.setOption({tooltip:{trigger:'axis'},...optionMap[result.chartType]})}</script>

六、实测效果与已知局限

6.1 实测效果(测试数据库:电商订单系统)

测试问题生成 SQL 是否正确响应时间
去年每月销售额趋势2.3s
各城市订单量 Top101.8s
已完成订单的平均金额1.5s
最近 7 天每日新增用户数2.1s
退款率最高的商品分类⚠️ 需要 refund 表,提示无数据1.9s
每个销售员的业绩排名❌(未提供 sales 表结构)2.0s

准确率约 80-90%,错误主要来自:跨表查询未提供关联表 Schema。

6.2 已知局限与应对方案

局限现象应对方案
多表复杂 JOINSQL 逻辑错误提供更详细的表关系注释
模糊时间描述“最近”、"近期"理解偏差在 Prompt 中定义时间规范
方言/缩写"GMV"等业务术语不识别Schema 注释中解释术语
SQL 注入风险已通过安全检查器覆盖双重防护:LLM 约束 + 白名单

七、生产环境优化建议

// 1. SQL 执行加超时保护jdbcTemplate.setQueryTimeout(30);// 超过 30 秒自动终止// 2. 结果集大小限制if(data.size()>10000){thrownewRuntimeException("查询结果超过1万行,请缩小查询范围");}// 3. 审计日志:记录每次 NL → SQL 的映射@EventListenerpublicvoidlogQuery(QueryEventevent){auditLogRepository.save(AuditLog.builder().question(event.getQuestion()).generatedSql(event.getSql()).executionTime(event.getDuration()).userId(event.getUserId()).build());}

总结

本文完整实现了自然语言 → SQL → ECharts 图表的智能报表全链路。核心要点

  1. Schema 注释的质量决定 SQL 准确率的上限,业务含义必须写清楚
  2. SQL 安全检查是必须的,绝不能省略白名单校验
  3. 图表类型推荐可以先用简单规则,后期迭代为大模型推荐
  4. 生产环境需要审计日志,方便追踪问题和持续优化 Prompt
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/8 16:57:59

为内部知识库构建智能问答助手时选择Taotoken的考量

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 为内部知识库构建智能问答助手时选择Taotoken的考量 构建一个高效、可靠的内部知识库智能问答助手&#xff0c;是现代企业提升信息…

作者头像 李华
网站建设 2026/5/8 16:57:58

多平台内容矩阵 API 适配架构设计与自动化运营体系技术实现

摘要针对全域内容矩阵运营中多平台 API 异构适配难、大规模账号授权管控复杂、自动化操作易触发平台风控等核心技术痛点&#xff0c;本文提出一套分层解耦的多平台 API 适配架构与全链路自动化运营体系。文章深度拆解了主流内容平台 API 的核心差异与技术挑战&#xff0c;详解了…

作者头像 李华
网站建设 2026/5/8 16:57:48

认识Spring IoC

目录前言1. 了解Spring2. IoC3. 注解4. Bean5. 了解DI总结前言 通过本篇文章你将了解到Spring IoC的思想、五大类注解、方法注解,DI。 1. 了解Spring Spring被称为一个框架&#xff0c;有着庞大的社区&#xff0c;支持很多应用场景。 简单来说就是一个包含了众多对象的容器。…

作者头像 李华
网站建设 2026/5/8 16:56:58

Dhizuku:Android设备所有者权限共享架构的技术实现与安全应用

Dhizuku&#xff1a;Android设备所有者权限共享架构的技术实现与安全应用 【免费下载链接】Dhizuku A tool that can share DeviceOwner permissions to other application. 项目地址: https://gitcode.com/gh_mirrors/dh/Dhizuku Dhizuku是一个创新的Android开源工具&a…

作者头像 李华
网站建设 2026/5/8 16:56:42

开发板推送代码到gitlab仓库

摘要 一次性讲透、直接能用的 git bundle create 完整用法&#xff0c;专门适配的场景&#xff1a;Jetson 板子 → 本地电脑 → GitLab&#xff0c; 整套固定流程&#xff1a;Jetson AGX 用 git bundle 打包 → 拷到本地 → 解包 → 推送到 GitLab 一、为什么要用 git bundle&a…

作者头像 李华
网站建设 2026/5/8 16:55:21

终极解决方案:Blender与Rhino的3D数据无缝迁移革命

终极解决方案&#xff1a;Blender与Rhino的3D数据无缝迁移革命 【免费下载链接】import_3dm Blender importer script for Rhinoceros 3D files 项目地址: https://gitcode.com/gh_mirrors/im/import_3dm 当建筑设计师精心构建的曲面模型在导入Blender后出现诡异变形&am…

作者头像 李华