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数据库的连接,后面会补全其他数据库的连接

相关推荐
Captaincc14 小时前
Arc浏览器团队新品Dia一手体验:为什么最牛的AI应用开发者,都在做AI浏览器?
ai编程
骑猪兜风23316 小时前
没有人知道“他妈的” 智能体到底是什么
人工智能·openai·ai编程
时物留影16 小时前
不写代码也能开发 API?试试这个组合!
前端·ai编程
我是阳明16 小时前
开发一个基于 SSE 类型的 Claude MCP 智能商城助手
aigc·cursor·mcp
蓝衣剑客16 小时前
山姆·奥特曼传(二):OpenAI的第一次内斗
人工智能·ai编程
蓝衣剑客16 小时前
山姆·奥特曼传(三):硬币的两面
人工智能·ai编程
蓝衣剑客16 小时前
山姆·奥特曼传(一):我是如何走到今天的
人工智能·ai编程
蓝衣剑客16 小时前
山姆·奥特曼传(四):安妮的心结
人工智能·ai编程
Captaincc17 小时前
Windsurf 编辑器六大更新支持一键部署功能
ai编程