痛点
历史积累代码量巨大, 上千张表, 上千条 sql 和 上百万行的 java 代码, 交接维护的压力比较大.
如果要查询: 某某功能使用了哪些sql, 或者那些表被谁使用了.
当某些接口后面依赖了十几个甚至几十个不同的 sql, 层层嵌套非常复杂.
利用 APM 可以解决部分问题, 但是有部分场景无法覆盖.
思路
将 sql, xml, java 代码. 通过 ast 语法树工具 (spoon 或 druid 等), 转化为语法树对象.
然后导出和分析他们之间的依赖关系.
比如: sql 可以分解为如下格式.
java 分析:
- 代码解析
可以直接解析 java src 目录 或者 xxxx-sources.jar 源码包
SpoonClassParser parser = new SpoonClassParser();
List<SpoonFolder> resources = new ArrayList<>();
resources.add(new FileSystemFolder(new File("C:\\java\\work\\git_oristar\\tradingcenterplatform\\csm\\csm-controller\\src\\main\\java")));
resources.add(new FileSystemFolder(new File("C:\\java\\work\\git_oristar\\tradingcenterplatform\\csm\\csm-api\\src\\main\\java")));
resources.add(new FileSystemFolder(new File("C:\\java\\work\\git_oristar\\tradingcenterplatform\\csm\\csm-service\\src\\main\\java")));
resources.add(new FileSystemFolder(new File("C:\\java\\work\\git_oristar\\tradingcenterplatform\\csm\\csm-common\\src\\main\\java")));
resources.add(new FileSystemFolder(new File("C:\\java\\work\\git_oristar\\tradingcenterplatform\\csm\\csm-report-api\\src\\main\\java")));
resources.add(new ZipFolder(new File("C:\\Users\\linpengfei\\.m2\\repository\\com\\oristartech\\ori-common-mybatis\\4.0.0-RELEASE\\ori-common-mybatis-4.0.0-RELEASE-sources.jar")));
JavaCodeInfo javaCodeInfo =parser.loadJavaCode(resources).trimCalleeMap();
技术细节:
使用 spoon 解析 java 文件, 从中提取方法信息及过滤依赖关系.
使用 mybatis 的 DocumentBuilderFactory 解析 xml 文件, 导出方法名及其 sql 代码.
不同的项目需要不同的过滤策略.
- 直接输出调用关系到 excel
方法名 | 上级调用 | 下级引用 | url (controller才有) | sql (mybatis xml才有) | 表名 (从 sql 中提取) | urls (mybatis xml才有) |
---|---|---|---|---|---|---|
com.oristartech.service.impl.SellGoodsWaresApiServiceImpl#selectSellsGoodWaresTend(RestRequest) | [“com.oristartech.controller.sellgoods.SellGoodsWaresController#downloadSellGoodsSaleTend(ChartDataSearchParam,HttpServletResponse,HttpServletRequest)”,”com.oristartech.controller.sellgoods.SellGoodsWaresController#sellsGoodWaresTend(RestRequest,HttpServletRequest)”] | [“com.oristartech.facade.model.indicator.IndicatorSellGoodsWares#setDateKey(String)”,”com.oristartech.service.impl.SellGoodsWaresApiServiceImpl#getSellTrendList()”,”com.oristartech.facade.vo.sellgoods.ChartWaresDataCollection#setIndicatorSellGoodsWares(List)”,”com.oristartech.service.impl.sellgoods.SellGoodsWaresServiceImpl#selectWithTable(SellGoodsDataSearchParam)”,”com.oristartech.service.impl.sellgoods.SellGoodsWaresServiceImpl#selectWithTableTend(SellGoodsDataSearchParam)”,”map#get()”,”com.oristartech.service.impl.SellGoodsWaresApiServiceImpl#buildSellGoodTrendSearchByDateType(ChartDataSearchParam)”,”com.oristartech.facade.model.indicator.IndicatorSellGoodsWares#setDateKey()”] | /bigscreen/cardmember | select … from sch_plan_960732 where … | sch_plan_960732 | 当前sql被哪些接口调用 |
数据剪枝:
java 代码中部分调用关系没有分析价值, 在分析时会对其进行清理从而简化输出, 比如:
横向, 默认会减除 java 类库, spring 类库, apache 类库等等工具包, 以及对象 get set 方法 等内容,
纵向, 默认会将: “调用者>接口类>接口实现类” 三层关系简化为: “调用者>接口实现类” 两层关系
- 在线可视化
地址:
说明:
双击任意元素即可追踪该元素
- 接口 与 xml 关系
导出每个接口依赖了哪些 mybatis xml.
url | conroller | 注释 | sql1 | sql2 |
---|---|---|---|---|
/kanban/overview | com.oristartech.controller.kanban.KanbanController#overView(RestRequest,HttpServletRequest) | 实时大屏 | <pre>KanbanTicketsMapper.xml com.oristartech.dao.kanban. KanbanTicketsDao#selectSumInfoDay <select id=”selectSumInfoDay”> SELECT SUM(brbod.box_office) AS box_office,… </select></pre> |
<pre>KanbanSellGoodsMapper.xml com.oristartech.dao.kanban. KanbanSellGoodDao#selectSumInfoDay <select id=”selectSumInfoDay”> SELECT SUM(brsgd.sales_volume) AS sales_volume,… </select></pre> |
- xml 与 接口 关系
导出每个 mybatis xml 属于哪些接口.
xml | tables | url |
---|---|---|
<pre>KanbanTicketsMapper.xml com.oristartech.dao.kanban. KanbanTicketsDao#selectSumInfoDay <select id=”selectSumInfoDay”> SELECT SUM(brbod.box_office) AS box_office,.. </select></pre> |
[“bi_cinema_org_info”,”bi_real_box_office_data”] | /bigscreen/todayamount /bigscreen/regionalAnalysis /bigscreen/cardmember /bigscreen/ticketTop5 /bigscreen/getCinema /bigscreen/hotGoods /bigscreen/hourTrade /bigscreen/ticketchannel /bigscreen/ticketOrder |
sql 分析:
- sql 注释
测试地址(3.0业务):
https://biadmindev.oristarcloud.com/dw-api-core/sql_translate.html?db_catalog=cx3#p_comment_sql
测试地址(4.0业务) 如果表重名, 需要指定数据库信息:
https://biadmindev.oristarcloud.com/dw-api-core/sql_translate.html?db_source=cx_schema#p_comment_sql
输入:
select pla.UID_CINEMA AS uidCinema,
count(pla.PLAN_ID) as saleTimeNum,
sum(hal.SEATNUM - hal.BAD_SEAT_COUNT ) as saleSeatNum
from sch_plan_321566 pla
left join ci_hall hal on pla.UID_HALL =hal.UID
where pla.SALE_STATUS = 'SALE' and pla.UID_CINEMA = ?
and SHOW_TIME_START >= ?
and SHOW_TIME_START < ?
group by 1
添加注释:
select pla.UID_CINEMA /* 影院UID */ AS uidCinema,
count(pla.PLAN_ID /* 主键ID */) as saleTimeNum,
sum(hal.SEATNUM /* 座位数 */ - hal.BAD_SEAT_COUNT /* 坏座数 */ ) as saleSeatNum
from sch_plan_321566 /* 表:放映计划 */ pla
left join ci_hall /* 表:影厅表 */ hal on pla.UID_HALL /* 影厅UID */ =hal.UID /* */
where pla.SALE_STATUS /* 销售状态.SALE可售、NOT_SALE不可售、STOP暂停 */ = 'SALE' and pla.UID_CINEMA /* 影院UID */ = ?
and SHOW_TIME_START /* 放映开始时间YYYY-MM-DD */ >= ?
and SHOW_TIME_START < ?
group by 1
添加注释并替换表名:
select sch_plan_321566.UID_CINEMA /* 影院UID */ AS uidCinema,
count(sch_plan_321566.PLAN_ID /* 主键ID */) as saleTimeNum,
sum(ci_hall.SEATNUM /* 影厅座位数 */ - ci_hall.BAD_SEAT_COUNT ) as saleSeatNum
from sch_plan_321566 /* 表:放映计划 */ pla
left join ci_hall hal on sch_plan_321566.UID_HALL /* 影厅UID */ =hal.UID /* */
where sch_plan_321566.SALE_STATUS /* 销售状态.SALE可售、NOT_SALE不可售、STOP暂停 */ = 'SALE' and sch_plan_321566.UID_CINEMA /* 影院UID */ = ?
and SHOW_TIME_START /* 放映开始时间YYYY-MM-DD */ >= ?
and SHOW_TIME_START < ?
group by 1
技术细节:
提前加载表结构信息到数据库(表面,字段名,注释等), 解析sql语句提取字段名表名, 并为其添加注释.
- 处理参数列表
输入:
[2023-04-21 14:28:30] : [DEBUG] - SQL: select * from a in (?,?,?) Parameters: [null,2,3a]
输出(左侧输出):
select * from a in (NULL,2,'3a]');
- sql 可视化
支持解析 sql 和大部分 mybatis xml.
测试地址(3.0业务):
https://biadmindev.oristarcloud.com/dw-api-core/js/plugin/viz/viz_join.html?table_catalog=cx3
测试地址(4.0业务):
https://biadmindev.oristarcloud.com/dw-api-core/js/plugin/viz/viz_join.html?db_source=cx_schema
输入:
select pla.UID_CINEMA AS uidCinema,
count(pla.PLAN_ID) as saleTimeNum,
sum(hal.SEATNUM - hal.BAD_SEAT_COUNT ) as saleSeatNum
from sch_plan_321566 pla
left join ci_hall hal on pla.UID_HALL =hal.UID
where pla.SALE_STATUS = 'SALE' and pla.UID_CINEMA = ?
and SHOW_TIME_START >= ?
and SHOW_TIME_START < ?
group by 1
输出:
技术细节:
提前加载表结构信息到数据库(表面,字段名,注释等), 解析sql语句提取字段名表名 和 join 关系, 使用 viz 出图.
- 近似表
一个很常见的问题是, 因为分库分表的原因, 当前SQL的表后缀(比如: SCH_PLAN_123456)在预置表结构数据库中不存在.
在分析代码中加上 “#近似表” 即可自动适配前缀相同数字后缀近似的表名(比如: SCH_PLAN_999056).
#近似表
select pla.UID_CINEMA AS uidCinema,
count(pla.PLAN_ID) as saleTimeNum,
sum(hal.SEATNUM - hal.BAD_SEAT_COUNT ) as saleSeatNum
from sch_plan_123456 pla
left join ci_hall hal on pla.UID_HALL =hal.UID
where pla.SALE_STATUS = 'SALE' and pla.UID_CINEMA = ?
and SHOW_TIME_START >= ?
and SHOW_TIME_START < ?
group by 1
- 变量表
一个很常见的问题是, 因为分库分表的原因, mybatis 的 sql 中不显示完成表名, 而是使用了部分变量(比如: SCH_PLAN_${tenant_id})
在分析代码中加上 “#补全$表” 即可自动适配前缀相同数字后缀近似的表名(比如: SCH_PLAN_999056).
#补全$表
select pla.UID_CINEMA AS uidCinema,
count(pla.PLAN_ID) as saleTimeNum,
sum(hal.SEATNUM - hal.BAD_SEAT_COUNT ) as saleSeatNum
from sch_plan_${tenant_id} pla
left join ci_hall hal on pla.UID_HALL =hal.UID
where pla.SALE_STATUS = 'SALE' and pla.UID_CINEMA = ?
and SHOW_TIME_START >= ?
and SHOW_TIME_START < ?
group by 1