在构建Text2SQL系统时,表关系维护是一个关键挑战。传统手动维护方式不仅工作量大,还容易出错。本文将介绍如何通过自动化工具从MyBatis Mapper文件中提取表关系并构建Neo4j图数据库,为Text2SQL应用提供准确的数据基础。
表关系在Text2SQL中的重要性
核心挑战分析
在Text2SQL工作流程中,表关系直接影响SQL生成的准确性:
用户问题 → 检索相关表 → 获取表关系 → 生成SQL → 执行查询 → 返回结果
表关系缺失的后果:
- 多表查询时无法确定JOIN条件
- 可能生成笛卡尔积查询
- 字段关联关系不明确
手动维护的痛点
传统方式需要在代码中手动维护关系列表:
RELATIONSHIPS = [
{
"from_table": "t_customers",
"to_table": "t_sales_orders",
"description": "t_customers places t_sales_orders",
"field_relation": "customer_id references customer_id",
}
# 数十上百个关系需要手动维护
]
面临的主要问题:
- 工作量大:需要逐个查看Mapper文件寻找JOIN关系
- 容易遗漏:复杂项目中难以保证完整性
- 更新困难:表结构变化后需要手动同步
自动化解决方案架构
整体设计
将自动化工具与Text2SQL Agent结合,形成完整解决方案:
Java项目(Mapper.xml) → 自动化工具 → Neo4j图数据库 → Text2SQL Agent
核心模块组成
| 模块 |
功能 |
技术栈 |
| 扫描模块 |
递归扫描项目文件 |
Python os.walk |
| 解析模块 |
提取SQL语句 |
xml.etree.ElementTree |
| 关系提取 |
识别JOIN和WHERE关系 |
正则表达式 |
| 数据写入 |
导入Neo4j |
py2neo |
关键技术实现
Mapper文件扫描
def scan_mapper_files(self) -> List[str]:
"""扫描Spring Boot项目中的Mapper XML文件"""
mapper_files = []
skip_dirs = {'target', 'build', '.git', 'node_modules'}
for root, dirs, files in os.walk(self.project_path):
dirs[:] = [d for d in dirs if d not in skip_dirs]
for file in files:
if not file.lower().endswith('.xml'):
continue
if 'mapper' in file.lower():
full_path = os.path.join(root, file)
mapper_files.append(full_path)
return mapper_files
SQL语句解析
处理包含动态标签的MyBatis SQL:
def _extract_sql_text(self, node: ET.Element) -> str:
"""递归提取SQL文本(包括动态标签)"""
sql_parts = []
if node.text:
sql_parts.append(node.text.strip())
for child in node:
child_text = self._extract_sql_text(child)
if child_text:
sql_parts.append(child_text)
if child.tail:
sql_parts.append(child.tail.strip())
return ' '.join(sql_parts)
JOIN关系识别
支持多种JOIN类型识别:
def _extract_join_relationships(self, sql: str, tables: Set[str]) -> List[Dict]:
"""从JOIN语句提取表关系"""
join_pattern = r'(LEFT\s+JOIN|RIGHT\s+JOIN|INNER\s+JOIN|JOIN)\s+' \
r'([a-zA-Z_][a-zA-Z0-9_]*)\s+' \
r'(?:AS\s+)?([a-zA-Z_][a-zA-Z0-9_]*)?\s+' \
r'ON\s+([a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*' \
r'([a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*)'
matches = re.findall(join_pattern, sql, re.IGNORECASE)
relationships = []
# 处理匹配结果...
return relationships
数据写入Neo4j
def create_table_relationships(self):
"""将关系写入Neo4j图数据库"""
for rel in self.relationships:
cypher = """
MATCH (from_table:Table {name: $from_table})
MATCH (to_table:Table {name: $to_table})
MERGE (from_table)-[r:REFERENCES {
description: $description,
field_relation: $field_relation,
join_type: $join_type,
source_file: $source_file,
sql_id: $sql_id
}]->(to_table)
"""
self.graph.run(cypher, **rel)
实战应用案例
电商系统场景
假设电商系统包含以下核心表:
- t_user(用户表)
- t_order(订单表)
- t_order_detail(订单明细表)
- t_product(商品表)
- t_category(分类表)
自动化提取流程
- 运行工具:执行Python脚本扫描项目
- 关系提取:自动识别JOIN关系
- 图谱构建:在Neo4j中生成表关系图谱
t_user ──[user_id]──> t_order ──[order_id]──> t_order_detail ──[product_id]──> t_product
Text2SQL集成效果
用户查询:"查询张三购买过的所有商品名称"
Text2SQL Agent基于表关系生成准确SQL:
SELECT DISTINCT p.product_name
FROM t_user u
INNER JOIN t_order o ON u.user_id = o.user_id
INNER JOIN t_order_detail od ON o.order_id = od.order_id
INNER JOIN t_product p ON od.product_id = p.product_id
WHERE u.user_name = '张三'
性能优化建议
大型项目处理
- 批量写入:使用事务提升写入速度
- 并行解析:多进程处理Mapper文件
- 索引优化:为Table.name创建索引
- 增量更新:支持部分关系更新
扩展功能
- 支持更多ORM框架(JPA/Hibernate)
- 关系基数分析(1:1、1:N、N:N)
- 循环依赖检测
- 可视化Web界面
总结
通过自动化工具从MyBatis Mapper提取表关系,显著提升了Text2SQL系统的准确性和维护效率:
| 维度 |
传统方式 |
自动化方案 |
| 维护时间 |
2-3天 |
5分钟 |
| 准确性 |
容易遗漏 |
完整提取 |
| 更新成本 |
高 |
低 |
| Text2SQL准确率 |
~60% |
~95% |
该方案与Text2SQL Agent形成完整生态,为自然语言查询数据库提供了可靠的数据基础。