SpringAI实践(四)

MCP作为一个开放的协议,简化了我们使用语言大模型跟外部工具和工具的通信,提供了标准的上下文方式,那我们能在这个基础之上做哪些应用呢?我首先想到的是能否让大模型读取我们数据库的表结构,直接基于表进行代码生成,辅助我们开发,直接实践一下。

一、明确需求

使用Spring AI 开发连接数据库的MCP服务端,最好可以支持多种数据库,MCP提供可以访问数据库元数据的能力,包括数据表信息、表的列信息、表外键关联信息、索引信息等;可以辅助我们生成代码。

二、新建工程引入依赖

我们新建一个名为dbc-mcp-server的Spring工程,引入依赖,这里为了方便我们扩展支持其他数据库依赖,我们引入阿里数据库连接池、Mysql驱动包、Sqlite驱动包等,支持通过配置文件修改连接不同的数据库。

这里我们使用SSE方式来调用MCP Server,所以引入webflu相关的依赖,也引入mybaits方便我们使用SQL查询数据信息

xml 复制代码
<dependency>  
    <groupId>org.springframework.ai</groupId>  
    <artifactId>spring-ai-core</artifactId>  
</dependency>  
  
<dependency>  
    <groupId>org.springframework.ai</groupId>  
    <artifactId>spring-ai-mcp</artifactId>  
</dependency>  
  
<dependency>  
    <groupId>io.modelcontextprotocol.sdk</groupId>  
    <artifactId>mcp-spring-webflux</artifactId>  
</dependency>  
  
  
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-webflux</artifactId>  
</dependency>  
  
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-test</artifactId>  
    <scope>test</scope>  
</dependency>  
  
<!--阿里数据库连接池 -->  
<dependency>  
    <groupId>com.alibaba</groupId>  
    <artifactId>druid-spring-boot-starter</artifactId>  
    <version>${druid.version}</version>  
</dependency>  
  
<!-- Mysql驱动包 -->  
<dependency>  
    <groupId>mysql</groupId>  
    <artifactId>mysql-connector-java</artifactId>  
    <version>8.0.22</version>  
</dependency>  
  
<!-- Sqlite驱动包 -->  
<dependency>  
    <groupId>org.xerial</groupId>  
    <artifactId>sqlite-jdbc</artifactId>  
</dependency>  
  
<!-- SqlServer驱动包 -->  
<dependency>  
    <groupId>com.microsoft.sqlserver</groupId>  
    <artifactId>mssql-jdbc</artifactId>  
</dependency>  
  
<!-- PostgreSql驱动包 -->  
<dependency>  
    <groupId>org.postgresql</groupId>  
    <artifactId>postgresql</artifactId>  
</dependency>  
  
<!-- SpringBoot集成mybatis框架 -->  
<dependency>  
    <groupId>org.mybatis.spring.boot</groupId>  
    <artifactId>mybatis-spring-boot-starter</artifactId>  
    <version>${mybatis.spring.boot.starter.version}</version>  
</dependency>

二、配置文件

启动在端口8081上,增加连接数据库的信息和mybaits的配置

properties 复制代码
server.port=8081  
  
spring.application.name=dbc-mcp-server  
  
spring.main.banner-mode=off  
logging.pattern.console=  
  
# transport.mode=stdio  
transport.mode=sse  
logging.file.name=dbc-mcp-server.log  
  
# AppProperties
app.dbUrl=127.0.0.1:3306
app.dbUser=root
app.dbPassword=your_pwd
app.dbDriver=com.mysql.cj.jdbc.Driver
app.dbName=your_db
app.dbType=mysql
  
mybatis.type-aliases-package=com.renne.ai.dbcmcp.domain  
mybatis.mapper-locations=classpath:mybatis/**/*Mapper.xml  
mybatis.config-location=classpath:mybatis/mybatis-config.xml

三、编写MCP Service方法

这里的Service就是获取数据库元数据的方法,包括数据库结构、表、表关联信息

java 复制代码
package com.renne.ai.dbcmcp.service;  
  
import com.renne.ai.dbcmcp.domain.SysTableColumnMeta;  
import com.renne.ai.dbcmcp.domain.SysTableKeyColumnUsage;  
import com.renne.ai.dbcmcp.domain.SysTableMeta;  
import com.renne.ai.dbcmcp.domain.SysTableUniqueMeta;  
import com.renne.ai.dbcmcp.mapper.DBQueryMapper;  
import com.renne.ai.dbcmcp.properties.AppProperties;  
import com.renne.ai.dbcmcp.utils.SpringUtils;  
import org.springframework.ai.tool.annotation.Tool;  
import org.springframework.ai.tool.annotation.ToolParam;  
import org.springframework.stereotype.Service;  
  
import javax.annotation.Resource;  
import java.util.List;  
  
/**  
 * 查询数据库表结构的服务  
 *  
 * @author LiuYu  
 * @since 2025-03-31 19:01  
 */@Service  
public class DBTableStructureService {  
  
    @Resource  
    private AppProperties appProperties;  
  
    /**  
     * 查询数据库所有表结构并返回表名和注释信息。  
     * <p>  
     * 该函数通过调用数据库查询接口,获取指定数据库中的所有表名及其注释信息,并将结果以字符串形式返回。  
     *  
     * @return 返回包含所有表名和注释信息的字符串,格式为列表的字符串表示形式。  
     */  
    @Tool(name = "getTableNameAndComment", description = """  
            查询数据库所有表结构并返回表名和注释信息。  
            该函数通过调用数据库查询接口,获取指定数据库中的所有表名及其注释信息,并将结果以字符串形式返回。            返回包含所有表名和注释信息的字符串,格式为列表的字符串表示形式。  
            """)  
    public String getTableNameAndComment() {  
        // 获取当前应用的数据库名称  
        String dbName = appProperties.getDbName();  
  
        // 通过SpringUtils获取DBQueryMapper的实例,并调用其方法查询表名和注释信息  
        List<SysTableMeta> tableNameAndComment = SpringUtils.getBean(DBQueryMapper.class).getTableNameAndComment(dbName);  
  
        // 将查询结果转换为字符串并返回  
        return tableNameAndComment.toString();  
    }  
  
    /**  
     * 查询数据库表字段的详细信息。  
     * <p>  
     * 该函数通过指定的表名,查询数据库中该表的字段信息,并返回字段的元数据列表。  
     *  
     * @param tableName 表名,用于指定要查询的数据库表。  
     * @return 返回包含表字段元数据的字符串表示形式。  
     */  
    @Tool(name = "getTableColumnDetail", description = """  
            查询数据库表字段的详细信息。  
            该函数通过指定的表名,查询数据库中该表的字段信息,并返回字段的元数据列表。             返回包含表字段元数据的字符串表示形式。  
            """)  
    public String getTableColumnDetail(@ToolParam(description = "表名,用于指定要查询的数据库表。") String tableName) {  
        // 获取当前应用的数据库名称  
        String dbName = appProperties.getDbName();  
  
        // 通过DBQueryMapper查询指定表的字段元数据  
        List<SysTableColumnMeta> tableNameAndComment = SpringUtils.getBean(DBQueryMapper.class).getTableColumnDetail(dbName, tableName);  
  
        // 将字段元数据列表转换为字符串并返回  
        return tableNameAndComment.toString();  
    }  
  
    /**  
     * 查询指定数据库表的外键信息。  
     * <p>  
     * 该函数通过调用数据库查询接口,获取指定表的外键信息,并将其以字符串形式返回。  
     *  
     * @param tableName 需要查询外键信息的表名。  
     * @return 返回包含外键信息的字符串,格式为列表的字符串表示。  
     */  
    @Tool(name = "getTableForeignKeys", description = """  
            查询指定数据库表的外键信息。  
            该函数通过调用数据库查询接口,获取指定表的外键信息,并将其以字符串形式返回。            返回包含外键信息的字符串,格式为列表的字符串表示。  
            """)  
    public String getTableForeignKeys(@ToolParam(description = "需要查询外键信息的表名。") String tableName) {  
        // 获取当前应用的数据库名称  
        String dbName = appProperties.getDbName();  
  
        // 通过DBQueryMapper查询指定表的外键信息  
        List<SysTableKeyColumnUsage> foreignKeys = SpringUtils.getBean(DBQueryMapper.class).getTableForeignKeys(dbName, tableName);  
  
        // 将外键信息列表转换为字符串并返回  
        return foreignKeys.toString();  
    }  
  
    /**  
     * 查询指定表的索引信息。  
     *  
     * 该函数通过调用数据库查询接口,获取指定表的索引信息,并将结果以字符串形式返回。  
     *  
     * @param tableName 表名,用于指定需要查询索引信息的表。  
     * @return 返回指定表的索引信息,以字符串形式表示。  
     */  
    @Tool(name = "getTableIndexes", description = """  
            查询指定表的索引信息。  
            该函数通过调用数据库查询接口,获取指定表的索引信息,并将结果以字符串形式返回。            返回指定表的索引信息,以字符串形式表示。  
            """)  
    public String getTableIndexes(@ToolParam(description = "表名,用于指定需要查询索引信息的表。") String tableName) {  
        // 获取当前应用的数据库名称  
        String dbName = appProperties.getDbName();  
  
        // 调用数据库查询接口,获取指定表的索引信息  
        List<SysTableUniqueMeta> indexes = SpringUtils.getBean(DBQueryMapper.class).getTableIndexes(dbName, tableName);  
  
        // 将索引信息转换为字符串并返回  
        return indexes.toString();  
    }  
  
    /**  
     * 查询指定表的行数。  
     *  
     * 该函数通过调用数据库查询接口,获取指定表的行数,并返回包含行数的字符串信息。  
     *  
     * @param tableName 需要查询行数的表名,不能为空。  
     * @return 返回一个字符串,格式为 "表 {tableName} 的行数为: {rowCount}",其中 {tableName} 是传入的表名,{rowCount} 是查询到的行数。  
     */  
    @Tool(name = "getTableRowCount", description = """  
            查询指定表的行数。  
            该函数通过调用数据库查询接口,获取指定表的行数,并返回包含行数的字符串信息。            返回一个字符串,格式为 "表 {tableName} 的行数为: {rowCount}",其中 {tableName} 是传入的表名,{rowCount} 是查询到的行数。  
            """)  
    public String getTableRowCount(@ToolParam(description = "需要查询行数的表名。") String tableName) {  
        // 通过 SpringUtils 获取 DBQueryMapper 的实例,并调用其 getTableRowCount 方法查询表的行数  
        int rowCount = SpringUtils.getBean(DBQueryMapper.class).getTableRowCount(tableName);  
  
        // 返回包含表名和行数的字符串  
        return "表 " + tableName + " 的行数为: " + rowCount;  
    }  
  
    /**  
     * 查询数据库的存储引擎。  
     *  
     * 该函数通过应用程序配置获取数据库名称,并使用该名称查询数据库的存储引擎。  
     * 最终返回一个包含数据库名称和存储引擎信息的字符串。  
     *  
     * @return 返回一个字符串,格式为 "数据库 [数据库名称] 的存储引擎为: [存储引擎]"  
     */    @Tool(name = "getDatabaseEngine", description = """  
            查询数据库的存储引擎。  
            该函数通过应用程序配置获取数据库名称,并使用该名称查询数据库的存储引擎,最终返回一个包含数据库名称和存储引擎信息的字符串。            返回一个字符串,格式为 "数据库 [数据库名称] 的存储引擎为: [存储引擎]"  
            """)  
    public String getDatabaseEngine() {  
        // 从应用程序配置中获取数据库名称  
        String dbName = appProperties.getDbName();  
  
        // 通过Spring工具类获取DBQueryMapper的实例,并查询数据库的存储引擎  
        String engine = SpringUtils.getBean(DBQueryMapper.class).getDatabaseEngine(dbName);  
  
        // 返回包含数据库名称和存储引擎信息的字符串  
        return "数据库 " + dbName + " 的存储引擎为: " + engine;  
    }  
}

编写一个生成代码的角色提示词Service,让模型生成代码时候先获取提示词作为上下文

java 复制代码
package com.renne.ai.dbcmcp.service;  
  
  
import org.springframework.ai.tool.annotation.Tool;  
import org.springframework.stereotype.Service;  
  
/**  
 * 内置角色提示词服务  
 *  
 * @author LiuYu  
 * @since 2025-04-01 14:26  
 */@Service  
public class RolePromptService {  
  
    /**  
     * 获取RuoYi Vue3代码生成角色提示词  
     *  
     * @return 角色提示词  
     */  
    @Tool(name = "getRuoYiVue3CodeGenRolePrompt", description = "获取RuoYi Vue3代码生成角色提示词")  
     public String getRuoYiVue3CodeGenRolePrompt() {  
         String prompt = """  
                 - Role: 前端开发工程师和代码生成专家  
                 - Background: 用户需要快速生成Vue项目的列表页面、编辑页面和详情页面,期望通过输入字段名来自动化生成代码,以提高开发效率,减少重复劳动。  
                 - Profile: 你是一位精通Vue.js框架的前端开发工程师,对Vue组件的开发有着丰富的经验,能够根据查询到的字段信息,快速生成符合规范的页面代码。  
                 - Skills: 你具备Vue.js项目开发、代码自动化生成、前端界面设计、数据结构解析等关键能力,能够根据字段名、字段类型和字段含义,自动生成对应的页面代码。  
                 - Goals: 根据查询到的字段名(支持中英文混合,带类型或不带类型),快速生成列表页面、编辑页面和详情页面的Vue代码。  
                 - Constrains: 生成的代码应遵循Vue.js的最佳实践,代码结构清晰,易于维护和扩展,确保字段名的正确解析和字段类型的准确应用。  
                 - OutputFormat: 生成的代码应包含完整的Vue组件结构,包括模板、脚本和样式,支持字段名的自动翻译和字段类型的自动识别。  
                 - Workflow:                   1. 解析查询到的字段名,识别中英文字段名、字段类型(如果存在)。  
                   2. 根据字段信息生成列表页面的代码,包括表格展示和分页功能。  
                   3. 根据字段信息生成编辑页面的代码,根据字段类型生成对应的表单项。  
                   4. 根据字段信息生成详情页面的代码,展示字段的详细信息。  
                                """;  
         return prompt;  
     }  
}

其他具体代码,看仓库地址吧,我会放在文末。

四、在Cursor中配置服务

在Cursor的mcp.json文件中增加配置,我们可以看到Cursor已经读取到我们写的方法

json 复制代码
{
    "mcpServers": {
        "dbc-mcp-server": {
            "url": "http://localhost:8081/sse"
        }
    }
}

配置好之后,我们在Cursor的chat中提问,数据库中有哪些表

提问,岗位信息表中有哪些字段,也能正确回答

继续提问,帮我生成岗位信息表的RuoYi Vue3部分的代码,这里也能完成代码生成,列表字段也都正确列出来

这样我们就完成了一个可以辅助代码生成的连接数据库MCP服务端了。

这里是我Gitee仓库地址dbc-mcp-server,代码里目前只实现了mysql数据库的连接,后面会补全其他数据库的连接

相关推荐
数据智能老司机2 小时前
Spring AI 实战——提交用于生成的提示词
spring·llm·ai编程
数据智能老司机2 小时前
Spring AI 实战——评估生成结果
spring·llm·ai编程
该用户已不存在3 小时前
免费的 Vibe Coding 助手?你想要的Gemini CLI 都有
人工智能·后端·ai编程
一只柠檬新5 小时前
当AI开始读源码,调Bug这件事彻底变了
android·人工智能·ai编程
用户4099322502127 小时前
Vue 3中watch侦听器的正确使用姿势你掌握了吗?深度监听、与watchEffect的差异及常见报错解析
前端·ai编程·trae
转转技术团队8 小时前
Cursor在回收团队的实践
cursor
yaocheng的ai分身8 小时前
【转载】我如何用Superpowers MCP强制Claude Code在编码前进行规划
ai编程·claude
重铸码农荣光9 小时前
从逐行编码到「氛围编程」:Trae 带你进入 AI 编程新纪元
ai编程·trae·vibecoding
Juchecar9 小时前
利用AI辅助"代码考古“操作指引
人工智能·ai编程
Juchecar9 小时前
AI时代,如何在人机协作中保持代码的清晰性与一致性
人工智能·ai编程