はじめに
今回は 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)クラスは作らない。テーブルや画面ごとに UserEntity や OrderDto のようなクラスを用意する必要はありません。
全部 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 のように int や LocalDate で受けて変換エラーになる心配がありません。
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_id→userId) - 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_id→userId)が不要 - コード量削減: マッピング処理を書く必要がない
- バグ削減: 変換ミスがなくなる
- 保守性向上: 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 の弱点は実用上ほぼカバーできると考えています。
関連記事リンク
他の記事もぜひご覧ください!
- 01 Javaフレームワークを自作した動機
- 02 直結型URLマッピング
- 03 JSON限定
- 04 モックアップ=実装コード
- 05 動的リスト表示
- 06 独自HTML属性
- 07 Map型設計(本記事)
- 08 1ファイルCSS設計
- 09 クライアント側データ管理とJWT認証
- 10 Java直書きSQL
- 11 バニラで作る理由
SIcoreフレームワーク リンク
実装コードと資料はすべてこちらで公開しています。
- HP: https://onepg.com/ja/
- GitHub: https://github.com/sugaiketadao/sicore-ja
- サンプル画面の確認方法: https://github.com/sugaiketadao/sicore-ja#%EF%B8%8F-%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E7%94%BB%E9%9D%A2%E3%81%AE%E7%A2%BA%E8%AA%8D%E6%96%B9%E6%B3%95---vs-code
- AI開発の始め方: https://github.com/sugaiketadao/sicore-ja#-ai%E9%96%8B%E7%99%BA%E3%81%AE%E5%A7%8B%E3%82%81%E6%96%B9
読んでいただきありがとうございました!
❤いいね!をしていただけると励みになります。