サンプルプロジェクトの構成

以下の3クラスからなるシンプルなECサイト風プロジェクトをサンプルとして使います。 DBアクセスは Spring JdbcTemplate 風の書き方ですが、フレームワークなしのプレーンJDBCでも同様に動作します。

Shell — プロジェクト構造
src/main/java/com/example/
├── repository/
│   ├── UserRepository.java
│   └── OrderRepository.java
└── service/
    └── ReportService.java

サンプルリポジトリクラス

Java — UserRepository.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);
    }
}
Java — OrderRepository.java
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);
    }
}
Java — ReportService.java
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, ...);
    }
}

ツールを実行する

Java — メインエントリーポイント
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
  • 定数フィールドが検出されているかUserRepositorystatic final SQLが漏れていないか
  • ローカル変数のSQLが検出されているかdeactivate()UPDATE が U として登録されているか

💡 動的SQLが含まれる場合

StringBuilderで組み立てるSQLや、条件によってテーブル名が変わるコードは今回の静的解析では検出できません。PART 08 でこの限界と対処法を詳しく解説します。

次の章では…

PART 08(最終回)では静的解析の限界・動的SQLへの対処・Gradle/Mavenプラグイン化の方向性・CI/CD組み込みのヒントをまとめます。

→ PART 08 — まとめへ