Webアプリに入力項目を追加するには?画面・Java・DBの変更箇所を解説

最終更新日

Schoo Java入門 中級 第10回 Webアプリケーション実装演習

「登録画面に学習場所を追加して、あとから一覧でも見られるようにしたい」。入力欄を1つ増やすだけに見えても、画面だけ直したのでは、その値はDBに残りません。保存できても、取得する処理を直していなければ一覧に表示できません。

項目を追加するときは、入力した値を保存する道筋と、保存した値を表示へ戻す道筋の両方をつなぎます。この記事では「学習場所」を例に、変更するファイルとコード、過去のデータの扱い、確認するテストケースまでを説明します。

本記事は、Schoo「Java入門 中級」第10回「Webアプリケーション実装演習」に対応するステップアップ学習記事です。未受講の方にも分かるように、例で使う仕様と各クラスの役割を示します。授業の配布ファイルは前提にしていません。

今回の変更:学習場所を保存して、一覧でも表示する

変更前のアプリは、ログインした利用者が学習日・カテゴリ・学習内容・学習時間・メモを登録し、自分の記録を一覧で見るものです。ここに学習場所を追加します。まず、入力欄の形より先に次の仕様を決めます。

決めること今回の仕様
入力方法「未指定・自宅・学校・その他」から選ぶ。任意項目とし、未指定のままでも登録できる。
保存する値自宅・学校・その他はその文字列を保存する。未指定は空文字(長さ0の文字列)を保存する。
想定外の入力選択肢にない文字列がサーバーに届いた場合は、エラーで通知して保存しない。
過去の記録学習場所は分からないため、空文字として扱い、一覧では「未指定」と表示する。過去の記録を「自宅」などと決め付けない。
変えないこと既存の入力条件・ログイン確認・利用者ごとの記録の分離は維持する。今回は登録と一覧が対象。編集など別の機能があれば、その仕様も確認する。

「任意」という言葉だけでは、未入力時に空文字を保存するのか、nullにするのか、登録を止めるのかが決まりません。未指定の保存方法と表示方法まで決めると、Java・DB・画面で扱いが食い違うのを防げます。

図解:入力した学習場所が、保存されて一覧に戻るまで

登録画面で選んだ自宅をServletが受け取り、Modelが選択肢を確認し、StudyLogに入れてDAOからDBへ保存する。一覧ではDAOが取得してDTOを作り、JSPが自宅を表示する。過去の空文字は未指定と表示する。
図は項目の対応を示す抜粋です。DB欄の「空文字」は説明用の表記で、その文字列を保存する意味ではありません。実際の一覧の並び順はSQLで指定します。

Servletはリクエストから値を受け取り、Modelは登録してよい値かを判断します。DTO(ここではStudyLog)は値をひとまとまりにして渡すクラス、DAOはSQLを使って保存・取得するクラスです。JSPは取得した値を一覧のHTMLとして表示します。

DTOにフィールドを追加しただけでは、DBの列は増えません。DBに列を追加しただけでも、フォームの値が自動的に保存されるわけではありません。それぞれの受け渡しをコードでつなぐ必要があります。

1.画面・Java・DBで使う名前を決める

場所名前と意味
画面の表示名学習場所。利用者に見せる言葉。
フォームのnamestudyPlace。ブラウザがパラメータを送信するときの名前。
Javaの変数・DTOstudyPlace。getStudyPlace()で値を取り出す。
DBの列study_place。SQLで指定する保存先の列名。

フォームのname="studyPlace"と、ServletのgetParameter("studyPlace")は同じ名前にします。DBの列名はstudy_placeと書きますが、Javaが自動変換するわけではありません。DAOのSQLやrs.getString("study_place")で明示します。

次のコードは、同じアプリの変更箇所をファイルごとに抜き出したものです。DTO以外はクラス全体ではありません。既存のログイン処理・例外処理・DB接続処理などを省略しており、断片だけでWebアプリ全体が起動するものではありません。

2.フォームに入力欄を追加し、Servletで受け取る

study-log-form.jspの登録用フォーム内に追加します。既存フォームはPOSTで登録用Servletへ送信するものとします。

<label for="studyPlace">学習場所</label>
<select id="studyPlace" name="studyPlace">
    <option value="">未指定</option>
    <option value="自宅">自宅</option>
    <option value="学校">学校</option>
    <option value="その他">その他</option>
</select>

選択肢の表示が「未指定」でも、送信される値はvalue=""の空文字です。idはラベルとの関連付けに使い、サーバーへの送信名になるのはnameです。

StudyLogRegisterServlet.javaのdoPost内です。ログイン済みのloginUserと、DB接続先を設定したmodelがある位置で、既存の取得・登録処理にstudyPlaceを追加します。文字コードの指定は、最初のパラメータ取得より前に置きます。

request.setCharacterEncoding("UTF-8");

String studyDate = request.getParameter("studyDate");
String category = request.getParameter("category");
String content = request.getParameter("content");
String studyMinutesText = request.getParameter("studyMinutes");
String memo = request.getParameter("memo");
String studyPlace = request.getParameter("studyPlace");

model.registerStudyLog(
    loginUser, studyDate, category, content,
    studyMinutesText, memo, studyPlace
);

getParameterで受け取る値はStringです。その名前のパラメータ自体がなければnullになります。今回は任意項目なので、Modelでnullも未指定として扱います。メソッドの呼び出しだけでなく、次に示すModel側の引数にも追加が必要です。既存のtry/catchによるエラー表示は維持します。

3.DTOに、学習場所を持つフィールドを追加する

StudyLog.javaの例です。既存の7項目にstudyPlaceを追加し、コンストラクタの最後で受け取ります。以下はpackage宣言を除くクラス本体です。既存プロジェクトでは元のpackage宣言を残します。

public class StudyLog {
    private int id;
    private int userId;
    private String studyDate;
    private String category;
    private String content;
    private int studyMinutes;
    private String memo;
    private String studyPlace;

    public StudyLog(
            int id, int userId, String studyDate,
            String category, String content,
            int studyMinutes, String memo,
            String studyPlace) {
        this.id = id;
        this.userId = userId;
        this.studyDate = studyDate;
        this.category = category;
        this.content = content;
        this.studyMinutes = studyMinutes;
        this.memo = memo;
        this.studyPlace = studyPlace;
    }

    public int getId() { return id; }
    public int getUserId() { return userId; }
    public String getStudyDate() { return studyDate; }
    public String getCategory() { return category; }
    public String getContent() { return content; }
    public int getStudyMinutes() { return studyMinutes; }
    public String getMemo() { return memo; }
    public String getStudyPlace() { return studyPlace; }
}

this.studyPlace = studyPlace;で、渡された値をその記録のフィールドに保存します。getStudyPlace()は、DAOやJSPがその値を取り出すためのメソッドです。ここではDTO自身がSQLを実行したり、画面を作ったりはしません。

コンストラクタの引数が7個から8個に変わるので、プロジェクト全体でnew StudyLogを検索します。登録用のModelだけでなく、検索結果からDTOを作るDAOや、テストデータを作るコードも変更対象です。コンパイルエラーを消すために全部へ空文字を渡すと、入力した学習場所が途中で失われます。

4.Modelで値を確認してから、DTOへ入れる

ブラウザの選択欄は、サーバーが受け取る値を保証するものではありません。StudyLogModel.javaに次のメソッドを追加し、未指定は空文字へそろえ、許可した3種類以外は拒否します。

private String checkStudyPlace(String studyPlace) {
    if (studyPlace == null || studyPlace.isEmpty()) {
        return "";
    }
    if ("自宅".equals(studyPlace)
            || "学校".equals(studyPlace)
            || "その他".equals(studyPlace)) {
        return studyPlace;
    }
    throw new IllegalArgumentException(
        "学習場所を選び直してください。"
    );
}

次は同じクラスの登録メソッドです。validateRequiredは既存項目の必須チェック、convertStudyMinutesは時間の整数変換と範囲チェック、fixMemoは未入力メモを扱う既存メソッドです。今回はそれらの条件を変えず、checkStudyPlaceによる確認を加えます。

public int registerStudyLog(
        LoginUser loginUser, String studyDate,
        String category, String content,
        String studyMinutesText, String memo,
        String studyPlace) throws SQLException {
    if (loginUser == null) {
        throw new IllegalArgumentException(
            "ログインしてください。"
        );
    }
    validateRequired(studyDate, "学習日を入力してください。");
    validateRequired(category, "カテゴリを入力してください。");
    validateRequired(content, "学習内容を入力してください。");
    validateRequired(studyMinutesText, "学習時間を入力してください。");

    int studyMinutes = convertStudyMinutes(studyMinutesText);
    String fixedMemo = fixMemo(memo);
    String fixedStudyPlace = checkStudyPlace(studyPlace);

    StudyLog studyLog = new StudyLog(
        0, loginUser.getId(), studyDate,
        category, content, studyMinutes,
        fixedMemo, fixedStudyPlace
    );
    StudyLogDao dao = new StudyLogDao(dbPath);
    return dao.insert(studyLog);
}

確認済みのfixedStudyPlaceを、DTOの最後の引数へ渡しています。選択肢にない値ならcheckStudyPlaceで例外になり、その下にあるdao.insertには進みません。ログインした利用者のIDはloginUserから取り、フォームから任意の利用者IDを受け取る形に変えないでください。

5.既存データを残したまま、DBに列を追加する

ここではSQLiteのstudy_logsテーブルを使います。既存の列はid・user_id・study_date・category・content・study_minutes・memoです。バックアップと検証用コピーを用意し、対象DBの定義を確認したうえで、次のSQLにより列を追加します。DB製品が異なる場合は、対応する構文・移行手順を確認します。

ALTER TABLE study_logs
ADD COLUMN study_place TEXT NOT NULL DEFAULT '';
  • TEXT:文字列を扱う列。
  • NOT NULL:SQLのNULLは保存しない。空文字はNULLではない。
  • DEFAULT '':値を指定しなかった場合の初期値を空文字にする。今回の追加後は、既存行の学習場所も空文字として読み取れる。

過去の記録には場所の情報がないため「未指定」として扱います。テーブルを削除して作り直したり、過去の行を自宅で埋めたりする必要はありません。このALTER TABLEは、study_place列がないDBへ1回適用するものです。すでに列があるDBへ繰り返して実行するSQLではありません。

新しいDBを作るためのCREATE TABLE文や、テスト用DBの定義にも同じ列を加えます。実際のDBだけ変えても、作成用SQLが古いままだと、新しい環境では列が存在しない状態に戻ってしまいます。

-- CREATE TABLE内の列定義に追加する1行
study_place TEXT NOT NULL DEFAULT ''

検証では、追加前後の件数と既存列の値が変わらないことも比較します。登録画面・Java・DBの変更を別々に本番へ出すと、列がないためエラーになったり、新しい値が保存されなかったりします。今回はローカルなどの検証環境で一式をそろえて確認する例です。

6.DAOのINSERT文に、保存先と値を追加する

StudyLogDao.javaのinsertです。getConnection()は既存のDB接続用メソッドを使います。java.sqlのConnection・PreparedStatement・SQLExceptionを使用しています。

public int insert(StudyLog studyLog) throws SQLException {
    String sql = "INSERT INTO study_logs "
        + "(user_id, study_date, category, content, "
        + "study_minutes, memo, study_place) "
        + "VALUES (?, ?, ?, ?, ?, ?, ?)";

    try (Connection conn = getConnection();
         PreparedStatement pstmt = conn.prepareStatement(sql)) {
        pstmt.setInt(1, studyLog.getUserId());
        pstmt.setString(2, studyLog.getStudyDate());
        pstmt.setString(3, studyLog.getCategory());
        pstmt.setString(4, studyLog.getContent());
        pstmt.setInt(5, studyLog.getStudyMinutes());
        pstmt.setString(6, studyLog.getMemo());
        pstmt.setString(7, studyLog.getStudyPlace());
        return pstmt.executeUpdate();
    }
}

列名・?・値をセットする処理の3つをそろえます。列の7番目がstudy_placeなので、7番目の?へgetStudyPlace()の値を渡します。setStringなどの番号は0ではなく1から始まります。DTOのコンストラクタでは8番目だった学習場所が、ここで7番目なのは、INSERTの列一覧に自動採番のidを含めていないためです。

このDB定義では、INSERTからstudy_placeを丸ごと抜かしても初期値の空文字で登録できてしまいます。「登録成功」と出るだけでは十分ではありません。選んだ「自宅」が実際にその列へ保存されたかを確かめます。

7.SELECTで取り出し、もう一度DTOへ入れる

保存側を直しても、一覧の取得処理が古いままでは値を表示できません。同じStudyLogDao.javaのfindByUserIdで、SELECTする列とDTOへ渡す値の両方を追加します。ListとArrayListはjava.util、ResultSetはjava.sqlの型です。

public List<StudyLog> findByUserId(int userId)
        throws SQLException {
    List<StudyLog> studyLogs = new ArrayList<>();
    String sql = "SELECT id, user_id, study_date, category, "
        + "content, study_minutes, memo, study_place "
        + "FROM study_logs WHERE user_id = ? "
        + "ORDER BY study_date DESC, id DESC";

    try (Connection conn = getConnection();
         PreparedStatement pstmt = conn.prepareStatement(sql)) {
        pstmt.setInt(1, userId);
        try (ResultSet rs = pstmt.executeQuery()) {
            while (rs.next()) {
                StudyLog studyLog = new StudyLog(
                    rs.getInt("id"),
                    rs.getInt("user_id"),
                    rs.getString("study_date"),
                    rs.getString("category"),
                    rs.getString("content"),
                    rs.getInt("study_minutes"),
                    rs.getString("memo"),
                    rs.getString("study_place")
                );
                studyLogs.add(studyLog);
            }
        }
    }
    return studyLogs;
}

登録時にはModelが入力値からDTOを作り、一覧取得時にはDAOがDBの検索結果からDTOを作っています。同じStudyLogクラスでも、作る場所と値の出どころは別です。検索時に最後の引数へ固定の空文字を渡してしまうと、DBに自宅があっても一覧用DTOには入りません。

利用者を絞るWHERE user_id = ?と、その値の指定は残します。項目を増やす変更で、他の利用者の記録まで取得するように変えてはいけません。一覧用Servletが、Modelから受け取ったList<StudyLog>をそのままrequestへ格納しているなら、その受け渡し処理自体は今回変更せずに使えます。

8.一覧JSPに、学習場所の列を追加する

study-log-list.jspで、見出し行の「メモ」の後ろに<th>学習場所</th>を追加します。続いて、既存のfor (StudyLog studyLog : studyLogs)による行の繰り返しの中で、メモのtdの後ろへ次のセルを追加します。

<td>
<% String studyPlace = studyLog.getStudyPlace();
   if ("自宅".equals(studyPlace)) { %>
    自宅
<% } else if ("学校".equals(studyPlace)) { %>
    学校
<% } else if ("その他".equals(studyPlace)) { %>
    その他
<% } else { %>
    未指定
<% } %>
</td>

今回は選択肢が決まっているので、保存値に対応する固定の表示文字を出します。空文字など該当しない値は未指定となり、過去の記録も読めます。選択肢にない値の登録を止める役割はModel、保存した値を画面でどう見せるかの役割はJSPです。表示の分岐だけで入力チェックの代わりにはしません。

見出しのthだけ追加したり、データのtdを違う位置に加えたりすると、列名と中身がずれます。両方ともメモの後ろに追加し、場所を選んだ記録と過去の記録を並べて確かめます。

9.「登録できた」ではなく、保存値と再表示まで確認する

テストでは、変更後のDBを使う検証環境で、他の必須項目を有効な値にそろえます。各ケースの前に件数と対象行を記録し、成功時は1件だけ増え、拒否時は増えないことを確認します。

テストケース期待結果
自宅・学校・その他各選択肢を別々に登録する。study_placeに選択した文字列が保存され、一覧を取得し直しても同じ場所が表示される。
未指定を選択空文字を保存する。一覧では空白のセルではなく「未指定」を表示する。
パラメータ自体がないstudyPlaceがnullでも未指定として登録できる。これは今回決めた任意項目の仕様。
想定外の値「カフェ」など選択肢にない値を検証用リクエストで送ると、理由を示して拒否する。DBの件数と内容は変わらない。
過去の記録列追加前に登録した行の場所は未指定。学習内容・時間・所有者など既存の値と件数は変わらない。
利用者の分離利用者Aの一覧に、利用者Bの記録や学習場所が混ざらない。
従来の入力条件学習内容の必須チェックなどが引き続き働く。学習場所が有効でも、他の項目が不正なら登録しない。

テスト仕様書に書くなら:学習場所「自宅」の登録

項目CH-02-01の記入例
事前条件修正版アプリと列追加済みの検証用DB。利用者1001でログイン済み。本人の記録2件、他利用者1件、全体3件。
入力学習日2026-09-28、カテゴリJava、学習内容「例外処理の復習」、学習時間30、メモ空欄、学習場所「自宅」。
手順登録画面で入力し、登録ボタンを1回押す。完了後に一覧を取得し直し、DBの追加行も確認する。
画面の期待結果登録が成功し、該当する新しい記録の学習場所が「自宅」と表示される。
DBの期待結果全体3→4件、利用者1001は2→3件。新しい行のuser_id=1001、study_place=自宅。他の入力値も対応し、既存行は変わらない。
実施結果欄対象の版・日時・実施者・実際の表示と保存値・合否を記録する。この表はテストの設計例であり、実行結果ではない。

Modelだけをテストして正常終了しても、フォームのname、SQLの列名、JSPの表示位置までは確認できません。反対に、一覧が自宅と表示されても、固定文字を出しているだけかもしれません。別の場所でも登録し、DBから取得し直した値と照合することで、受け渡しを確かめられます。

追加した項目が表示されないときの調べ方

起きていること確認する場所
選んだのに未指定になるフォームのnameとgetParameterの引数。ModelへstudyPlaceを渡しているか、DTOに固定の空文字を入れていないか。
DTOを作る場所でコンパイルエラーnew StudyLogの呼び出しをすべて検索する。登録・検索・テストで8個目の値を渡す。
列が存在しないエラー接続先のDBを確認する。別のDBコピーだけに列を追加していないか。
?の数や番号のエラーINSERTの列数・?の数・setStringなどの番号と型を照合する。
DBには自宅、一覧では未指定SELECTにstudy_placeがあるか、rs.getStringで取得し、検索用DTOの最後の引数に渡しているか。
一覧の見出しと値がずれる追加したthとtdの位置、各行のセル数を確認する。

最初から全部を直し直すのではなく、フォーム送信値、Modelに届いた値、保存された列、取得したDTO、表示の順に確認します。どこまでは自宅で、どこから未指定になったかを絞ると、直す場所を見つけやすくなります。

確認問題

  1. フォームにid="studyPlace"だけを付け、nameを書き忘れました。ServletでgetParameter("studyPlace")を呼ぶとどうなりますか。この例で登録結果がどうなるかも答えてください。
  2. StudyLogのコンストラクタに学習場所を追加しました。Modelだけ直せば十分ですか。ほかに確認する場所と、その理由を答えてください。
  3. 既存の学習記録に場所の情報はありません。study_place列の初期値を空文字にした場合、過去の記録はどう表示しますか。「自宅」で埋めてはいけない理由も答えてください。
  4. DBのstudy_placeには「学校」が保存されていますが、一覧は「未指定」になります。保存処理をやり直す前に、どの処理を確認しますか。
  5. 「その他」を選んで登録完了画面が出ました。これだけで項目追加のテストを合格にしてよいですか。追加で確認する保存・表示・既存機能の項目を挙げてください。

演習後の確認:こちらのページを使って、自分で考えた内容を見直せます。

まとめ

  • 項目追加では、保存する経路と取得・表示する経路の両方をつなぐ。
  • 名前・選択肢・未指定の扱い・過去データの表示を先に決める。
  • DTOの変更に加え、登録と検索それぞれのDTO作成箇所を確認する。
  • SQLの列名・?・セットする値をそろえ、既存DBと作成用定義の両方を更新する。
  • 登録完了だけでなく、保存値・再表示・既存データ・利用者の分離まで確かめる。

関連記事

シェアする