JSqlParserの基本
JSqlParser の CCJSqlParserUtil.parse() はSQL文字列を受け取り、
Statement(文のAST)を返します。
返ってくる型を instanceof で分岐して SELECT/INSERT/UPDATE/DELETE を判別します。
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.delete.Delete;
String sql = "SELECT id, name FROM users WHERE active = 1";
Statement stmt = CCJSqlParserUtil.parse(sql);
if (stmt instanceof Select) System.out.println("READ");
if (stmt instanceof Insert) System.out.println("CREATE");
if (stmt instanceof Update) System.out.println("UPDATE");
if (stmt instanceof Delete) System.out.println("DELETE")
操作種別の判定
| CRUD | JSqlParser型 | SQL例 |
|---|---|---|
| Create | Insert | INSERT INTO orders ... |
| Read | Select | SELECT * FROM orders ... |
| Update | Update | UPDATE orders SET ... |
| Delete | Delete | DELETE FROM orders ... |
テーブル名の取得
各 Statement 型からテーブル名を取得する方法は型によって異なります。
TablesNamesFinder ユーティリティを使うと型を意識せず全テーブル名を取得できます。
import net.sf.jsqlparser.util.TablesNamesFinder;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import java.util.List;
public class TableExtractor {
public List<String> getTableNames(String sql) throws Exception {
Statement stmt = CCJSqlParserUtil.parse(sql);
TablesNamesFinder finder = new TablesNamesFinder();
return finder.getTableList(stmt);
}
}
// 使用例
TableExtractor te = new TableExtractor();
// JOINを含むSQLでも全テーブルを返す
List<String> tables = te.getTableNames(
"SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id"
);
// → ["users", "orders"]
// サブクエリのテーブルも含む
List<String> tables2 = te.getTableNames(
"SELECT * FROM orders WHERE user_id IN (SELECT id FROM users WHERE active=1)"
);
// → ["orders", "users"]
SQL: SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id テーブル: [users, orders] SQL: DELETE FROM order_items WHERE order_id = ? テーブル: [order_items]
JOIN・サブクエリへの対応
TablesNamesFinder はサブクエリ・CTE・UNION内のテーブルも再帰的に収集します。
ただし操作種別の割り当てには注意が必要です。
JOIN 先のテーブルは SELECT で参照されているため R ですが、
UPDATE ... SET ... FROM join_table のように JOIN を伴う UPDATE の場合、
主テーブルは U、結合テーブルは R と分ける必要があります。
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.*;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.util.TablesNamesFinder;
import java.util.*;
public class CrudClassifier {
public enum CrudOp { C, R, U, D }
/**
* SQL文字列から テーブル名 → 操作種別セット のマップを返す
*/
public Map<String, Set<CrudOp>> classify(String sql) throws Exception {
Statement stmt = CCJSqlParserUtil.parse(sql);
Map<String, Set<CrudOp>> result = new LinkedHashMap<>();
if (stmt instanceof Select) {
// SELECTは全テーブルをR扱い
allTables(stmt).forEach(t -> add(result, t, CrudOp.R));
} else if (stmt instanceof Insert ins) {
// 主テーブルはC
add(result, ins.getTable().getName(), CrudOp.C);
// SELECT部分(INSERT INTO ... SELECT)はR
if (ins.getSelect() != null) {
new TablesNamesFinder()
.getTableList(ins.getSelect())
.forEach(t -> add(result, t, CrudOp.R));
}
} else if (stmt instanceof Update upd) {
// 主テーブルはU
add(result, upd.getTable().getName(), CrudOp.U);
// FROM句のJOINテーブルはR
if (upd.getFromItem() != null) {
new TablesNamesFinder()
.getTableList(upd.getFromItem())
.forEach(t -> add(result, t, CrudOp.R));
}
} else if (stmt instanceof Delete del) {
// 主テーブルはD
add(result, del.getTable().getName(), CrudOp.D);
// JOINはR
if (del.getJoins() != null) {
del.getJoins().forEach(j ->
add(result, j.getRightItem().toString(), CrudOp.R));
}
}
return result;
}
private List<String> allTables(Statement stmt) throws Exception {
return new TablesNamesFinder().getTableList(stmt);
}
private void add(Map<String, Set<CrudOp>> map, String table, CrudOp op) {
map.computeIfAbsent(table.toLowerCase(), k -> new LinkedHashSet<>()).add(op);
}
}
CRUD結果データクラス
解析結果を保持するシンプルなデータクラスを定義します。
import java.util.*;
/** 1件分のCRUD情報 */
public record CrudEntry(
String className, // 発見元クラス名
String tableName, // テーブル名
Set<CrudClassifier.CrudOp> operations // C/R/U/D の集合
) {}
// 使用例
CrudClassifier classifier = new CrudClassifier();
Map<String, Set<CrudClassifier.CrudOp>> result = classifier.classify(
"SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id"
);
result.forEach((table, ops) -> {
System.out.println(table + " → " + ops);
});
// users → [R]
// orders → [R]
パースエラーのハンドリング
JSqlParser が対応していない方言や、SQL以外の文字列が混入した場合にパース例外が発生します。 これらは静かに読み飛ばし、ログに記録するのが実用的です。
public Optional<Map<String, Set<CrudClassifier.CrudOp>>> safeClassify(
String sql, String sourceClass) {
try {
return Optional.of(new CrudClassifier().classify(sql));
} catch (Exception e) {
// SQL以外の文字列やJSqlParser非対応構文は読み飛ばす
System.err.printf("[SKIP] %s — %s%n", sourceClass, e.getMessage());
return Optional.empty();
}
}