0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Map型設計 - いまさらながら Javaフレームワークを自作した(10年ぶり3回目) #07

0
Last updated at Posted at 2026-02-12

はじめに

今回は SIcore の Javaコード設計の中心にある 「Map型設計」 について解説します。

前回までの記事で、SIcore は通信を「JSON限定」にしていると紹介しました。ブラウザ → JSON → Java → JSON → ブラウザ という流れの中で、Java 側のデータの器として使うのが Mapベースの Io クラス です。JSON は連想配列(キーと値のペア)なので、Java 側も Map で受け取るのが最も自然な形になります。

SIcore では、この Io クラスでリクエスト・レスポンス・DB操作のすべてを行い、Entity(Bean)クラスを使用しません。この割り切りにより、コード量の削減・項目名の統一・AI との親和性を実現しています。

この記事で書くこと

  • Map型設計とは
  • 全部 Map で回す
  • 項目名統一の考え方
  • 型安全・null安全なデータ取得
  • バグ対策機能
  • ディープコピーによる安全性
  • メリット/デメリット

Map型設計とは

SIcore のサーバー側(Java)では、リクエスト・レスポンス・DB操作のすべてを Io クラス(Map を継承したクラス)で扱います。

public void doExecute(final Io io) throws Exception {
  // リクエストから値を取得(キーでアクセス)
  String userId = io.getString("user_id");
  BigDecimal incomeAm = io.getBigDecimal("income_am");

  // DB抽出結果をレスポンスとしてセット
  IoItems row = SqlUtil.selectOne(getDbConn(), sb);
  io.putAll(row);

  // ioオブジェクトがそのままレスポンスになる
}

Entity(Bean)クラスは作らない。テーブルや画面ごとに UserEntityOrderDto のようなクラスを用意する必要はありません。

全部 Map で回す

SIcore では、ブラウザからDBまでの全工程を Map(Io クラス)で回します。

[ブラウザ]  JSON  →  [Java] Io(Map)  →  [DB] SQL
                  ←  [Java] Io(Map)  ←

Webページのデータは「文字」である

Webページの入力値は、数値や日付に見えてもすべて文字列String)です。

<!-- ブラウザ上では数値や日付に見えるが... -->
<input type="text" name="income_am" value="1200000">
<input type="text" name="birth_dt" value="20250101">

これらは JavaScript → JSON → Java という経路を通る間、ずっと文字列です。型変換が必要になるのは、業務ロジックで使う瞬間だけです。

また、数値や日付の項目でも未入力(ブランク)がありえます。文字列で保管していれば未入力をそのまま保持でき、Bean のように intLocalDate で受けて変換エラーになる心配がありません。

Io クラスでは、内部的にすべて文字列(String)で保管し、必要に応じて型変換して取得するアプローチを採用しています。

// 文字列のまま取得
String incomeAmStr = io.getString("income_am"); // "1200000"

// 必要な時に型変換して取得
BigDecimal incomeAm = io.getBigDecimal("income_am"); // 1200000
LocalDate birthDt = io.getDateNullable("birth_dt");  // 2025-01-01

Bean を作ると何が増えるか

テーブルが 50、画面が 30 ある業務システムを想像してみてください。

  • テーブルごとの Entity クラス × 50
  • 画面ごとの Form / DTO クラス × 30(以上)
  • Entity ⇔ DTO の変換処理
  • キャメルケース変換(user_iduserId
  • getter / setter のリフレクション呼び出し

Map で全部回すなら、これらは すべて不要 です。

JavaScript と同じ感覚になる

JSON、JavaScript の連想配列、Java の Map はすべて「キーでアクセスする」という同じ構造です。

// JavaScript
const userId = req['user_id'];
// Java(Ioクラス)
String userId = io.getString("user_id");

フロントからバックまで、データの扱い方が統一されます。

項目名統一の考え方

DB項目物理名 = HTML name属性 = Java マップキー を統一します。

-- DB
CREATE TABLE t_user (
  user_id VARCHAR(10),
  user_nm VARCHAR(50),
  income_am NUMERIC(10),
  birth_dt DATE
);
<!-- HTML -->
<input name="user_id">
<input name="user_nm">
<input name="income_am">
<input name="birth_dt">
// Java
String userId = io.getString("user_id");
String userNm = io.getString("user_nm");

そのまま SQL になる

項目名が統一されているので、MapのデータをそのままSQLに使えます。

public void doExecute(final Io io) throws Exception {
  // io の中身(リクエストJSON がそのまま入っている)
  // {
  //   "user_id"   : "U001",
  //   "user_nm"   : "マイク・デイビス",
  //   "income_am" : "1200000",
  //   "birth_dt"  : "20250101"
  // }

  SqlUtil.insertOne(getDbConn(), "t_user", io);
  // ↑ キー名 = DB項目名なので、そのまま INSERT文になる

  // 実行される SQL:
  // INSERT INTO t_user (user_id, user_nm, income_am, birth_dt)
  //   VALUES ('U001', 'マイク・デイビス', 1200000, 2025-01-01)
  // ※SqlUtil が列の型をDBメタ情報から自動判定し、数値・日付等を適切な型でバインドする
}

DB取得からレスポンス表示も同様です。SELECT結果がそのまま画面表示に使えます。

public void doExecute(final Io io) throws Exception {
  // リクエストから抽出キーを取得
  SqlBuilder sb = new SqlBuilder();
  sb.addQuery("SELECT * FROM t_user WHERE user_id = ", io.getString("user_id")); // "user_id": "U001"
  // DB抽出(結果は IoItems = Map)
  IoItems row = SqlUtil.selectOne(getDbConn(), sb);
  // row の中身:
  // { "user_id": "U001", "user_nm": "マイク・デイビス", "income_am": "1200000", "birth_dt": "20250101" }

  // レスポンスにセット → そのまま JSON としてブラウザへ返る
  io.putAll(row);
  // ↑ キー名 = HTML name属性なので、画面の各項目に自動セットされる
}

項目名統一のメリット:

  • 変換コード不要: キャメルケース変換(user_iduserId)が不要
  • コード量削減: マッピング処理を書く必要がない
  • バグ削減: 変換ミスがなくなる
  • 保守性向上: DB設計書がそのまま仕様書として機能する

型安全・null安全なデータ取得

「Bean を使わないと型安全ではないのでは?」という懸念があると思います。

Io クラスは、型安全・null安全な getメソッドを提供することで、これに対応しています。

null安全

一般的な Map では get()null を返し、NullPointerException の原因になります。

// 一般的なMap
Map<String, String> map = new HashMap<>();
String value = map.get("key"); // null → NullPointerException の原因

Io クラスでは、基本メソッドは null を返しません。null を取得したい場合は明示的に Nullable メソッドを使います。

// Ioクラス
String value = io.getString("key");         // "" (nullではなくブランク)
String value = io.getStringNullable("key"); // null(明示的にnull取得)

型安全

型変換メソッドが用意されており、型変換エラー時はキーと値をログ出力します。

int age = io.getInt("age");                       // ブランクはゼロへ変換
BigDecimal income = io.getBigDecimal("income_am"); // 精度を保つ
LocalDate birthDt = io.getDateNullable("birth_dt"); // 日付形式チェック

型変換エラーが起きた場合も、発生箇所は必ず Io クラスの get メソッドに集約されます。エラーログにキーと値が出力されるため、AI に修正指示を出す際も「どのキーの値が不正か」が明確で、バリデートなどの対処が容易です。

バグ対策機能

Io クラスは、一般的な Map で発生しやすいバグを防止する機能を持っています。

キー重複の厳密チェック

意図しない値の上書きを検出します。

// 一般的なMap
map.put("user_id", "U001");
map.put("user_id", "U002"); // 上書きされる(警告なし)

// Ioクラス
io.put("user_id", "U001");
io.put("user_id", "U002");      // エラー(キーをログ出力)
io.putForce("user_id", "U002"); // 上書きする場合は意図的に

存在しないキーでの取得エラー

タイプミスによる間違いを検出します。

// 一般的なMap
map.put("user_id", "U001");
String value = map.get("userid"); // null(タイプミスに気付かない)

// Ioクラス
io.put("user_id", "U001");
io.getString("userid"); // エラー(存在しないキーをログ出力)

これらのチェックにより、Map でありながら 「宣言済み変数」のような安全性 が得られます。

ディープコピーによる安全性

Io クラスは、リスト・ネストマップの格納・取得時にディープコピーを行います。

// 格納時のディープコピー
List<String> srcList = new ArrayList<>(Arrays.asList("A", "B"));
io.putList("items", srcList);
srcList.add("C");  // 元リストを変更
// io.getList("items") は ["A", "B"] のまま(影響なし)

// 取得時のディープコピー
List<String> gotList = io.getList("items");
gotList.add("D");  // 取得リストを変更
// io.getList("items") は ["A", "B"] のまま(影響なし)

参照共有による予期しない副作用を防止しています。

メリット

  • Entity / DTO / Form クラスが不要:コード量が大幅に削減される
  • 項目名統一で変換コード不要:HTML → Java → SQL がシームレスに繋がる
  • JavaScript と同じ感覚:フロントとバックでキーアクセスの統一感
  • バグ対策が組み込み済み:null安全・型安全・キー重複チェック・存在チェック
  • ディープコピーで安全:参照共有による副作用を防止
  • AI がコードを生成しやすい:パターンが単純で一貫性がある

デメリット

  • IDE のコード補完が効かない:Bean ならフィールド名が補完されるが、Map のキー文字列は補完されない
    • ただし、バグ対策機能(存在しないキーでエラー)でカバーできる
    • また、AI がキー名を含むコードを生成するのでカバーできる
  • コンパイル時の型チェックがない:Bean ならコンパイル時にフィールド型が保証されるが、Map では実行時チェックになる
    • ただし、Io クラスの型変換メソッドが実行時に安全に変換する
    • バリデーション処理で事前にチェックすることで補う

おわりに

「Bean を使わない」と聞くと不安に思う方もいるかもしれません。

しかし、Entity / DTO / Form という Bean クラスの層がなくなる ことで、コード量が減り、項目名の不一致によるバグもなくなり、新しい画面やテーブルを追加する際の作業量が大幅に軽減されます。

Io クラスのバグ対策機能(null安全・型安全・キー重複チェック・存在チェック)を組み合わせれば、Map の弱点は実用上ほぼカバーできると考えています。

関連記事リンク

他の記事もぜひご覧ください!

SIcoreフレームワーク リンク

実装コードと資料はすべてこちらで公開しています。


読んでいただきありがとうございました!
❤いいね!をしていただけると励みになります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?