サンプルプロジェクトの構成
以下の3クラスからなるシンプルなECサイト風プロジェクトをサンプルとして使います。 DBアクセスは Spring JdbcTemplate 風の書き方ですが、フレームワークなしのプレーンJDBCでも同様に動作します。
src/main/java/com/example/
├── repository/
│ ├── UserRepository.java
│ └── OrderRepository.java
└── service/
└── ReportService.java
サンプルリポジトリクラス
package com.example.repository;
public class UserRepository {
private static final String FIND_ALL = "SELECT id, name, email FROM users";
private static final String FIND_BY_ID =
"SELECT id, name, email FROM users WHERE id = ?";
private static final String INSERT_USER =
"INSERT INTO users (name, email) VALUES (?, ?)";
private static final String UPDATE_USER =
"UPDATE users SET name = ?, email = ? WHERE id = ?";
public void deactivate(long id) {
// ローカル変数でのSQL
String sql = "UPDATE users SET active = 0 WHERE id = ?";
// jdbcTemplate.update(sql, id);
}
}
package com.example.repository;
public class OrderRepository {
public void createOrder(long userId, int total) {
String sql = "INSERT INTO orders (user_id, total, status) VALUES (?, ?, 'NEW')";
// jdbcTemplate.update(sql, userId, total);
}
public void findOrdersWithItems(long userId) {
String sql =
"SELECT o.id, o.total, i.product_id, i.qty " +
"FROM orders o " +
"JOIN order_items i ON o.id = i.order_id " +
"WHERE o.user_id = ?";
// jdbcTemplate.query(sql, ...);
}
public void addItem(long orderId, long productId, int qty) {
String sql = "INSERT INTO order_items (order_id, product_id, qty) VALUES (?, ?, ?)";
// jdbcTemplate.update(sql, orderId, productId, qty);
}
public void updateStatus(long orderId, String status) {
String sql = "UPDATE orders SET status = ? WHERE id = ?";
// jdbcTemplate.update(sql, status, orderId);
}
public void deleteItems(long orderId) {
String sql = "DELETE FROM order_items WHERE order_id = ?";
// jdbcTemplate.update(sql, orderId);
}
}
package com.example.service;
public class ReportService {
public void generateSalesReport() {
String sql =
"SELECT u.name, SUM(o.total) AS total_sales " +
"FROM users u " +
"JOIN orders o ON u.id = o.user_id " +
"GROUP BY u.id, u.name " +
"ORDER BY total_sales DESC";
// jdbcTemplate.query(sql, ...);
}
public void getItemizedReport(long userId) {
String sql =
"SELECT u.name, o.id, i.product_id, i.qty " +
"FROM users u " +
"JOIN orders o ON u.id = o.user_id " +
"JOIN order_items i ON o.id = i.order_id " +
"WHERE u.id = ?";
// jdbcTemplate.query(sql, ...);
}
}
ツールを実行する
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) throws Exception {
Path srcRoot = Path.of("src/main/java");
CrudMatrixBuilder builder = new CrudMatrixBuilder();
CrudMatrixAggregator agg = builder.build(srcRoot);
CrudRenderer renderer = new CrudRenderer();
String md = renderer.toMarkdown(agg);
String mmd = renderer.toMermaid(agg);
String puml = renderer.toPlantUml(agg);
Files.writeString(Path.of("crud-matrix.md"), md, StandardCharsets.UTF_8);
Files.writeString(Path.of("crud-diagram.mmd"), mmd, StandardCharsets.UTF_8);
Files.writeString(Path.of("crud-diagram.puml"), puml, StandardCharsets.UTF_8);
System.out.println("=== CRUD Matrix ===");
System.out.println(md);
}
}
Markdown出力結果
crud-matrix.md
| クラス | order_items | orders | users | |---------------------|:-----------:|:------:|:-----:| | OrderRepository | CR D | CRU | | | ReportService | R | R | R | | UserRepository | | | CRU |
Mermaid出力結果
crud-diagram.mmd(```mermaid ブロックに貼り付ける)
flowchart LR OrderRepository["OrderRepository"] order_items[(order_items)] OrderRepository --CRD--> order_items orders[(orders)] OrderRepository --CRU--> orders ReportService["ReportService"] ReportService --R--> order_items ReportService --R--> orders users[(users)] ReportService --R--> users UserRepository["UserRepository"] UserRepository --CRU--> users
PlantUML出力結果
crud-diagram.puml
@startuml
left to right direction
database "order_items" as order_items
database "orders" as orders
database "users" as users
rectangle "OrderRepository" as OrderRepository {
}
OrderRepository --> order_items : CRD
OrderRepository --> orders : CRU
rectangle "ReportService" as ReportService {
}
ReportService --> order_items : R
ReportService --> orders : R
ReportService --> users : R
rectangle "UserRepository" as UserRepository {
}
UserRepository --> users : CRU
@enduml
出力の検証ポイント
出力結果を見て確認すべきポイントを整理します。
- JOIN先テーブルが R になっているか —
ReportServiceは SELECT のみなのですべて R のはず - DELETE が正しく D になっているか —
OrderRepository.deleteItems()はorder_itemsへの D - 定数フィールドが検出されているか —
UserRepositoryのstatic finalSQLが漏れていないか - ローカル変数のSQLが検出されているか —
deactivate()のUPDATEが U として登録されているか
💡 動的SQLが含まれる場合
StringBuilderで組み立てるSQLや、条件によってテーブル名が変わるコードは今回の静的解析では検出できません。PART 08 でこの限界と対処法を詳しく解説します。
✅ 次の章では…
PART 08(最終回)では静的解析の限界・動的SQLへの対処・Gradle/Mavenプラグイン化の方向性・CI/CD組み込みのヒントをまとめます。