大家好,我是猿码叔叔。目前在北京从事 Java 软件开发。
今天为大家带来的内容是如何用 Java 语言统计基于 Mybatis 框架的项目中都用到了哪些表,以及哪些表被注释掉的相关信息。业务本身乏善可陈,但如何结合自己的开发经验与思路来实现却充满挑战与趣味性。本次统计的文件主要是 .xml 扩展类型的文件,且需要我们对 MySQL 和 Oracle 数据库语言有一定的基础。
一、寻找规律
-
关键字
from、update、join、into
上面的 4 个关键字如果语法没有问题,肯定会出现在表名的前方。因此我们在扫描 xml 文件时,以这四个关键字为目标,然后根据情况截取紧跟其后的字符串就可以拿到对应的表。
-
注意事项
1、换行
在对 SQL 脚本进行排版时,有时会出现 from 关键字与对应表名不在一行的情况。这时候如果我们是一行一行的读取 xml 文件,就需要考虑这种情况的存在。对应的解决办法也很简单,我们可以一次性将这个 xml 文件读出来,然后再将其作为一个完整字符串来处理。
2、子句中多个表查询
SELECT * FROM TABLE_A A, TABLE_B B WHERE A.ORDER_ID = B.ORDER_ID;
上面这个脚本中TABLE_A 确实出现在了 FROM 关键字之后,但 TABLE_B 却不是,这种写法在项目中区别于左右关联查询,为了使得统计结果更准确,应当考虑到且加以处理。
3、如何处理 2 的情况
对于 FROM 关键字之后的字符串,',' 号可以作为多张表的分隔标准,紧跟其后的第一个完整单词就是表名。
4、如何处理字段名中有 'update'、'from'、'join' 和 'into' 关键字的情况
很显然,字段名称是不能以关键字来命名的,但可以用其他单词与关键字组合命名。为了区分这一点,我们可以避开关键字开始和结束时不以空格字符出现的情况。
二、性能优化
根据第一条的规律和思路,基本上就可以写一个简单程序,来统计出目标文件内的所有表名。但是我们必须要读取整个 xml 文件的内容吗?不是必须的。
- XML 文件的特点
xml 文件里的内容除了脚本以外就是标签。即便是注释也是标签的一种。这些标签都是以 '<>' 组成的。因此在读取内容时,根据“左右括号”原理创建一个计数变量,遇到 “<” 进行累计计数,遇到“>”进行减数,当变量值为 0 时才进行SQL脚本的扫描与截取。
- 注释
xml 文件里的注释形式是这样的: “<!---->”。他也是一个标签,区别于 “<!DOCTYPE>” 。注释内容不可忽略的一个原因是其可能存在SQL脚本内容。如果我们希望对统计结果做特殊处理,对于注释的内容中的表名可以予以标记。
三、代码实现
package utils.java;import java.io.*;
import java.util.*;public class FindAllTables {private static String URL, TARGET;private static SortedMap<String, Boolean> TABLES;public static void main(String[] args) {String source = "D:\\WORK\\xxxx\\dao\\dev11";FindAllTables.URL = source;FindAllTables.TARGET = "D:\\WORK\\xxxx\\task_doc\\2025\\collection\\0114\\DEV11\\";FindAllTables.findAllFileName();FindAllTables.writeData();}/*** 将统计结果写入到本地文件*/public static void writeData() {File f = new File(TARGET);if (!f.exists()) {f.mkdirs();}TARGET = TARGET.concat("ALL_TABLE_11.txt");try (BufferedWriter bw = new BufferedWriter(new FileWriter(TARGET))) {for (Map.Entry<String, Boolean> ent : TABLES.entrySet()) {bw.append(ent.getKey()).append(ent.getValue() ? " <被注释>" : "").append("\n");}bw.flush();// 写完文件后,不用手动打开Desktop.getDesktop().open(new File(TARGET));} catch (Exception e) {System.out.println(e.getMessage());}}public static void findAllFileName() {TABLES = new TreeMap<>();readFile();}private static void readFile() {File file = new File(URL);File[] files = file.listFiles();for (File f : files) {if (f.getName().endsWith(".xml")) {analysisData(f);}}}/*** 一次性读取整个文件内容,然后做进一步处理* * @param f 文件*/private static void analysisData(File f) {try (BufferedReader bw = new BufferedReader(new FileReader(f))) {String line;StringBuilder sb = new StringBuilder();while ((line = bw.readLine()) != null) {if (line.isEmpty()) continue;line = line.trim();sb.append(line).append(" ");}findAllTables(sb.toString());} catch (Exception e) {e.fillInStackTrace();}}/*** 对内容做分词处理* * @param data xml 文件内瓤*/private static void findAllTables(String data) {char[] cs = data.toCharArray();int n = cs.length, i = 0;int docCnt = 0, tagCnt = 0, mask = 1049376; // mask 为 i、j、u、f 的左移后的压缩数字while (i < n) {if (cs[i] == '<') {if (cs[i + 1] == '!' && cs[i + 2] == '-') {++docCnt;} else {++tagCnt;}} else if (cs[i] == '>') {if (cs[i - 1] == '-') {--docCnt;} else {--tagCnt;}}if (tagCnt > 0 || ((1 << Character.toLowerCase(cs[i]) - 'a') & mask) == 0 || cs[i - 1] != ' ') {++i;continue;}int x = matchKeyword(cs, i);if (x == 0) { // fromi += 4;if (cs[i] != ' ') {continue;}char c = ',';while (c == ',') {i += 1;i = delWhiteSpace(cs, i);i = appendTableName(cs, i, docCnt > 0);i = delWhiteSpace(cs, i);i = delOneWord(cs, i);i = delWhiteSpace(cs, i);c = cs[i];}} else if (x <= 3) { // 其他关键字匹配处理i += KEY_WORDS[x].length;if (cs[i] != ' ') {continue;}i = delWhiteSpace(cs, i);i = appendTableName(cs, i, docCnt > 0);} else { // 非关键字++i;}}}private static int delOneWord(char[] cs, int x) {while (x < cs.length && cs[x] != ' ') {++x;}return x;}/*** 截取出表名* * @param cs 将 xml 内容转成 char 数组处理* @param x 表名开始的索引* @param documented 是否是注释内容* @return 返回拼接结束后的索引*/private static int appendTableName(char[] cs, int x, boolean documented) {StringBuilder sb = new StringBuilder();boolean notValid = false;while (!notValid && x < cs.length && cs[x] != ' ' && cs[x] != ')') {notValid = cs[x] == '(' || cs[x] == '#' || cs[x] == '=';sb.append(cs[x++]);}if (!notValid) {String key = sb.toString().toUpperCase();if (!"DUAL".equals(key) && (!TABLES.containsKey(key) || TABLES.get(key))) {TABLES.put(key, documented);}}return x;}private static int delWhiteSpace(char[] cs, int x) {while (x < cs.length && cs[x] == ' ') {++x;}return x;}private static int matchKeyword(char[] cs, int x) {// _updatefor (int i = 0; i < 4; ++i) {if (x + KEY_WORDS[i].length >= cs.length) continue;int tx = x, j = 0, n = KEY_WORDS[i].length;boolean isMatch = true;while (j < n && isMatch) {isMatch = Character.toLowerCase(cs[tx++]) == KEY_WORDS[i][j++];}if (isMatch && cs[tx] == ' ') return i;}return x;}private static final char[][] KEY_WORDS = {{'f', 'r', 'o', 'm'},{'j', 'o', 'i', 'n'},{'i', 'n', 't', 'o'},{'u', 'p', 'd', 'a', 't', 'e'},};
}
四、结语
写博客不易,期待您的支持。如果您需要对负责的项目甚至其他类型的文本内容进行相关信息的统计,可以参考这篇博客或使用AI来帮助您。有疑问的伙伴也可以私信我。