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.javaComponentpublicclassSchemaExtractor{AutowiredprivateDataSourcedataSource;/** * 提取指定数据库的表结构格式化为 Prompt 友好的文本 */publicStringextractSchema(String...tableNames)throwsSQLException{StringBuildersbnewStringBuilder();sb.append(【数据库表结构】\n);try(ConnectionconndataSource.getConnection()){DatabaseMetaDatametaconn.getMetaData();for(StringtableName:tableNames){sb.append(\n表名).append(tableName).append(\n);// 获取列信息ResultSetcolumnsmeta.getColumns(null,null,tableName,null);sb.append(字段列表\n);while(columns.next()){StringcolNamecolumns.getString(COLUMN_NAME);StringcolTypecolumns.getString(TYPE_NAME);Stringremarkscolumns.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.javaServiceSlf4jpublicclassText2SqlService{AutowiredprivateSchemaExtractorschemaExtractor;AutowiredprivateSqlSecurityCheckersqlSecurityChecker;AutowiredprivateJdbcTemplatejdbcTemplate;// DeepSeek API 配置兼容 OpenAI SDKprivatefinalOpenAiApiopenAiApi;publicText2SqlService(Value(${deepseek.api-key})StringapiKey){this.openAiApinewOpenAiApi(https://api.deepseek.com,apiKey);}/** * 主流程自然语言 → SQL → 查询结果 */publicQueryResultquery(StringnaturalLanguage)throwsException{// 1. 提取 SchemaStringschemaschemaExtractor.extractSchema(orders,products,users);// 2. 调用 LLM 生成 SQLStringsqlgenerateSql(naturalLanguage,schema);log.info(生成 SQL: {},sql);// 3. 安全检查sqlSecurityChecker.check(sql);// 4. 执行查询ListMapString,ObjectdatajdbcTemplate.queryForList(sql);// 5. 推荐图表类型StringchartTyperecommendChartType(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 ;StringuserPromptschema\n\n【用户问题】\nquestion;ChatCompletionRequestrequestChatCompletionRequest.builder().model(deepseek-chat).messages(List.of(newChatCompletionMessage(systemPrompt,Role.SYSTEM),newChatCompletionMessage(userPrompt,Role.USER))).temperature(0.1)// 低温度确保SQL稳定.maxTokens(1024).build();ChatCompletionresponseopenAiApi.chatCompletion(request).block();StringrawSqlresponse.choices().get(0).message().content();// 清理可能的 markdown 代码块标记returncleanSql(rawSql);}privateStringcleanSql(Stringraw){returnraw.replaceAll(sql,).replaceAll(,).trim();}privateStringrecommendChartType(Stringquestion,ListMapString,Objectdata){if(data.isEmpty())returntable;// 简单规则根据关键词和数据结构推断Stringqquestion.toLowerCase();intcolsdata.get(0).size();if(q.contains(趋势)||q.contains(每月)||q.contains(每天)){returnline;// 折线图}elseif(q.contains(占比)||q.contains(比例)||q.contains(分布)){returnpie;// 饼图}elseif(cols2){returnbar;// 柱状图}else{returntable;// 数据表格}}}4.2 SQL 安全防注入检查器// SqlSecurityChecker.javaComponentpublicclassSqlSecurityChecker{// 危险关键词黑名单privatestaticfinalListStringDANGEROUS_KEYWORDSArrays.asList(INSERT,UPDATE,DELETE,DROP,TRUNCATE,ALTER,CREATE,GRANT,REVOKE,EXEC,EXECUTE,UNION,--,/*,xp_);// 允许的 SQL 起始词白名单privatestaticfinalListStringALLOWED_STARTSArrays.asList(SELECT,WITH);publicvoidcheck(Stringsql){if(sqlnull||sql.trim().isEmpty()){thrownewIllegalArgumentException(SQL 不能为空);}StringupperSqlsql.trim().toUpperCase();// 白名单必须以 SELECT 或 WITH 开头booleanvalidStartALLOWED_STARTS.stream().anyMatch(upperSql::startsWith);if(!validStart){thrownewSecurityException(只允许 SELECT 查询拒绝执行: sql);}// 黑名单检测危险关键词for(Stringkeyword:DANGEROUS_KEYWORDS){// 使用词边界匹配避免误判如 selector 误报 SELECTStringpattern\\bkeyword\\b;if(upperSql.matches(.*pattern.*)){thrownewSecurityException(SQL 包含危险操作: keyword);}}// 长度限制if(sql.length()5000){thrownewIllegalArgumentException(SQL 过长拒绝执行);}}}4.3 Controller 层支持 SSE 流式// QueryController.javaRestControllerRequestMapping(/api/query)CrossOriginpublicclassQueryController{AutowiredprivateText2SqlServicetext2SqlService;PostMapping(/ask)publicResponseEntityQueryResultask(RequestBodyQueryRequestrequest){try{QueryResultresulttext2SqlService.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(查询失败请换个说法重试));}}}// 请求/响应 DTODataBuilderpublicclassQueryResult{privateStringsql;privateListMapString,Objectdata;privateStringchartType;privateStringerror;publicstaticQueryResulterror(Stringmsg){returnQueryResult.builder().error(msg).build();}}五、前端 ECharts 动态渲染根据后端返回的chartType和data前端自动选择图表类型// ChartRenderer.vuetemplatedivdivclassquery-inputel-input v-modelquestionplaceholder请输入问题如去年每月的销售额是多少keyup.enterquery/el-button typeprimaryclickquery:loadingloading查询/el-button/div!--生成的SQL展示--div v-ifresult.sqlclasssql-previewcode{{result.sql}}/code/div!--动态图表--div refchartRefstylewidth: 100%; height: 400px;v-showshowChart/!--数据表格降级--el-table v-ifresult.chartType table:dataresult.databorder//div/templatescript setupimport*asechartsfromechartsimport{ref,onMounted,watch}fromvueimportaxiosfromaxiosconstquestionref()constresultref({})constloadingref(false)constchartRefref(null)letchartInstancenullonMounted((){chartInstanceecharts.init(chartRef.value)})constqueryasync(){loading.valuetruetry{const{data}awaitaxios.post(/api/query/ask,{question:question.value})result.valuedataif(data.chartType!table){renderChart(data)}}finally{loading.valuefalse}}constrenderChart(result){constrowsresult.dataif(!rows||rows.length0)returnconstkeysObject.keys(rows[0])constxKeykeys[0]// 第一列为 X 轴时间/类别constyKeykeys[1]// 第二列为 Y 轴数值constxDatarows.map(rr[xKey])constyDatarows.map(rNumber(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各城市订单量 Top10✅1.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 图表的智能报表全链路。核心要点Schema 注释的质量决定 SQL 准确率的上限业务含义必须写清楚SQL 安全检查是必须的绝不能省略白名单校验图表类型推荐可以先用简单规则后期迭代为大模型推荐生产环境需要审计日志方便追踪问题和持续优化 Prompt