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

「登録画面に学習場所を追加して、あとから一覧でも見られるようにしたい」。入力欄を1つ増やすだけに見えても、画面だけ直したのでは、その値はDBに残りません。保存できても、取得する処理を直していなければ一覧に表示できません。
項目を追加するときは、入力した値を保存する道筋と、保存した値を表示へ戻す道筋の両方をつなぎます。この記事では「学習場所」を例に、変更するファイルとコード、過去のデータの扱い、確認するテストケースまでを説明します。
本記事は、Schoo「Java入門 中級」第10回「Webアプリケーション実装演習」に対応するステップアップ学習記事です。未受講の方にも分かるように、例で使う仕様と各クラスの役割を示します。授業の配布ファイルは前提にしていません。
今回の変更:学習場所を保存して、一覧でも表示する
変更前のアプリは、ログインした利用者が学習日・カテゴリ・学習内容・学習時間・メモを登録し、自分の記録を一覧で見るものです。ここに学習場所を追加します。まず、入力欄の形より先に次の仕様を決めます。
| 決めること | 今回の仕様 |
|---|---|
| 入力方法 | 「未指定・自宅・学校・その他」から選ぶ。任意項目とし、未指定のままでも登録できる。 |
| 保存する値 | 自宅・学校・その他はその文字列を保存する。未指定は空文字(長さ0の文字列)を保存する。 |
| 想定外の入力 | 選択肢にない文字列がサーバーに届いた場合は、エラーで通知して保存しない。 |
| 過去の記録 | 学習場所は分からないため、空文字として扱い、一覧では「未指定」と表示する。過去の記録を「自宅」などと決め付けない。 |
| 変えないこと | 既存の入力条件・ログイン確認・利用者ごとの記録の分離は維持する。今回は登録と一覧が対象。編集など別の機能があれば、その仕様も確認する。 |
「任意」という言葉だけでは、未入力時に空文字を保存するのか、nullにするのか、登録を止めるのかが決まりません。未指定の保存方法と表示方法まで決めると、Java・DB・画面で扱いが食い違うのを防げます。
図解:入力した学習場所が、保存されて一覧に戻るまで

Servletはリクエストから値を受け取り、Modelは登録してよい値かを判断します。DTO(ここではStudyLog)は値をひとまとまりにして渡すクラス、DAOはSQLを使って保存・取得するクラスです。JSPは取得した値を一覧のHTMLとして表示します。
DTOにフィールドを追加しただけでは、DBの列は増えません。DBに列を追加しただけでも、フォームの値が自動的に保存されるわけではありません。それぞれの受け渡しをコードでつなぐ必要があります。
1.画面・Java・DBで使う名前を決める
| 場所 | 名前と意味 |
|---|---|
| 画面の表示名 | 学習場所。利用者に見せる言葉。 |
| フォームのname | studyPlace。ブラウザがパラメータを送信するときの名前。 |
| Javaの変数・DTO | studyPlace。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、表示の順に確認します。どこまでは自宅で、どこから未指定になったかを絞ると、直す場所を見つけやすくなります。
確認問題
- フォームにid="studyPlace"だけを付け、nameを書き忘れました。ServletでgetParameter("studyPlace")を呼ぶとどうなりますか。この例で登録結果がどうなるかも答えてください。
- StudyLogのコンストラクタに学習場所を追加しました。Modelだけ直せば十分ですか。ほかに確認する場所と、その理由を答えてください。
- 既存の学習記録に場所の情報はありません。study_place列の初期値を空文字にした場合、過去の記録はどう表示しますか。「自宅」で埋めてはいけない理由も答えてください。
- DBのstudy_placeには「学校」が保存されていますが、一覧は「未指定」になります。保存処理をやり直す前に、どの処理を確認しますか。
- 「その他」を選んで登録完了画面が出ました。これだけで項目追加のテストを合格にしてよいですか。追加で確認する保存・表示・既存機能の項目を挙げてください。
演習後の確認:こちらのページを使って、自分で考えた内容を見直せます。
まとめ
- 項目追加では、保存する経路と取得・表示する経路の両方をつなぐ。
- 名前・選択肢・未指定の扱い・過去データの表示を先に決める。
- DTOの変更に加え、登録と検索それぞれのDTO作成箇所を確認する。
- SQLの列名・?・セットする値をそろえ、既存DBと作成用定義の両方を更新する。
- 登録完了だけでなく、保存値・再表示・既存データ・利用者の分離まで確かめる。