MVCモデル
はじめに
MVCモデルとは、 Model 、 View 、 Controller の3つの単語の頭文字から取られたものです。
要素 | 役割 | ファイルクラス |
---|---|---|
Model (モデル) |
アプリケーションの主たる処理(計算処理など)やデータの格納などを行う | java |
View (ビュー) |
ユーザに対して画面の表示を行う | jsp |
Controller (コントローラー) |
ユーザからの要求を受け取り、処理の実行をモデルに依頼し、 その結果の表示をビューに依頼する |
servlet |
もう気づいた方もいるでしょうか・・・?
実は、4章チェックテストは、このMVCモデルで作られていたのです。
なので、すでに身をもってMVCモデルを体感しています。
習ってなくてもできた?
このMVCモデルというのはシステムを作る上での単なる、 概念(考え方) の領域だからです。
この考え方を、 エンジニア全員 が 共通認識 として持つことで、いわゆる 話が早くなります 。
極端な例にはなりますが、あなたがModelを作ってと言われれば、Modelの役割の担うコーディングをすればOKなのです。
もっともメジャーな概念が MVCモデル ということですね。
この章で、さらにMVCモデルという考え方に慣れて行きましょう!
Step1 : MVCモデルの概要
基本的な動作、業務上の処理を行う部分を Model が受け持ち、
ユーザインタフェース部分を View が受け持ち、
これらのやり取りを制御するのが Controller です。
と言われてもパッとしないですね。
スキルチェック3で使ったソースも含めて、MVCの相関図を以下の画像にまとめてみました。
プログラムの大きな流れも①~⑧まであるので、順を追って確認してみると、さらにイメージがつきやすいと思います。
シーケンス | 処理内容 |
---|---|
① | ユーザーの要求(入力やボタン操作等) |
② | ①の要求に沿って処理(パラメーターの取得) |
③ | 要求ごとの処理(入力チェックや呼び出す処理の選定) |
④ | ServiceよりSQLを実行 |
⑤ | 実行結果を返却しBeanへ格納 |
⑥ | ③の要求に基づいて結果を返す |
⑦ | jspに結果を返す(③ or ⑥の結果) |
⑧ | 受け取った結果を元に画面へ表示 |
しっくりきたでしょうか….?
一度、MVCモデルを触っているので、この構造の理解については問題ないと思います。
では、なぜMVCモデルでシステムを構築しなくてはならないのでしょうか?
その大きな理由として、2つのことが考えられます。
理由①:生産性の向上
ModelをViewやControllerから密接な結びつきがないよう完全に切り離すことにより、
異なるWebアプリケーションでも使いまわすことが可能です。
同じようなロジックを、ゼロから記述しなくて済みます。
例えば、3か月費やして完成させた社員情報システム処理があったとします。
他の案件で似たようなものを作ってほしいとお願いされた時、
そのModelを流用し、ちょっと手を加えるだけでクリアできてしまう。なんていうことが可能になります。
理由②:メンテナンス性の向上
MVCモデルは、Model、View、Controllerを分割することで、それぞれの独立性を高めます。
独立性が高まれば、修正や追加機能といった要求に対して容易かつ柔軟に対応することが可能です。
そのため、メンテナンス性の向上が図れます。
概要としては、以上になります。
それでは、Model、View、Controllerそれぞれについて、詳しく見て行きましょう。
Step2 : Model
EmployeeBean
として一つファイルを設けたかと思います。
実はファイル名を Bean
としたことにも意味があります。
Bean
とは、ViewやControllerとは関係のないものであり、Java言語で作成された普通のクラスのことです。
それぞれの実際の部品化(コンポーネント化)したもの1個単位を単にBeanと呼ぶこともあります。
そのような名前の由来は、Javaのコーヒー豆という意味でBean(豆)という言葉を掛け合わせたものです。
総称して JavaBeans
と呼んでいます。
それでは、EmployeeBean
の中身を見て行きましょう。
public class EmployeeBean { private String Id; private String PassWord; private String Name; private String Comment; private String Login_Time; public void setId(String Id) { this.Id = Id; } public String getId() { return Id; } public void setPassWord(String PassWord) { this.PassWord = PassWord; } public String getPassWord() { return PassWord; } public void setName(String Name) { this.Name = Name; } public String getName() { return Name; } public void setLogin_Time(String Login_Time) { this.Login_Time = Login_Time; } public String getLogin_Time() { return Login_Time; } public void setComment(String Comment) { this.Comment = Comment; } public String getComment() { return Comment; } }
解説
JavaBeans
の仕様は、あらかじめ規約が定められています。
例えば、JSPから利用されるJavaBeans(Bean)として成立するためには、
外部からアクセスされるフィールドに対して、アクセッサメソッドを持つことです。
セッターメソッド
フィールドの値を設定するためのアクセッサメソッド、「setフィールド名」という形で記述します。
ゲッターメソッド
フィールドの値を取得するためのアクセッサメソッド、「getフィールド名」という形で記述します。
EmployeeBeanクラス
は、 Id
PassWord
Name
Comment
Login_Time
というフィールドがありますが、
それぞれのフィールドには値を設定または取得するための
setId()
、 getId()
というアクセッサメソッドを定義しています。
このようにして、セッター、ゲッターを設けることが、 JavaBeans(Bean)
の
決まりごとの一つとして定められています。
ビジネスロジック
MVCの中には、ビジネスロシックという部類があります。
SC3だと、 EmployeeService
がそれに該当します。
ビジネスロジック とは、実際の 業務にあたる部分 です。
EmployeeService
の場合、
「特定のユーザを探し、ヒットすればユーザー情報を提供し、その時間を記録する」という処理が流れているわけですが、
これは本来、人が仕事として論理的に(業務のルールに従って)裁かなくてはなりません。
このようにして、 人の業務を担うロジック ことを ビジネスロジック と呼んでいます。
ファイル名に関しても、 ○ ○ ○ ○ ○ Service
とすることが基本です。
package service; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.text.SimpleDateFormat; import java.util.Calendar; import bean.EmployeeBean; /** * ・社員情報管理サービス * * @author s.nanaumi * @since 2019/03/15 * */ public class EmployeeService { /** ドライバーのクラス名 */ private static final String POSTGRES_DRIVER = "org.postgresql.Driver"; /** ・JDMC接続先情報(※ポート番号【5433】は個人個人で異なります) */ private static final String JDBC_CONNECTION = "jdbc:postgresql://localhost:5433/lesson_db"; /** ・ユーザー名 */ private static final String USER = "postgres"; /** ・パスワード */ private static final String PASS = "postgres"; /** ・タイムフォーマット */ private static final String TIME_FORMAT = "yyyy/MM/dd hh:mm:ss"; EmployeeBean employeeData = null; // 送信されたIDとPassWordを元に社員情報を検索するためのメソッド public EmployeeBean search(String id, String password) { Connection connection = null; Statement statement = null; ResultSet resultSet = null; try { // データベースに接続 Class.forName(POSTGRES_DRIVER); connection = DriverManager.getConnection(JDBC_CONNECTION, USER, PASS); statement = connection.createStatement(); // ログインタイムの生成 Calendar cal = Calendar.getInstance(); SimpleDateFormat sdFormat = new SimpleDateFormat(TIME_FORMAT); String login_time = sdFormat.format(cal.getTime()); // 任意のユーザーのログインタイムをUPDATEする。プリペアドステートメントを作成してください。 String SQL_UPDATE = "UPDATE employee_table SET login_time = ? where id = ?"; PreparedStatement psExecuteUpdate = connection.prepareStatement(SQL_UPDATE); psExecuteUpdate.setString(1, login_time); psExecuteUpdate.setString(2, id); psExecuteUpdate.executeUpdate(); // 渡ってきたIDとPassWordで検索をかけるためのSQL文を記述しましょう。 String SELECT_SQL = "select * from employee_table WHERE id = ? and password = ? "; // PreparedStatementで PreparedStatement psExecuteQuery = connection.prepareStatement(SELECT_SQL); psExecuteQuery.setString(1, id); psExecuteQuery.setString(2, password); resultSet = psExecuteQuery.executeQuery(); while (resultSet.next()) { //column1,2,3という変数にName,Comment,Login_Timeを代入してください。 String column1 = resultSet.getString("name"); String column2 = resultSet.getString("comment"); String column3 = resultSet.getString("login_time"); //ShohinBeanに取得したデータをいれていきましょう employeeData = new EmployeeBean(); employeeData.setName(column1); employeeData.setComment(column2); employeeData.setLogin_Time(column3); } // forName()で例外発生 } catch (ClassNotFoundException e) { e.printStackTrace(); // getConnection()、createStatement()、executeQuery()で例外発生 } catch (SQLException e) { e.printStackTrace(); } finally { try { if (resultSet != null) { resultSet.close(); } if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } return employeeData; } }
解説
EmployeeServiceクラスは、 search()
メソッドにより、
ユーザID( id ) と パスワード( password ) を条件にして、
合致するものが、任意のデータベースのテーブルにあるかないかを判別する処理を行っています。
合致するものがあれば employeeData
にテーブルの値を格納し、
なければ、 employeeData
を null
で返す。というようなロジックになっています。
このようなクラス(ビジネスロジック)も Model という枠の中に入ってきます。
理由としては、 ビジネスロジックとして再利用可能な部品 なので、
ViewやControllerとは関係のないもの、つまり JavaBeans
という位置づけになるからです。
補足
- ・
null
による初期化 - Bean のような、 インスタンスを生成する必要があるクラスオブジェクトを最終的にViewへ返すようなロジック においては、
Beanをnull
で初期化しておくことが多いです。
また、null
で初期化することにより、プログラム実行時に余計なメモリを使用することを避けています。
(検索結果として表示データが無い場合に、インスタンスを生成していてもメモリを無駄に消費することを回避する意図があります。
加えて、今回は社員情報という1つの情報を丸っとViewへ返す作りとしているため、
View側で最低限の表示ロジックを組むにあたっての判断材料( if (employeeBean != null)
の部分)としています。
(※詳細は Step3 : View にて解説します。
Step3 : View
wiki3-2
で JSP
について学びましたが、
MVCモデルにおいての JSP
の役割は、 ブラウザに表示するためのもの です。
そのため、JSPには表示するための簡単なロジックが少しあるにせよ、
制御ロジックやビジネスロジックなどは無いようにします。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ page import="bean.EmployeeBean"%> <% // EmployeeController.javaから渡されたBeanを受けとります。 EmployeeBean employeeBean = (EmployeeBean) request.getAttribute("EmployeeBean"); %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>検索結果</title> </head> <body> <div align="center"> <% if (employeeBean != null) { %> <table border="1"> <tr> <th>社員名</th> <td><%=employeeBean.getName()%></td> </tr> <tr> <th>コメント</th> <!-- コメントを表示してください --> <td><%=employeeBean.getComment()%></td> </tr> <tr> <th>ログインタイム</th> <!-- ログインタイムを表示してください --> <td><%=employeeBean.getLogin_Time()%></td> </tr> </table> <% } else { %> IDもしくはパスワードが間違ってます <% } %> </div> </body> </html>
解説
まず、EmployeeController
からの指示を受け取ります。
EmployeeBean employeeBean = (EmployeeBean) request.getAttribute("EmployeeBean");
EmployeeController
から渡ってきたデータが employeeBean
に値が格納されるわけですが、
その値を使って、条件分岐します。条件は二つです。
条件1 : nullではない
nullではない つまり、検索条件に合致したデータが存在し、テーブルのデータを取得できているはずなので、
getメソッドを用いて取得データの値を表示します。
<% if (employeeBean != null) { %> <table border="1"> <tr> <th>社員名</th> <td><%=employeeBean.getName()%></td> </tr> <tr> <th>コメント</th> <!-- コメントを表示してください --> <td><%=employeeBean.getComment()%></td> </tr> <tr> <th>ログインタイム</th> <!-- ログインタイムを表示してください --> <td><%=employeeBean.getLogin_Time()%></td> </tr> </table>
条件2:nullだった場合
nullだった つまり、テーブルに条件と合致したデータがなかったので、
その旨を表す文言を表示する。
今回の場合は、 IDもしくはパスワードが間違ってます
としてます。
<% } else { %> IDもしくはパスワードが間違ってます <% } %>
このようにして、MVC構造においての JSP
はとってもシンプルなものになります。
Step4 : Controller
最後は、 Controller についてです。
package controller; /** * 社員情報管理コントローラー(メインサーブレット) * * @author s.nanaumi * @since 2019/03/15 * */ import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import bean.EmployeeBean; import service.EmployeeService; public class EmployeeController extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { // index.htmlから送信されたIDとPassWordの値を取得できるように修正しましょう。 String id = request.getParameter("id"); String password = request.getParameter("password"); /* * IDとPassWordと共に、社員情報を検索する関数の呼び出し、結果をJSPに渡す処理 */ // 問① EmployeeServiceクラスをインスタンス化する。 EmployeeService employeeService = new EmployeeService(); // 問② EmployeeBeanをインスタンス化する。 EmployeeBean employeeBean = new EmployeeBean(); // 問③ インスタンス化したEmployeeBeanにEmployeeServiceのsearch関数を代入 employeeBean = employeeService.search(id, password); // 問④ 取得したEmployeeBeanの値をindex.jspに渡すためリクエストスコープにセット request.setAttribute("EmployeeBean", employeeBean); } catch (Exception e) { e.printStackTrace(); } finally { ServletContext context = this.getServletContext(); RequestDispatcher dispatcher = context.getRequestDispatcher("/index.jsp"); dispatcher.forward(request, response); } } }
解説
図とシーケンス表をもとに、順を追ってみていきましょう。
シーケンス | 処理内容 |
---|---|
① | ユーザーの要求(入力やボタン操作等) |
② | ①の要求に沿って処理(パラメーターの取得) |
③ | 要求ごとの処理(入力チェックや呼び出す処理の選定) |
④ | ServiceよりSQLを実行 |
⑤ | 実行結果を返却しBeanへ格納 |
⑥ | ③の要求に基づいて結果を返す |
⑦ | jspに結果を返す(③ or ⑥の結果) |
⑧ | 受け取った結果を元に画面へ表示 |
シーケンス1:ユーザーの要求(入力やボタン操作等)
ユーザーが画面にて、任意の情報を入力する
シーケンス2:①の要求に沿って処理
EmployeeController
が任意の情報をキャッチ
ユーザーからの入力情報を、パラメーターとして受け取ります。
String id = request.getParameter("id"); String password = request.getParameter("password");
シーケンス3:要求ごとの処理(入力チェックや呼び出す処理の選定)
EmployeeController
が 任意のユーザー情報をもとに、
社員情報の照会をする必要があるため、EmployeeService
の search
メソッドの呼び出す。
employeeBean = employeeService.search(id, password);
シーケンス4:ServiceよりSQLを実行
ビジネスロジックである、 EmployeeService
が仕事を開始する。
まずは、SQLに問い合わせる。
String SELECT_SQL = "select * from employee_table WHERE id = ? and password = ? "; // PreparedStatementで PreparedStatement psExecuteQuery = connection.prepareStatement(SELECT_SQL); psExecuteQuery.setString(1, id); psExecuteQuery.setString(2, password); resultSet = psExecuteQuery.executeQuery();
シーケンス5:実行結果を返却しBeanへ格納
ヒットしたので、SQLの値を employeeData
に見繕って EmployeeController
返してあげる。
employeeData = new EmployeeBean(); employeeData.setName(column1); employeeData.setComment(column2); employeeData.setLogin_Time(column3);
search
メソッドの戻り値が employeeData
というのもポイントです。
//searchメソッドの戻り値 return employeeData;
シーケンス6:③の要求に基づいて結果を返す
searchメソッド
の戻り値が employeeData
(SQLの結果が格納されている変数)なので、
それを employeeBean
に格納。
employeeBean = employeeService.search(id, password);
シーケンス7:jspに結果を返す(③ or ⑥の結果)
JSP結果を返す値を準備。
キーは "EmployeeBean"
として、
値は、 SQLの値が格納されている employeeBean
となる。
request.setAttribute("EmployeeBean", employeeBean);
渡したいJSPを指定する。
RequestDispatcher dispatcher = context.getRequestDispatcher("/index.jsp");
シーケンス8:受け取った結果を元に画面へ表示
よって、index.jspが受け取った値を表示するが可能になる。
このように、MVCモデルにおいての Controller
は、 ユーザー 、 Model、 View の仲介役になっています。
まさに、システムを Controller しているわけですね。
Step5:総括
基本はMVC
MVCモデルのつながりについて一通りは説明してきましたが、実際にプログラミングしていくと大変難しいと思います。
また、最初のTOPページがhtmlであったり、JSPであったりすると少し難易度が上がりますし、
複数ページが遷移する場合などは、とても難しく感じると思われます。
しかし、複数ページが遷移する場合でも基本的な MVCモデルのつながりは同じ です。
プログラムの流れ を理解し、それぞれの 役割を把握する ことで、
どこにどんな処理が必要なのか が見えてきます。
そんなことを意識しながら、これからの課題を進めて行きましょう。