チーム名、プロジェクト名、サービス名を決めて、ロバストネス図を作成する。
9月15日
SpringMVCにおけるユーザー認証
SpringMVCでは、spring-securityを使ってユーザー認証を行う。
そのために、pom.xmlに以下の内容を追加する。
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>4.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.0.1.RELEASE</version> </dependency>
web.xmlの最後にフィルターを追加する。
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
src/main/resources/spring フォルダに、spring-security.xml を作成する。
<beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd"> <http pattern="/" security="none"/> <http pattern="/index.jsp" security="none"/> <http auto-config="true" > <intercept-url pattern="/**" access="hasRole('ROLE_USER')" /> <form-login /> <logout /> </http> <beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <beans:property name="driverClassName" value="org.hsqldb.jdbc.JDBCDriver" /> <beans:property name="url" value="jdbc:hsqldb:hsql://localhost/mydata" /> <beans:property name="username" value="sa" /> <beans:property name="password" value="" /> </beans:bean> <!-- ユーザとROLEを定義 --> <authentication-manager> <authentication-provider> <jdbc-user-service data-source-ref="dataSource" users-by-username-query="SELECT user_name, password, enabled FROM users WHERE user_name = ?" authorities-by-username-query="SELECT user_name, role FROM users WHERE user_name = ?" /> </authentication-provider> </authentication-manager> </beans:beans>
ユーザーとユーザの権限を管理するためのテーブルを作成する。
CREATE TABLE users( user_id bigint NOT NULL identity, user_name varchar(50) NOT NULL, password varchar(50) NOT NULL, enabled tinyint NOT NULL, role varchar(100) NOT NULL );
USERSテーブルにテスト用のユーザーを登録する。
INSERT INTO users ( user_name , password , enabled, role) VALUES ( 'taro', 'abcd' , 1, 'ROLE_USER' );
チーム開発
チームで開発するWebアプリケーションを決めよう!
- 2ちゃんねる
- LINE
- Wiki
- EC(ネットショップ)
以下のことを決めよう。
- 作成するサービスを決める(候補を出す)
- チーム名を決める
- リーダーを決める
- サービスの名前(プロジェクト名)を決める
- 基本的な画面遷移図(ロバストネス図)を作成する
ロバストネス図は、astah community で作れる。
チーム1
チーム名:やりたい
リーダー:西田
サービス名:やりたま
プロジェクト名:Yaritama
チーム2
チーム名:ナイトプール
リーダー:中尾
サービス名: 写真共有アプリ
プロジェクト名: PhotoSharing
チーム3
チーム名:ボビーオロゴン
リーダー:前田
サービス名:診断メーカー
プロジェクト名:ShindanMaker
チーム4
チーム名:ママ、早く!
リーダー:山本
サービス名:掲示板
プロジェクト名:3chan
チーム5
チーム名:ゲームウィキ麻生支部
リーダー:宮崎
サービス名:ゲームウィキ
プロジェクト名:GameWiki
9月11日
Gitの練習
todo.jsp を変更してみる。
<body> <h1>ToDo項目</h1> <form method="post" action="input"> <input type="text" name="item"> <input type="submit" value="登録"> </form> <h3>未完了</h3> <div id="todo"> <c:if test="${list != null}"> <form method="post" action="check"> <c:forEach var="t" items="${list}"> <c:if test="${t.done == null}"> <div id="check${t.id}"> <input type="checkbox" name="items" value="${t.id}" />${t.item} <span class="days">${t.days}日前</span> </div> </c:if> </c:forEach> </form> </c:if> </div>
変更して保存すると、プロジェクトエクスプローラ上のファイル名の左側に「>」が表示される。
これは、リポジトリの最新の状態から変更があったことを示す。
プロジェクトを右クリックして[チーム]-[コミット]を選択すると、todo.jsp がファイルリストに表示される。
todo.jspをダブルクリックすると変更箇所が確認できる。
変更内容が間違いなければ、コミットメッセージを入力してコミットを実行する。
未完了項目の経過日数表示
未完了項目の右側に登録してからの経過日数を表示するようにする。
経過日数はサーバー側で計算した方が実装が簡単そうなので、ToDoエンティティに経過日数を格納する days を追加する。
このデータはデータベースに保存する必要はないので @Transient アノテーションを追加しておく。
package jp.abc; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Transient; @Entity public class ToDo { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column private long id; @Column private String item; @Column private Date created; @Column private Date done; @Transient private int days; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getItem() { return item; } public void setItem(String item) { this.item = item; } public Date getCreated() { return created; } public void setCreated(Date created) { this.created = created; } public Date getDone() { return done; } public void setDone(Date done) { this.done = done; } public int getDays() { return days; } public void setDays(int days) { this.days = days; } }
コントローラを見ると、あちこちで dao.getAll() を使っているので、コントローラではなくDAO実装内で追加すると一か所の変更で済ませることができる。
public List<ToDo> getAll() { EntityManager manager = factory.createEntityManager(); List<ToDo> list = null; CriteriaBuilder builder = manager.getCriteriaBuilder(); CriteriaQuery<ToDo> q = builder.createQuery(ToDo.class); Root<ToDo> r = q.from(ToDo.class); q.select(r); list = (List<ToDo>)manager.createQuery(q).getResultList(); manager.close(); Date now = new Date(); for (ToDo t : list) { long diff = now.getTime() - t.getCreated().getTime(); diff = diff / (1000 * 60 * 60 * 24); t.setDays((int)diff); } return list; }
todo.jsp の未完了項目の右側に値を追加してみる。
<h3>未完了</h3> <div id="todo"> <c:if test="${list != null}"> <form method="post" action="check"> <c:forEach var="t" items="${list}"> <c:if test="${t.done == null}"> <div id="check${t.id}"> <input type="checkbox" name="items" value="${t.id}" />${t.item} ${t.days}日前 </div> </c:if> </c:forEach> </form> </c:if> </div>
「○日前」の文字サイズや色を変更するために span タグで囲む。
<input type="checkbox" name="items" value="${t.id}" />${t.item} <span class="days">${t.days}日前</span>
days クラス用のCSSを用意する。
<title>ToDoアプリ</title> <style type="text/css"> span.days { font-size: 9pt; color: gray; margin-left: 20px; } </style> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
動作確認したら、プロジェクトを右クリックして[チーム]-[コミット]を選択する。
ToDo.java
ToDoDaoImpl.java
todo.jsp
の3ファイルがリストされる。
それぞれのファイルをダブルクリックして変更箇所を確認し、問題がなければコミットメッセージを入力してコミットを実行する。
完了済みの項目の経過日数
完了済みの項目の経過日数は、完了した日からの経過日数を表示する。
まずは、未完了項目と同じように、登録日からの経過日数を表示してみる。
<h3>完了済み</h3> <div id="done"> <c:if test="${list != null}"> <c:forEach var="t" items="${list}"> <c:if test="${t.done != null}"> <div id="check${t.id}"> <input type="checkbox" name="items" value="${t.id}" checked="checked" />${t.item} <span class="days">${t.days}日前</span> </div> </c:if> </c:forEach> </c:if> </div>
完了項目に対しての days の値は、完了日からの経過日数となるように、サーバー側の ToDoDaoImpl.java で計算する。
public List<ToDo> getAll() { EntityManager manager = factory.createEntityManager(); List<ToDo> list = null; CriteriaBuilder builder = manager.getCriteriaBuilder(); CriteriaQuery<ToDo> q = builder.createQuery(ToDo.class); Root<ToDo> r = q.from(ToDo.class); q.select(r); list = (List<ToDo>)manager.createQuery(q).getResultList(); manager.close(); Date now = new Date(); for (ToDo t : list) { long from; if (t.getDone() == null) { from = t.getCreated().getTime(); } else { from = t.getDone().getTime(); } long diff = now.getTime() - from; diff = diff / (1000 * 60 * 60 * 24); t.setDays((int)diff); } return list; }
新しく項目を追加したり、チェックボックスをチェックすると「○日前」の表示が消えてしまう。
これは、上記操作をしたときに、jQueryでHTML要素を生成しなおしているため。
span要素を追加する処理を、jQueryで記述することで解決する。
function init() { $('input[name="items"]').change(function(){ console.log($(this).prop('checked')); var url; if ($(this).prop('checked')) { url = 'done'; } else { url = 'redo'; } $.post( url, 'id=' + $(this).val(), function(data) { console.log(data); $('#todo form').children().remove(); $('#done').children().remove(); for (var i = 0; i < data.length; i++) { var div = $('<div>'); div.attr('id', 'check' + data[i].id); var cb = $('<input>') .attr('type', 'checkbox') .attr('name', 'items') .attr('value', data[i].id); div.append(cb).append(data[i].item) var s = $('<span>'); s.addClass('days'); s.text(data[i].days + '日前'); div.append(s); if (data[i].done == null) { $('#todo form').append(div); } else { cb.attr('checked', 'checked'); $('#done').append(div); } } init(); }); }); }
動作確認できたら、[チーム]-[コミット]で変更内容をGitリポジトリに反映させる。
これまでの変更内容は、[チーム]-[ヒストリーに表示]で確認できる。
9月8日
サーバーから受信したデータをもとに画面を更新するようにしたところ、最初のチェックに対してはToDo項目が完了済みに移動したが、2個目からは移動しなくなってしまった。
これは、change()イベントを拾う処理が画面読み込み完了時(load)のみに行われているため。
新しく生成した input[name=”items”] のセレクタで指定した要素にはイベントを拾う処理が登録されていないために動作しない。
この対策を実施する。
まずはload時の処理をfunctionに分割する。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>ToDoアプリ</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script type="text/javascript"> $(function(){ init(); }) function init() { $('input[name="items"]').change(function(){ console.log($(this).parent()); $('#done').append($(this).parent()); $.post( 'done', 'id=' + $(this).val(), function(data) { console.log(data); $('#todo form').children().remove(); $('#done').children().remove(); for (var i = 0; i < data.length; i++) { var div = $('<div>'); div.attr('id', 'check' + data[i].id); var cb = $('<input>') .attr('type', 'checkbox') .attr('name', 'items') .attr('value', data[i].id); div.append(cb).append(data[i].item); if (data[i].done == null) { $('#todo form').append(div); } else { cb.attr('checked', 'checked'); $('#done').append(div); } } }); }); } </script> </head> <body> <h1>ToDo項目</h1> <form method="post" action="input"> <input type="text" name="item"> <input type="submit" value="登録"> </form> <div id="todo"> <c:if test="${list != null}"> <form method="post" action="check"> <c:forEach var="t" items="${list}"> <c:if test="${t.done == null}"> <div id="check${t.id}"> <input type="checkbox" name="items" value="${t.id}" />${t.item} </div> </c:if> </c:forEach> </form> </c:if> </div> <hr /> <h3>完了済み</h3> <div id="done"> <c:if test="${list != null}"> <c:forEach var="t" items="${list}"> <c:if test="${t.done != null}"> <div id="check${t.id}"> <input type="checkbox" name="items" value="${t.id}" checked="checked" />${t.item} </div> </c:if> </c:forEach> </c:if> </div> </body> </html>
この変更ではいままでどおりの動作となる。
さらに、サーバーからデータを受信して、新規に要素を生成して画面上に追加した処理のあとに init() を呼ぶようにすることで、チェックボックスのイベントに対しての処理を行うようにする。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>ToDoアプリ</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script type="text/javascript"> $(function(){ init(); }) function init() { $('input[name="items"]').change(function(){ console.log($(this).parent()); $('#done').append($(this).parent()); $.post( 'done', 'id=' + $(this).val(), function(data) { console.log(data); $('#todo form').children().remove(); $('#done').children().remove(); for (var i = 0; i < data.length; i++) { var div = $('<div>'); div.attr('id', 'check' + data[i].id); var cb = $('<input>') .attr('type', 'checkbox') .attr('name', 'items') .attr('value', data[i].id); div.append(cb).append(data[i].item); if (data[i].done == null) { $('#todo form').append(div); } else { cb.attr('checked', 'checked'); $('#done').append(div); } } init(); }); }); } </script> </head> <body> <h1>ToDo項目</h1> <form method="post" action="input"> <input type="text" name="item"> <input type="submit" value="登録"> </form> <div id="todo"> <c:if test="${list != null}"> <form method="post" action="check"> <c:forEach var="t" items="${list}"> <c:if test="${t.done == null}"> <div id="check${t.id}"> <input type="checkbox" name="items" value="${t.id}" />${t.item} </div> </c:if> </c:forEach> </form> </c:if> </div> <hr /> <h3>完了済み</h3> <div id="done"> <c:if test="${list != null}"> <c:forEach var="t" items="${list}"> <c:if test="${t.done != null}"> <div id="check${t.id}"> <input type="checkbox" name="items" value="${t.id}" checked="checked" />${t.item} </div> </c:if> </c:forEach> </c:if> </div> </body> </html>
チェックボックスのチェックを外された時は、ToDo項目を完了済みから未完了に移動させるようにする。
サーバー側では、URL /redo を用意する。処理の内容は、done()とほぼ同じで、ToDoエンティティのcreatedの値を現在時刻にして、doneの値をnullに戻すだけの違い。
@RequestMapping(value = "/redo", method = RequestMethod.POST) @ResponseBody @ResponseStatus(value = HttpStatus.OK) public List<ToDo> redo(@RequestParam(value = "id") String id, Model model) { ToDoDao<ToDo> dao = new ToDoDaoImpl(); ToDo todo = dao.findById(Long.parseLong(id)); todo.setCreated(new Date()); todo.setDone(null); dao.update(todo); List<ToDo> list = dao.getAll(); return list; }
ブラウザ側では、完了済みにあるチェックボックスのチェックが外されたときは、redo に POST 送信するように、JavaScriptコードを書き換えればよい。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>ToDoアプリ</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script type="text/javascript"> $(function(){ init(); }) function init() { $('input[name="items"]').change(function(){ console.log($(this).prop('checked')); var url; if ($(this).prop('checked')) { url = 'done'; } else { url = 'redo'; } $.post( url, 'id=' + $(this).val(), function(data) { console.log(data); $('#todo form').children().remove(); $('#done').children().remove(); for (var i = 0; i < data.length; i++) { var div = $('<div>'); div.attr('id', 'check' + data[i].id); var cb = $('<input>') .attr('type', 'checkbox') .attr('name', 'items') .attr('value', data[i].id); div.append(cb).append(data[i].item); if (data[i].done == null) { $('#todo form').append(div); } else { cb.attr('checked', 'checked'); $('#done').append(div); } } init(); }); }); } </script> </head> <body> <h1>ToDo項目</h1> <form method="post" action="input"> <input type="text" name="item"> <input type="submit" value="登録"> </form> <div id="todo"> <c:if test="${list != null}"> <form method="post" action="check"> <c:forEach var="t" items="${list}"> <c:if test="${t.done == null}"> <div id="check${t.id}"> <input type="checkbox" name="items" value="${t.id}" />${t.item} </div> </c:if> </c:forEach> </form> </c:if> </div> <hr /> <h3>完了済み</h3> <div id="done"> <c:if test="${list != null}"> <c:forEach var="t" items="${list}"> <c:if test="${t.done != null}"> <div id="check${t.id}"> <input type="checkbox" name="items" value="${t.id}" checked="checked" />${t.item} </div> </c:if> </c:forEach> </c:if> </div> </body> </html>
Gitによるバージョン管理
Eclipseでプロジェクトを右クリックし、[チーム]-[プロジェクトの共用]を選択する。
リポジトリタイプは「Git」を選択し「次へ」をクリックする。
リポジトリの「作成」をクリックし、リポジトリディレクトリに「C:\Users\name\git」が指定されている。
「完了」ボタンが押せない場合は、フォルダ名を適当に変更して「完了」ボタンが有効になるようにし、「完了」をクリックする。
「完了」すると、プロジェクト名の右側に「NO-HEAD」の表示が出る。
プロジェクトを右クリックし、[チーム]-[コミット]を選択すると、画面の下にコミット対象となるファイルの一覧が表示される。余計なファイルがたくさん含まれているので、これを除外する設定を行う。
リポジトリ内にあるプロジェクトのフォルダ内に「.gitignore」ファイルを作成する。
エクスプローラでは新規ファイルでテキストファイル「gitignore.txt」を作成する。
コマンドプロンプトを起動してファイル名を変更する。
> ren gitignore.txt .gitignore
.gitignore ファイル内に、除外するファイルやフォルダ名を記載する。
/target .settings/ .classpath .project /pleiades45
.gitignore ファイルを保存したら、Eclipseでプロジェクトを右クリックし「リフレッシュ」を選択する。
プロジェクトを右クリックし[チーム]-[コミット]を選択する。
コミット対象のファイル一覧に余計なファイルが含まれていないことを確認したら、コミットメッセージに変更理由(今回は最初なので「新規作成」でもよい)などを記載して「コミット」をクリックする。
コミットが成功したら、プロジェクト名の右側が「master」に変化する。
9月4日
前回は、ToDo項目にチェックすると、AJAXでサーバーにデータを送信して、完了日時をエンティティに登録し、データベースを更新するところまで実装した。
この状態でToDo項目の画面にアクセスすると、完了済みの項目が未完了に表示されてしまう。
未完了のToDo項目と、完了済みのToDo項目をそれぞれの領域に表示するよう修正する。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>ToDoアプリ</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script type="text/javascript"> $(function(){ $('input[name="item"]').change(function(){ console.log($(this).parent()); $('#done').append($(this).parent()); $.post('done', 'id=' + $(this).val()); }); }) </script> </head> <body> <h1>ToDo項目</h1> <form method="post" action="input"> <input type="text" name="item"> <input type="submit" value="登録"> </form> <c:if test="${list != null}"> <form method="post" action="check"> <c:forEach var="t" items="${list}"> <c:if test="${t.done == null}"> <div id="check${t.id}"> <input type="checkbox" name="item" value="${t.id}" />${t.item} </div> </c:if> </c:forEach> </form> </c:if> <hr /> <h3>完了済み</h3> <div id="done"> <c:if test="${list != null}"> <c:forEach var="t" items="${list}"> <c:if test="${t.done != null}"> <div id="check${t.id}"> <input type="checkbox" name="item" value="${t.id}" checked="checked" />${t.item} </div> </c:if> </c:forEach> </c:if> </div> </body> </html>
テキストフィールドに入力して「登録」ボタンを押すと、テキストフィールドが完了済みに移動してしまう問題を解決する。
jQueryで input[name=”item”] というセレクタを指定していると、これがテキストフィールドにも該当してしまう。
チェックボックスのnameをitemsに変更してchangeイベントを拾うところもinput[name=”items”]とする。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>ToDoアプリ</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script type="text/javascript"> $(function(){ $('input[name="items"]').change(function(){ console.log($(this).parent()); $('#done').append($(this).parent()); $.post('done', 'id=' + $(this).val()); }); }) </script> </head> <body> <h1>ToDo項目</h1> <form method="post" action="input"> <input type="text" name="item"> <input type="submit" value="登録"> </form> <c:if test="${list != null}"> <form method="post" action="check"> <c:forEach var="t" items="${list}"> <c:if test="${t.done == null}"> <div id="check${t.id}"> <input type="checkbox" name="items" value="${t.id}" />${t.item} </div> </c:if> </c:forEach> </form> </c:if> <hr /> <h3>完了済み</h3> <div id="done"> <c:if test="${list != null}"> <c:forEach var="t" items="${list}"> <c:if test="${t.done != null}"> <div id="check${t.id}"> <input type="checkbox" name="items" value="${t.id}" checked="checked" />${t.item} </div> </c:if> </c:forEach> </c:if> </div> </body> </html>
pom.xmlにJSONを扱うためのライブラリを追加する。
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.8.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.9</version> </dependency>
追加したら、[maven]-[プロジェクトの更新]と[実行]-[maven install]を実行する。
ToDo項目を更新するための /done に対してPOST送信したら、その応答で、JSONデータを返すようにする。
@RequestMapping(value = "/done", method = RequestMethod.POST) @ResponseBody @ResponseStatus(value = HttpStatus.OK) public List<ToDo> done(@RequestParam(value = "id") String id, Model model) { ToDoDao<ToDo> dao = new ToDoDaoImpl(); ToDo todo = dao.findById(Long.parseLong(id)); todo.setDone(new Date()); dao.update(todo); List<ToDo> list = dao.getAll(); return list; }
jQueryでPOSTしたときに応答を受信してJavaScriptコンソールにデータを出力してみる。
<script type="text/javascript"> $(function(){ $('input[name="items"]').change(function(){ console.log($(this).parent()); $('#done').append($(this).parent()); $.post( 'done', 'id=' + $(this).val(), function(data) { console.log(data); }); }); }) </script>
チェックして、サーバーからデータを受け取ったら、受け取ったデータに基づいてToDo項目を更新する。
まずは、既存の項目をすべて削除する。
<script type="text/javascript"> $(function(){ $('input[name="items"]').change(function(){ console.log($(this).parent()); $('#done').append($(this).parent()); $.post( 'done', 'id=' + $(this).val(), function(data) { console.log(data); $('#todo form').children().remove(); $('#done').children().remove(); }); }); }) </script>
受信したJSONデータにもとづいてdiv要素を作り直す。
完了日時が設定されていなければ未完了側に、設定されていれば完了側にdiv要素を追加する。
$(function(){ $('input[name="items"]').change(function(){ console.log($(this).parent()); $('#done').append($(this).parent()); $.post( 'done', 'id=' + $(this).val(), function(data) { console.log(data); $('#todo form').children().remove(); $('#done').children().remove(); for (var i = 0; i < data.length; i++) { var div = $('<div>'); div.attr('id', 'check' + data[i].id); var cb = $('<input>') .attr('type', 'checkbox') .attr('name', 'items') .attr('value', data[i].id); div.append(cb).append(data[i].item); if (data[i].done == null) { $('#todo form').append(div); } else { cb.attr('checked', 'checked'); $('#done').append(div); } } }); }); }) </script>
9月1日
jQueryとAJAXでTODO項目を更新する。
jQueryライブラリを追加する。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>ToDoアプリ</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> </head> <body> <h1>ToDo項目</h1> <form method="post" action="input"> <input type="text" name="item"> <input type="submit" value="登録"> </form> <c:if test="${list != null}"> <form method="post" action="check"> <c:forEach var="t" items="${list}"> <input type="checkbox" name="item" value="${t.id}" />${t.item}<br /> </c:forEach> </form> </c:if> </body> </html>
jQueryでチェックボックスのイベントを取得するためのコードを追加する。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>ToDoアプリ</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script type="text/javascript"> $(function(){ $('input[name="item"]').change(function(){ console.log("change"); }); }) </script> </head> (略)
チェックボックスをクリックすると、JavaScriptコンソールに change が出力されるのが確認できる。
次に、チェックボックスをクリックしたときに、完了済みのエリアにチェックボックスを移動するように処理を追加する。
そのために、完了済みのチェックボックスを配置するための場所を用意する。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>ToDoアプリ</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script type="text/javascript"> $(function(){ $('input[name="item"]').change(function(){ console.log("change"); }); }) </script> </head> <body> <h1>ToDo項目</h1> <form method="post" action="input"> <input type="text" name="item"> <input type="submit" value="登録"> </form> <c:if test="${list != null}"> <form method="post" action="check"> <c:forEach var="t" items="${list}"> <input type="checkbox" id="check${t.id}" name="item" value="${t.id}" />${t.item}<br /> </c:forEach> </form> </c:if> <hr /> <h3>完了済み</h3> <div id="done"></div> </body> </html>
実際にチェックボックスをクリックしてみると、□だけが移動してしまう。
文字も移動させるために、div要素でまとめる。
<c:if test="${list != null}"> <form method="post" action="check"> <c:forEach var="t" items="${list}"> <div id="check${t.id}"> <input type="checkbox" name="item" value="${t.id}" />${t.item} </div> </c:forEach> </form> </c:if>
チェックボックスがクリックされたイベントが発生したら、完了済みに移動する。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>ToDoアプリ</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script type="text/javascript"> $(function(){ $('input[name="item"]').change(function(){ console.log($(this).parent()); $('#done').append($(this).parent()); }); }) </script> </head>
コントローラに、完了を受け取るためのメソッドを用意する。
パラメータを受け取れたかどうかを確認するためのコードを書いておく。
@RequestMapping(value = "/done", method = RequestMethod.POST) public void done(@RequestParam(value = "id") String id, Model model) { System.out.println(id); }
ブラウザでチェックボックスをチェックすると、そのIDがサーバー側のコンソールに表示されることがわかる。
ToDoエンティティを更新できるようにする。
ToDoDaoにメソッドを追加する。
package jp.abc; import java.util.List; public interface ToDoDao<T> { public List<T> getAll(); public void add(T item); public T findById(long id); public void update(T item); }
ToDoDaoImplに実装を追加する。
package jp.abc; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; public class ToDoDaoImpl implements ToDoDao<ToDo> { private static EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistenceUnit"); public List<ToDo> getAll() { EntityManager manager = factory.createEntityManager(); List<ToDo> list = null; CriteriaBuilder builder = manager.getCriteriaBuilder(); CriteriaQuery<ToDo> q = builder.createQuery(ToDo.class); Root<ToDo> r = q.from(ToDo.class); q.select(r); list = (List<ToDo>)manager.createQuery(q).getResultList(); manager.close(); return list; } public void add(ToDo item) { EntityManager manager = factory.createEntityManager(); EntityTransaction tx = manager.getTransaction(); tx.begin(); manager.persist(item); tx.commit(); manager.close(); } public ToDo findById(long id) { EntityManager manager = factory.createEntityManager(); Query q = manager.createQuery("from ToDo where id = " + id); return (ToDo)q.getSingleResult(); } public void update(ToDo item) { EntityManager manager = factory.createEntityManager(); EntityTransaction tx = manager.getTransaction(); tx.begin(); manager.merge(item); tx.commit(); manager.close(); } }
コントローラでToDoエンティティを更新するコードを追加する。
@RequestMapping(value = "/done", method = RequestMethod.POST) public void done(@RequestParam(value = "id") String id, Model model) { ToDoDao<ToDo> dao = new ToDoDaoImpl(); ToDo todo = dao.findById(Long.parseLong(id)); todo.setDone(new Date()); dao.update(todo); }
8月28日
SpringMVCによるWebアプリを作成する練習問題
ToDoリストアプリ
プロジェクト名: ToDo
パッケージ名: jp.abc
コントローラクラス名: ToDoController
エンティティクラス名: ToDo
JSPファイル名: todo.jsp
最初に基本動作となる画面遷移部分を作成する。
- プロジェクトを作成する。
- [Maven]-[プロジェクトの更新]を実行する。
- [実行]-[maven install]を実行する。
- Tomcatサーバーに作成したプロジェクトを追加する。
- Tomcatサーバーを起動する。
- http://localhost:8080/ToDo/ にアクセスする。
→ 「click to enter」表示でOK - mvc-config.xml を修正して jp.abc パッケージを読み込むようにする。
- ToDoControllerを作成する。
- ToDoControllerで指定したURLにアクセスする。
→ todo.jsp がないのでエラー - todo.jspを作成する。
- ToDoControllerで指定したURLにアクセスする。
→ todo.jsp の内容が表示されればOK - 「登録」ボタンを押す。→ POSTメソッドに未対応なのでエラー
- ToDoControllerにPOSTメソッドを受け付けるメソッドを追加してtodo.jspにリダイレクトする。
- 「登録」ボタンを押す。
→ todo.jspが表示されればOK - 日本語は文字化けするのでweb.xmlを変更して文字化け対策をする。
コントローラは、単純に todo.jsp を表示させるだけのシンプルなものを作成する。
ToDoController.java
package jp.abc; import java.util.List; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class ToDoController { @RequestMapping(value = "/list", method = RequestMethod.GET) public String todo(Model model) { return "todo"; } }
JSPは、ToDo項目を入力するフィールドと登録ボタンを用意する。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>ToDoアプリ</title> </head> <body> <h1>ToDo項目</h1> <form method="post" action="input"> <input type="text" name="item"> <input type="submit" value="登録"> </form> </body> </html>
画面遷移ができるようになったら、エンティティとDAOを用意してToDo項目をデータベースに保存できるようにする。
エンティティ: ToDo
DAO: ToDoDao
DAO実装: ToDoDaoImpl
設定ファイルなどの準備作業を行う。
- pom.xml にデータベースアクセス関連の依存関係を追加する。
- database.properties ファイルを作成する。
- persistence.xmlファイルを作成する。
- application-config.xmlファイルを修正する。
pom.xml にデータベース関連の依存関係を追加する。
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.1</version> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.3.4</version> </dependency> <dependency> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>3.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>3.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>1.3.4.RELEASE</version> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> <exclusion> <groupId>xml-apis</groupId> <artifactId>xml-apis</artifactId> </exclusion> </exclusions> </dependency>
エンティティとDAOとDAO実装を作成して、コントローラからデータベースにアクセスする。
- ToDoエンティティを作成する。
- DAOとDAOの実装を作成する。
- ToDoControllerに、DAOを通してデータベースにアクセスするコードを追加する。
ToDoエンティティでは、ToDoの内容をitem、作成した日時 created、完了日 done のメンバーを持たせる。
ToDo.java
package jp.abc; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class ToDo { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column private long id; @Column private String item; @Column private Date created; @Column private Date done; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getItem() { return item; } public void setItem(String item) { this.item = item; } public Date getCreated() { return created; } public void setCreated(Date created) { this.created = created; } public Date getDone() { return done; } public void setDone(Date done) { this.done = done; } }
DAOインタフェースを作成する。
ToDoDao.java
package jp.abc; import java.util.List; public interface ToDoDao<T> { public List<T> getAll(); public void add(T item); }
DAOの実装を作成する。
ToDoDaoImple.java
package jp.abc; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; public class ToDoDaoImpl implements ToDoDao<ToDo> { private static EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistenceUnit"); public List<ToDo> getAll() { EntityManager manager = factory.createEntityManager(); List<ToDo> list = null; CriteriaBuilder builder = manager.getCriteriaBuilder(); CriteriaQuery<ToDo> q = builder.createQuery(ToDo.class); Root<ToDo> r = q.from(ToDo.class); q.select(r); list = (List<ToDo>)manager.createQuery(q).getResultList(); manager.close(); return list; } public void add(ToDo item) { EntityManager manager = factory.createEntityManager(); EntityTransaction tx = manager.getTransaction(); tx.begin(); manager.persist(item); tx.commit(); manager.close(); } }
コントローラからデータベースにアクセスするコードを追加する。
ToDoController.java
package jp.abc; import java.util.Date; import java.util.List; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @Controller public class ToDoController { @RequestMapping(value = "/list", method = RequestMethod.GET) public String todo(Model model) { ToDoDao<ToDo> dao = new ToDoDaoImpl(); List<ToDo> list = dao.getAll(); model.addAttribute("list", list); return "todo"; } @RequestMapping(value = "/input", method = RequestMethod.POST) public String add(@RequestParam(value = "item") String item, Model model) { ToDo todo = new ToDo(); ToDoDao<ToDo> dao = new ToDoDaoImpl(); todo.setItem(item); todo.setCreated(new Date()); dao.add(todo); return "redirect:/list"; } }
todo.jsp に、ToDo項目のリストを表示するコードを追加する。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>ToDoアプリ</title> </head> <body> <h1>ToDo項目</h1> <form method="post" action="input"> <input type="text" name="item"> <input type="submit" value="登録"> </form> <c:if test="${list != null}"> <form method="post" action="check"> <c:forEach var="t" items="${list}"> <input type="checkbox" name="item" value="${t.id}" />${t.item}<br /> </c:forEach> </form> </c:if> </body> </html>
8月25日
Webアプリ「Shindan」で診断した内容をデータベースに保存する機能を追加する。
まずはDAOを作成する。
ShindanDao.java
package jp.abc; import java.io.Serializable; import java.util.List; public interface ShindanDao<T> extends Serializable { public List<T> getAll(); public void add(Shindan s); }
ShindanDao の実装クラス ShindanDaoImpl を作成する。
今回は、クエリ部分を CriteriaAPI で実装する。
ShindanDaoImpl.java
package jp.abc; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; public class ShindanDaoImpl implements ShindanDao<Shindan> { private static EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistenceUnit"); public List<Shindan> getAll() { EntityManager manager = factory.createEntityManager(); List<Shindan> list = null; CriteriaBuilder builder = manager.getCriteriaBuilder(); CriteriaQuery<Shindan> q = builder.createQuery(Shindan.class); Root<Shindan> r = q.from(Shindan.class); q.select(r); list = (List<Shindan>)manager.createQuery(q).getResultList(); manager.close(); return list; } public void add(Shindan s) { EntityManager manager = factory.createEntityManager(); EntityTransaction tx = manager.getTransaction(); tx.begin(); manager.persist(s); tx.commit(); manager.close(); } }
コントローラに、DAOを通してDBにアクセスするコードを追加する。
入力チェックは、バリデーション機能を使わずに、独自で簡単に実装しておく。
package jp.abc; import java.util.Date; import java.util.List; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @Controller public class ShindanController { @RequestMapping(value = "/input", method = RequestMethod.GET) public String shindan(Model model) { Shindan s = new Shindan(); ShindanDao<Shindan> dao = new ShindanDaoImpl(); List<Shindan> list = dao.getAll(); model.addAttribute("datalist", list); return "shindan"; } @RequestMapping(value = "/input", method = RequestMethod.POST) public String form(@RequestParam(value = "name") String name, @RequestParam(value = "type") String type, Model model) { if (name == null || type == null) { model.addAttribute("message", "入力が不足しています。"); return "shindan"; } model.addAttribute("name", name); model.addAttribute("type", type); String s = name + type; int h = Math.abs(s.hashCode()); h = h % 101; model.addAttribute("percent", h); Shindan shindan = new Shindan(); ShindanDao<Shindan> dao = new ShindanDaoImpl(); shindan.setName(name); shindan.setType(type); shindan.setValue(h); shindan.setTime(new Date()); dao.add(shindan); return "redirect:/input"; } }
JSPに、DBから取得した履歴データを表示するコードを追加する。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>診断アプリ</title> </head> <body> <c:if test="${message != null}"> <p style="color:red;">${message}</p> </c:if> <form method="post" action="input"> <input type="text" name="name">さんの<br> <input type="text" name="type">度を診断します<br> <input type="submit" value="診断"> </form> <div style="margin: 10px 50px;font-size: 20pt;"> <c:if test="${name != null}"> ${name} さんの ${type} 度は、${percent}%です。<br> </c:if> </div> <hr> <c:if test="${datalist != null}"> <table border="1"> <tr><th>名前</th><th>診断内容</th><th>値</th></tr> <c:forEach var="s" items="${datalist}"> <tr> <td>${s.name}</td> <td>${s.type}</td> <td>${s.value} %</td> </tr> </c:forEach> </table> </c:if> </body> </html>
persistence.xml に間違いがあったので修正する。
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" /> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbc.JDBCDriver" /> <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:hsql://localhost/mydata" /> </properties> </persistence-unit> </persistence>
pom.xml の HSQLDB のバージョンも修正する。
<dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.3.4</version> </dependency>
データベースへの接続でエラーが発生する場合は、hsqldb.bat で パラメータを追加する。
hsqldb.bat
cd C:\pleiades45\hsqldb-2.3.4\hsqldb\lib java -cp hsqldb.jar org.hsqldb.Server --database.0 db/mydata --dbname.0 mydata
DB登録までできた人は、診断結果の値でソートする機能を追加してみること。
8月21日
復習としてSpringMVCを使ったWebアプリケーションを作成する。
ServletとJSPを使って作成した診断アプリを、SpringMVCを使って作成してみよう。
プロジェクト名: Shindan
5月29日の内容を参考にしてプロジェクトを作成する。
6月2日の内容を参考にして mvc-config.xml を編集する。
コントローラを作成する。
コントローラ: ShindanController.java
コントローラから shindan.jsp を表示させるコードを書く。
package jp.abc; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class ShindanController { @RequestMapping(value = "/input", method = RequestMethod.GET) public String shindan(Model model) { return "shindan"; } }
この状態で、http://localhost:8080/Shindan/input にアクセスすると404エラーが発生する。
エラーで WEB-INF/view/shindan.jsp が見つからないといわれるので、shindan.jsp を作成する。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>診断アプリ</title> </head> <body> <form method="post" action="ShindanServlet"> <input type="text" name="name">さんの<br> <input type="text" name="type">度を診断します<br> <input type="submit" value="診断"> </form> </body> </html>
コントローラにPOSTメソッドのリクエストを受け取るためのメソッドを追加する。
package jp.abc; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class ShindanController { @RequestMapping(value = "/input", method = RequestMethod.GET) public String shindan(Model model) { return "shindan"; } @RequestMapping(value = "/input", method = RequestMethod.POST) public String form(Model model) { return "shindan"; } }
フォームのあとに、「○○さんの○○度はXX%です。」と表示する部分を追加する。
nameにデータがあるときだけ表示するように、JSTLの c:if タグで条件を指定する。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>診断アプリ</title> </head> <body> <form method="post" action="input"> <input type="text" name="name">さんの<br> <input type="text" name="type">度を診断します<br> <input type="submit" value="診断"> </form> <c:if test="${name != null}"> ${name} さんの ${type} 度は、${percent}%です。<br> </c:if> </body> </html>
名前と診断内容を表示するために、リクエストパラメータから値を取得する。
そのために、POSTメソッドを受け取る form メソッドに、@RequestParam アノテーションを追加した引数を追加する。
@RequestMapping(value = "/input", method = RequestMethod.POST) public String form(@RequestParam(value = "name") String name, @RequestParam(value = "type") String type, Model model) { model.addAttribute("name", name); model.addAttribute("type", type); return "shindan"; }
これで、名前と診断内容は表示できるようになる。
次に診断結果を表示できるようにする。
以前の ShindanServlet を参考にコードを追加する。
@RequestMapping(value = "/input", method = RequestMethod.POST) public String form(@RequestParam(value = "name") String name, @RequestParam(value = "type") String type, Model model) { model.addAttribute("name", name); model.addAttribute("type", type); String s = name + type; int h = Math.abs(s.hashCode()); h = h % 101; model.addAttribute("percent", h); return "shindan"; }
これまでの診断結果をデータベースに保存するようにしてみよう!
そのためには、pom.xml にデータベース関連のライブラリを追加する必要があるので、
6月19日の内容を参照して pom.xml を変更する。
さらに、database.properties ファイルと persistence.xml を追加する。
これも6月19日を参照すればOK。
また、6月19日の記録を参照し、application-config.xml ファイルも変更する。
ここまでやれば、データベースを利用する準備が完了。
デスクトップに作成した hsqldb.bat をダブルクリックして HSQLDB を起動する。
Shindan エンティティの作成
jp.abc パッケージを右クリックして、[新規]-[クラス]を選択する。
クラス名は「Shindan」とする。
package jp.abc; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Shindan { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column private long id; @Column private Date time; @Column private String name; @Column private String type; @Column private int value; public long getId() { return id; } public void setId(long id) { this.id = id; } public Date getTime() { return time; } public void setTime(Date time) { this.time = time; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } }
次回予定
- DAOインタフェースの作成
- DAO実装クラスの作成
- コントローラの修正
- JSPの修正
以上を行えば、データベースにアクセスできるようになるはず。
7月3日
試験範囲について
- Eclipseを使った開発
- ストリームを利用したファイル入出力とURLからのデータ取得
- FizzBuzz問題を例題としてTDDによる開発手法の学習
- TDDBC大阪の例題の自動販売機を実装する
- JavaによるWebアプリケーションの基礎(Servlet/JSP/JSTL)
- JSPによる入力フォームとServletでのデータの受け取り
サービスとリポジトリは便利ですが、使わなくてもWebアプリケーションを作れるので、今回はパスします。
興味がある人は自分でやってみてください。
エンティティの連携
MsgDataエンティティを作成する。
package jp.abc; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.Table; import javax.validation.constraints.NotNull; import org.hibernate.validator.constraints.NotEmpty; @Entity @Table(name = "msgdata") public class MsgData { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column @NotNull private long id; @Column private String title; @Column(nullable = false) @NotEmpty private String message; @ManyToOne private MyData mydata; public MsgData() { super(); mydata = new MyData(); } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public MyData getMydata() { return mydata; } public void setMydata(MyData mydata) { this.mydata = mydata; } }
MyDataを修正する
MyDataに、MsgDataへの参照を追加する。
インスタンス変数を追加したら、Eclipseの[ソース]-[getterおよびsetterの生成]でアクセサメソッドを自動生成する。
package jp.abc; import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; @Entity @Table(name = "mydata") public class MyData { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column private long id; @Column(length = 50, nullable = false) private String name; @Column(length = 200, nullable = true) private String mail; @Column(nullable = true) private Integer age; @Column(nullable = true) private String memo; @OneToMany(cascade = CascadeType.ALL) @Column(nullable = true) private List<MsgData> msgdatas; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getMail() { return mail; } public void setMail(String mail) { this.mail = mail; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getMemo() { return memo; } public void setMemo(String memo) { this.memo = memo; } public List<MsgData> getMsgdatas() { return msgdatas; } public void setMsgdatas(List<MsgData> msgdatas) { this.msgdatas = msgdatas; } }
MsgDataDaoの用意
MsgDataDaoインタフェースを作成する。
package jp.abc; import java.io.Serializable; import java.util.List; public interface MsgDataDao<T> extends Serializable { public List<T> getAll(); public T findById(long id); public void add(T data); public void update(T data); public void delete(T data); public void delete(long id); }
MsgDataDaoImplクラスを作成する。
テキストの通りに各メソッドに @Override アノテーションをつけるとコンパイルエラーになる。
これは、プロジェクトの設定で Javaコンパイラのバージョンが1.5になっているため。
1.6に変更すれば @Override アノテーションのコンパイルエラーは消える。
同時に、プロジェクト・ファセットの設定でバージョンを合わせる必要がある。
package jp.abc; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; import javax.persistence.Query; public class MsgDataDaoImpl implements MsgDataDao<MsgData> { private static EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistenceUnit"); @Override public List<MsgData> getAll() { EntityManager manager = factory.createEntityManager(); List<MsgData> list = null; Query q = manager.createQuery("from MsgData"); list = q.getResultList(); manager.close(); return list; } @Override public MsgData findById(long id) { EntityManager manager = factory.createEntityManager(); Query q = manager.createQuery("from MsgData where id = " + id); return (MsgData)q.getSingleResult(); } @Override public void add(MsgData data) { EntityManager manager = factory.createEntityManager(); EntityTransaction tx = manager.getTransaction(); tx.begin(); manager.persist(data); tx.commit(); manager.close(); } @Override public void update(MsgData data) { EntityManager manager = factory.createEntityManager(); EntityTransaction tx = manager.getTransaction(); tx.begin(); manager.merge(data); tx.commit(); manager.close(); } @Override public void delete(MsgData data) { EntityManager manager = factory.createEntityManager(); EntityTransaction tx = manager.getTransaction(); tx.begin(); MsgData entity = manager.merge(data); manager.remove(entity); tx.commit(); manager.close(); } @Override public void delete(long id) { delete(findById(id)); } }
ビューテンプレートの用意
[新規]-[JSPファイル]でJSPファイルを作成する。
ファイル名はテキストの通り「showMsgData.jsp」とする。
<!DOCTYPE html> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>${title}</title> <style> h1 { font-size: 16pt; background-color: #ccccff; padding: 3px;} p { color: #000066; } </style> </head> <body> <h1>${title}</h1> <p>${message}</p> <table> <form:form modelAttribute="msgdata"> <tr> <td></td> <td><form:errors path="*" /></td> </tr> <tr> <td><form:label path="title">タイトル</form:label></td> <td><form:input path="title" size="20" /></td> </tr> <tr> <td><form:label path="message">メッセージ</form:label></td> <td><form:textarea path="message" cols="20" rows="5" /></td> </tr> <tr> <td><form:label path="mydata">MYDATA_ID</form:label></td> <td><form:input path="mydata" size="20" /></td> </tr> <tr> <td></td> <td><input type="submit" /></td> </tr> </form:form> </table> <hr /> <c:if test="${datalist != null}"> <table> <tr> <th>ID</th><th>投稿者</th><th>タイトル</th><th>メッセージ</th> </tr> <c:forEach var="obj" items="${datalist}" varStatus="status"> <tr> <td><c:out value="${obj.id}" /></td> <td><c:out value="${obj.mydata.name}" /></td> <td><c:out value="${obj.title}" /></td> <td><c:out value="${obj.message}" /></td> </tr> </c:forEach> </table> </c:if> </body> </html>
コントローラに新しいURL用のメソッドを追加する
@RequestMapping(value = "/msg", method = RequestMethod.GET) public String msg(Model model) { model.addAttribute("title", "Sample"); model.addAttribute("message", "MsgDataのサンプルです。"); MsgData msgdata = new MsgData(); model.addAttribute("msgdata", msgdata); MsgDataDao<MsgData> dao = new MsgDataDaoImpl(); List<MsgData> list = dao.getAll(); model.addAttribute("datalist", list); return "showMsgData"; } @RequestMapping(value = "/msg", method = RequestMethod.POST) public String msgform(@Valid @ModelAttribute MsgData msgdata, Errors result, Model model) { System.out.println("msgform: " + msgdata.getMydata()); if (result.hasErrors()) { model.addAttribute("title", "Sample [ERROR]"); model.addAttribute("message", "値を再チェックしてください。"); return "showMsgData"; } MsgDataDao<MsgData> dao = new MsgDataDaoImpl(); dao.add(msgdata); return "redirect:/msg"; }
POSTするとエラーが発生する。
フォームではMYDATA_IDは単なる文字としての値なので、これをMyDataのインスタンスに変換するための処理が必要になる。
そのために、PropertyEditor を継承するクラス MyDataPropertyEditor を用意する。
package jp.abc; import java.beans.PropertyEditorSupport; public class MyDataPropertyEditor extends PropertyEditorSupport { public String getAsText() { MyData value = (MyData)getValue(); System.out.println("getAsText: " + value); if (value == null) { return ""; } else { return "" + value.getId(); } } public void setAsText(String value) { MyDataDao<MyData> dao = new MyDataDaoImpl(); MyData mydata = (MyData)dao.findById(Long.parseLong(value)); System.out.println("setAsText: " + mydata); setValue(mydata); } }
作成した MyDataPropertyEditor を使用するためのコードをコントローラに追加する。
@Controller public class MyDataConroller { @InitBinder protected void initBinder(HttpServletRequest req, ServletRequestDataBinder binder) throws Exception { binder.registerCustomEditor(MyData.class, new MyDataPropertyEditor()); }