3. チュートリアル(Todoアプリケーション)¶
Caution
本バージョンの内容は既に古くなっています。最新のガイドラインはこちらからご参照ください。
3.1. はじめに¶
3.1.1. このチュートリアルで学ぶこと¶
- TERASOLUNA Global Frameworkによる基本的なアプリケーションの開発方法およびEclipseプロジェクトの構築方法
- TERASOLUNA Global Frameworkの アプリケーションのレイヤ化 に従った開発方法
3.1.2. 対象読者¶
- SpringのDIやAOPに関する基礎的な知識がある
- Servlet/JSPを使用してWebアプリケーションを開発したことがある
- SQLに関する知識がある
3.1.3. 検証環境¶
このチュートリアルは以下の環境で動作確認している。他の環境で実施する際は本書をベースに適宜読み替えて設定していくこと。
種別 | 名前 |
---|---|
OS | Windows7 64bit |
JVM | Java 1.6 |
IDE | Spring Tool Suite Version: 3.2.0.RELEASE, Build Id: 201303060821 (以下STS) Build Maven 3.0.4 (STS付属) |
Application Server | VMWare vFabric tc Server Developer Edition v2.8 (STS付属) |
Web Browser | Google Chrome 27.0.1453.94 m |
3.2. 作成するアプリケーションの説明¶
3.2.2. アプリケーションの業務要件¶
ルールID | 説明 |
---|---|
B01 | 未完のTODOは5件までしか登録できない |
B02 | 完了済みのTODOは完了できない |
Note
本要件は学習のためのもので、現実的なTODO管理アプリケーションとしては適切ではない。
3.2.3. アプリケーションの画面遷移¶
項番 | プロセス名 | HTTPメソッド | URL | 説明 |
---|---|---|---|---|
1 | Show all TODO | GET | /todo/list | |
2 | Create TODO | POST | /todo/create | 作成完了後1へリダイレクト |
3 | Finish TODO | POST | /todo/finish | 作成完了後1へリダイレクト |
4 | Delete TODO | POST | /todo/delete | 作成完了後1へリダイレクト |
3.2.3.1. Show all TODO¶
- TODOを全件表示する
- 未完了のTODOに対しては”Finish”と”Delete”用のボタンが付く
- 完了のTODOは打ち消し線で装飾する
- TODOの件名のみ
3.2.3.2. Create TODO¶
- フォームから送信されたTODOを保存する
- TODOの件名は1文字以上30文字以下であること
- アプリケーションの業務要件 のB01を満たさない場合はエラーコードE001でビジネス例外をスローする
3.2.3.3. Finish TODO¶
- フォームから送信されたtodoIdに対応するTODOを完了済みにする
- アプリケーションの業務要件 のB02を満たさない場合はエラーコードE002でビジネス例外をスローする
- 該当するTODOが存在しない場合はエラーコードE404でビジネス例外をスローする
3.2.3.4. Delete TODO¶
- フォームから送信されたtodoIdに対応するTODOを削除する
- 該当するTODOが存在しない場合はエラーコードE404でビジネス例外をスローする
3.2.4. エラーメッセージ一覧¶
エラーコード | メッセージ | 置換パラメータ |
---|---|---|
E001 | [E001] The count of un-finished Todo must not be over {0}. | {0}… max unfinished count |
E002 | [E002] The requested Todo is already finished. (id={0}) | {0}… todoId |
E404 | [E404] The requested Todo is not found. (id={0}) | {0}… todoId |
3.3. 環境構築¶
3.3.1. プロジェクトの作成¶
「File」->「Other」->「Maven」->「Maven Project」を選択して「Next」。
「Create a simple project」にチェックを入れて「Next」。
Group Id: | org.terasoluna.tutorial |
---|---|
Artifact Id: | todo |
Packaging: | war |
で「Finish」
以下のようなプロジェクトが作成される。
3.3.2. Mavenの設定¶
pom.xmlを以下のように変更する。 Mavenの知識がない場合は、pom.xmlをコピーするだけで、解説は読み飛ばしてよい。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.terasoluna.tutorial</groupId>
<artifactId>todo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<!-- (1) -->
<parent>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-parent</artifactId>
<version>1.0.0.RELEASE</version>
</parent>
<!-- (2) -->
<repositories>
<repository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>terasoluna-gfw-releases</id>
<url>http://repo.terasoluna.org/nexus/content/repositories/terasoluna-gfw-releases/</url>
</repository>
<repository>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>terasoluna-gfw-snapshots</id>
<url>http://repo.terasoluna.org/nexus/content/repositories/terasoluna-gfw-snapshots/</url>
</repository>
<repository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>terasoluna-gfw-3rdparty</id>
<url>http://repo.terasoluna.org/nexus/content/repositories/terasoluna-gfw-3rdparty/</url>
</repository>
</repositories>
<dependencies>
<!-- (3) -->
<!-- TERASOLUNA -->
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-web</artifactId>
</dependency>
<!-- (4) -->
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-security-web</artifactId>
</dependency>
<!-- (5) -->
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-recommended-dependencies</artifactId>
<type>pom</type>
</dependency>
<!-- (6) -->
<!-- Servlet API/ JSP API -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>7.0.40</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jsp-api</artifactId>
<version>7.0.40</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
pom.xmlを編集した後、プロジェクト名を右クリックし、「Maven」->「Update Project」をクリックし、
「OK」ボタンをクリックする。
以下のように”JRE System Library”のバージョンが”[JavaSE-1.6]”になっていることを確認する。
Note
JDKのバージョンを7に変更したい場合は、pom.xmlの
<properties>
に<java-version>1.7</java-version>
を設定した後、 「Update Project」を実施すること。<project> <!-- omitted --> <properties> <java-version>1.7</java-version> </properties> </project>
Mavenの知識がある場合は、以下の解説を確認すること。
項番 | 説明 |
---|---|
(1)
|
TERASOLUNA Global Frameworkの親pomファイルを指定する。
これにより、terasoluna-parentで定義されているライブラリは、versionを指定しなくても、dependencyに追加することができる。
|
(2)
|
TERASOLUNA Global Frameworkを使うためのMavenレポジトリのURLを指定する。
|
(3)
|
TERASOLUNA Global Frameworkの共通ライブラリ(Web用)をdependencyに追加する。
|
(4)
|
TERASOLUNA Global Frameworkの共通ライブラリ(セキュリティWeb用)をdependencyに追加する。
|
(5)
|
TERASOLUNA Global Frameworkで推奨されるライブラリ群を追加する。
terasoluna-gfw-recommended-dependenciesはただのpomファイルであるため
<type>pom</type> を記述する必要がある。 |
(6)
|
Servlet/JSP APIをdependencyに追加する。Servlet3に対応する必要がある。
これらはscope=provided(本来APサーバーから提供される)であり、warには含まれないが、eclipse上でコンパイルするためには明示的にdependencyに追加する必要がある。
(尚、dependency名がtomcat-xxxとなっているが、内包するクラスのパッケージはjavax.servletであるためtomcatに依存しているわけではない。)
|
Note
Proxyサーバーを介してインターネットアクセスする必要がある場合は、 <HOME>/.m2/settings.xmlに以下のような設定を行う。 (Windows7の場合C:\Users\<YourName>.m2settings.xml)
<settings> <proxies> <proxy> <active>true</active> <protocol>[Proxy Server Protocol (http)]</protocol> <port>[Proxy Server Port]</port> <host>[Proxy Server Host]</host> <username>[Username]</username> <password>[Password]</password> </proxy> </proxies> </settings>
3.3.3. プロジェクト構成¶
今後作成していくプロジェクトの構成について、以下に示す。
src
└main
├java
│ └todo
│ ├ app ... アプリケーション層を格納
│ │ └todo ... todo管理業務に関わるクラスを格納
│ └domain ... ドメイン層を格納
│ ├model ... Domain Objectを格納
│ ├repository ... Repositoryを格納
│ │ └todo ... Todo用Repository
│ └service ... Serviceを格納
│ └todo ... TODO業務Service
├resources
│ └META-INF
│ └spring ... spring関連の設定ファイルを格納
└wepapp
└WEB-INF
└views ... jspを格納
順番に作成していくので、最初に上記構成を用意する必要はない。
Note
前節の「プロジェクト構成」 ではマルチプロジェクトにすることを推奨していたが、 本チュートリアルでは、学習容易性を重視しているためシングルプロジェクト構成にしている。ただし、実プロジェクトで適用する場合は、 マルチプロジェクト構成を強く推奨する。
3.3.4. 設定ファイルの作成¶
3.3.4.1. web.xmlの設定¶
src/main/webapp/WEB-INF/web.xmlを作成して、サーブレットやフィルタの定義を行う。 WEB-INFフォルダは「New」->「Folder」で新規作成すること。
「New」->「File」でweb.xmlを作成し、
内容は以下のように記述する。
<?xml version="1.0" encoding="UTF-8"?>
<!-- (1) -->
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<!-- (2) -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- Root ApplicationContext -->
<param-value>
classpath*:META-INF/spring/applicationContext.xml
</param-value>
</context-param>
<!-- (3) -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- (4) -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- ApplicationContext for Spring MVC -->
<param-value>classpath*:META-INF/spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- (5) -->
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>false</el-ignored>
<page-encoding>UTF-8</page-encoding>
<scripting-invalid>false</scripting-invalid>
<include-prelude>/WEB-INF/views/common/include.jsp</include-prelude>
</jsp-property-group>
</jsp-config>
</web-app>
項番 | 説明 |
---|---|
(1)
|
Servlet3.0を使用するための宣言。
|
(2)
|
ContextLoaderListener の定義。このリスナーが作成する ApplicationContext がルートコンテキストとなる。Bean定義ファイルのパスをclasspath直下のMETA-INF/spring/applicationContext.xmlとする。
|
(3)
|
CharacterEncodingFilter の定義。リクエストとレスポンスの文字コードをUTF-8にするための設定。 |
(4)
|
Spring MVCのエントリポイントとなるDispatcherServletの定義。
Spring MVCで使用するBean定義ファイルのパスをclasspath直下のMETA-INF/spring/spring-mvc.xmlとする。
ここで作成される
ApplicationContext は(2)で作成される ApplicatnionContext の子となる。 |
(5)
|
JSP共通でincludeするJSPの定義。任意のJSP(*.jsp)に対して、/WEB-INF/views/common/include.jspをincludeする。
|
3.3.4.2. 共通JSPの設定¶
src/main/webapp/WEB-INF/views/common/include.jspに各JSP共通でincludeする内容を記述する。taglibの定義を共通的に行う。 views/commonフォルダ、include.jspファイルを作成し、以下のように記述する。
<%@ page session="false"%>
<!-- (1) -->
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!-- (2) -->
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<!-- (3) -->
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>
<!-- (4) -->
<%@ taglib uri="http://terasoluna.org/functions" prefix="f"%>
<%@ taglib uri="http://terasoluna.org/tags" prefix="t"%>
項番 | 説明 |
---|---|
(1)
|
標準タグライブラリを定義する。
|
(2)
|
Spring MVC用タグライブラリを定義する。
|
(3)
|
Spring Security用タグライブラリを定義する。(ただし本チュートリアルでは使用しない。)
|
(4)
|
共通ライブラリで提供されている、EL関数、タグライブラリを定義する。
|
3.3.4.3. Bean定義ファイルの設定¶
Bean定義ファイルは、以下4種類のファイルを作成する。
- applicationContext.xml
- todo-domain.xml
- todo-infra.xml
- spring-mvc.xml
上から順に説明する。
3.3.4.3.1. applicationContext.xml¶
src/main/resources/META-INF/spring/applicationContext.xmlに、Todoアプリ全体に関わる設定を行う。
META-INF/springフォルダを作成し、「New」->「Spring Bean Configuration File」でapplicationContext.xmlを作成する。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- (1) -->
<import resource="classpath:/META-INF/spring/todo-domain.xml" />
<!-- (2) -->
<context:property-placeholder
location="classpath*:/META-INF/spring/*.properties" />
<!-- (3) -->
<bean class="org.dozer.spring.DozerBeanMapperFactoryBean">
<property name="mappingFiles"
value="classpath*:/META-INF/dozer/**/*-mapping.xml" />
</bean>
</beans>
項番 | 説明 |
---|---|
(1)
|
次に説明する、ドメイン層に関するBean定義ファイルをimportする。
|
(2)
|
プロパティファイルの読み込み設定を行う。
src/main/resources/META-INF/spring直下の任意のプロパティファイルを読み込む。
この設定により、プロパティファイルの値をBean定義ファイル内で${propertyName}形式で埋め込んだり、Javaクラスに@Value(“${propertyName}”)でインジェクションすることができる。
|
(3)
|
Bean変換用ライブラリDozerのMapperを定義する。
(今回は使用しないが、マッピング用XMLファイルを定義する場合はsrc/main/resources/META-INF/dozer/xxx-mapping.xmlという形式でマッピングファイルを作成すること。
マッピングファイルに関して Dozerマニュアル を参照されたい。)
|
3.3.4.3.2. todo-domain.xml¶
src/main/resources/META-INF/spring/todo-domain.xmlに、ドメイン層に関わる設定を行う。
META-INF/spring直下において、「New」->「Spring Bean Configuration File」でtodo-domain.xmlを作成する。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- (1) -->
<import resource="classpath:META-INF/spring/todo-infra.xml"/>
<!-- (2) -->
<context:component-scan base-package="todo.domain" />
</beans>
項番 | 説明 |
---|---|
(1)
|
次に説明する、インフラストラクチャ層に関するBean定義ファイルをimportする。
|
(2)
|
ドメイン層のクラスを管理するtodo.domainパッケージ配下をcomponent-scan対象とする。
これにより、todo.domainパッケージ配下のクラスに
@Repository , @Service , @Controller , @Component などのアノテーションを付けることで、DI対象にできる。 |
3.3.4.3.3. todo-infra.xml¶
src/main/resources/META-INF/spring/todo-infra.xmlに、インフラストラクチャ層に関するBean定義を行う。 ここではDBの設定などを行うが、本節ではDBを使用しないため、以下のように空定義で良い。次節でBean定義を行う。
META-INF/spring直下において、「New」->「Spring Bean Configuration File」でtodo-infra.xmlを作成する。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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.xsd">
</beans>
Note
todo-domain.xml, todo-infra.xmlの内容もすべてapplicationContext.xmlに記述すればよいように思えるかもしれないが、 役割(層)ごとにファイルを分割することを推奨する。どこに何が定義されているか想像しやすく、メンテナンス性が向上するからである。 今回のチュートリアルのような小さなアプリケーションでは効果がない。しかし、アプリケーションの規模が大きくなるにつれ、効果が大きくなる。
3.3.4.3.4. spring-mvc.xml¶
src/main/resources/META-INF/spring/spring-mvc.xmlに、Spring MVCに関する定義を行う。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- (1) -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- (2) -->
<context:component-scan base-package="todo.app" />
<!-- (3) -->
<mvc:resources mapping="/resources/**"
location="/resources/,classpath:META-INF/resources/"
cache-period="#{60 * 60}" />
<mvc:interceptors>
<!-- (4) -->
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<bean
class="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
<!-- (5) -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
項番 | 説明 |
---|---|
(1)
|
Spring MVCのアノテーションベースのデフォルト設定を行う。
|
(2)
|
アプリケーション層のクラスを管理するtodo.appパッケージ配下をcomponent-scan対象とする。
|
(3)
|
静的リソース(css, images, jsなど)アクセスのための設定を行う。
mapping属性にURLのパスを、location属性に物理的なパスの設定を行う。
この設定の場合<contextPath>/rerources/css/styles.cssに対してリクエストが来た場合、WEB-INF/resources/css/styles.cssを探し、見つからなければクラスパス上(src/main/resourcesやjar内)のresources/css/style.cssを探す。
WEB-INF/resources/css/styles.cssが見つからなければ、404エラーを返す。
ここではcache-period属性で静的リソースのキャッシュ時間(3600秒=60分)も設定している。
尚、本チュートリアルでは静的リソースは使用しない。
|
(4)
|
コントローラ処理のTraceログを出力するインターセプタを設定する。/resources以下を除く任意のパスに適用されるように設定する。
|
(5)
|
ViewResolverの設定を行う。この設定により、例えばコントローラからview名”hello”が返却された場合には/WEB-INF/views/hello.jspが実行される。
|
3.3.4.4. logback.xmlの設定¶
src/main/resources/logback.xmlに、logbackによるログの出力設定を行う。
src/main/resources/直下において、「New」->「File」でlogback.xmlを作成する。
<!DOCTYPE logback>
<configuration>
<!-- (1) -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss} [%thread] [%-5level] [%-48logger{48}] - %msg%n]]></pattern>
</encoder>
</appender>
<!-- Application Loggers -->
<!-- (2) -->
<logger name="todo">
<level value="debug" />
</logger>
<!-- TERASOLUNA -->
<!-- (3) -->
<logger name="org.terasoluna.gfw">
<level value="info" />
</logger>
<!-- (4) -->
<logger name="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor">
<level value="trace" />
</logger>
<!-- 3rdparty Loggers -->
<!-- (5) -->
<logger name="org.springframework">
<level value="warn" />
</logger>
<!-- (6) -->
<logger name="org.springframework.web.servlet">
<level value="info" />
</logger>
<!-- (7) -->
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
項番 | 説明 |
---|---|
(1)
|
標準出力でログを出力するアペンダを設定する。
|
(2)
|
todoパッケージ以下はdebugレベル以上を出力するように設定する。
|
(3)
|
共通ライブラリのログレベルをinfoにする。
|
(4)
|
spring-mvc.xmlに設定した
TraceLoggingInterceptor に出力されるようにtraceレベルで設定する。 |
(5)
|
Springframeworkのログはwarnレベル以上を出力するように設定する。
|
(6)
|
Springframeworkのログの中でもorg.springframework.web.servlet以下は開発中に有益なログを出力するためinfoレベル以上で設定する。
|
(7)
|
デフォルトはwarnレベル以上を出力するように設定する。
|
3.3.5. 動作確認¶
Todoアプリケーションの開発を始める前に、SpringMVCのHelloWorldアプリケーションを作成して、動作確認を行う。「New」->「Class」で
Package: | todo.app.hello |
---|---|
Name: | HelloController |
でtodo.app.hello.HelloControllerを作成する。
HelloControllerを以下のように編集する。
package todo.app.hello;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
// (1)
@Controller
public class HelloController {
// (2)
private static final Logger logger = LoggerFactory
.getLogger(HelloController.class);
// (3)
@RequestMapping("/")
public String hello(Model model) {
Date now = new Date();
// (4)
logger.debug("hello {}", now);
// (5)
model.addAttribute("now", now);
// (6)
return "hello";
}
}
項番 | 説明 |
---|---|
(1)
|
Controllerとしてcomponent-scanの対象とするため、クラスレベルに
@Controller アノテーションをつける。 |
(2)
|
ロガーの生成を行う。ロガーの実装はlogbackのものであるが、APIはSLF4Jのものであるため、
org.slf4j.Logger を使用すること。 |
(3)
|
@RequestMapping で”/”(ルート)へのアクセスに対するメソッドのマッピングを設定する。 |
(4)
|
debugログを出力する。”{}”はプレースホルダである。
|
(5)
|
画面へ日付を渡すためにModelに”now”という名前でDateオブジェクトを追加する。
|
(6)
|
view名としてhelloを返す。ViewResolverの設定により、WEB-INF/views/hello.jspが出力される。
|
次にview(jsp)を作成する。src/main/webapp/WEB-INF/views/hello.jspを作成して、以下のように記述する。
<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
<p>
Today is
<!-- (1) -->
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd HH:mm:ss" />
</p>
</body>
</html>
項番 | 説明 |
---|---|
(1)
|
Controllerから渡された”now”を表示する。ここでは
<fmt:formatDate> タグを用いて日付フォーマットを行っている。 |
パッケージプロジェクト名”todo”を右クリックして「Run As」->「Run on Server」
実行したいAPサーバー(ここではVMWare vFabric tc Server Developer Edition v2.8)を選び 「Next」をクリック
todoが「Configured」に含まれていることを確認して「Finish」をクリックしてサーバーを起動する。
起動すると以下のようなログが出力される。”/”というパスに対して todo.app.hello.HelloController
のhelloメソッドがマッピングされていることが分かる。
2013-06-14 14:26:54 [localhost-startStop-1] [WARN ] [org.dozer.config.GlobalSettings ] - Dozer configuration file not found: dozer.properties. Using defaults for all Dozer global properties.
2013-06-14 14:26:54 [localhost-startStop-1] [INFO ] [o.springframework.web.servlet.DispatcherServlet ] - FrameworkServlet 'appServlet': initialization started
2013-06-14 14:26:54 [localhost-startStop-1] [INFO ] [o.s.w.s.m.m.a.RequestMappingHandlerMapping ] - Mapped "{[/],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String todo.app.hello.HelloController.hello(org.springframework.ui.Model)
2013-06-14 14:26:55 [localhost-startStop-1] [INFO ] [o.s.web.servlet.handler.SimpleUrlHandlerMapping ] - Mapped URL path [/resources/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
2013-06-14 14:26:55 [localhost-startStop-1] [INFO ] [o.springframework.web.servlet.DispatcherServlet ] - FrameworkServlet 'appServlet': initialization completed in 986 ms
Note
一行目のWARNログは無視しても良い。抑止したい場合はsrc/main/resourcesに空のdozer.propertiesを作成すること。
ブラウザでhttp://localhost:8080/todo にアクセスすると、以下のように表示される。
コンソールを見ると TraceLoggingInterceptor
によるTRACEログとControllerで実装したdebugログが出力されていることがわかる。
2013-06-14 15:40:59 [tomcat-http--3] [TRACE] [o.t.gfw.web.logging.TraceLoggingInterceptor ] - [START CONTROLLER] HelloController.hello(Model)
2013-06-14 15:40:59 [tomcat-http--3] [DEBUG] [todo.app.hello.HelloController ] - hello Fri Jun 14 15:40:59 JST 2013
2013-06-14 15:40:59 [tomcat-http--3] [TRACE] [o.t.gfw.web.logging.TraceLoggingInterceptor ] - [END CONTROLLER ] HelloController.hello(Model)-> view=hello, model={now=Fri Jun 14 15:40:59 JST 2013}
2013-06-14 15:40:59 [tomcat-http--3] [TRACE] [o.t.gfw.web.logging.TraceLoggingInterceptor ] - [HANDLING TIME ] HelloController.hello(Model)-> 15,043,704 ns
Note
TraceLoggingInterceptor
はControllerの開始、終了でログを出力する。終了時にはViewとModelの情報および処理時間を出力する。
ログの確認後は、HelloController, hello.jspの2ファイルを削除しても構わない。
3.4. Todoアプリケーションの作成¶
- ドメイン層(+ インフラストラクチャ層)
- Domain Object作成
- Repository作成
- Service作成
- アプリケーション層
- Controller作成
- Form作成
- View作成
なお、本節では、Todoの保存にDBを使用しない。DBを使用するRepositoryの作成は、インフラストラクチャ層の変更で行う。
3.4.1. ドメイン層の作成¶
3.4.1.1. Domain Objectの作成¶
ドメインオブジェクトに必要なプロパティは、
- ID
- タイトル
- 完了フラグ
- 作成日
である。
以下の、Domainオブジェクトを作成する。 FQCNは、todo.domain.model.Todoとする。JavaBeanとして実装すればよい。
Package: | todo.domain.model |
---|---|
Name: | Todo |
Interfaces: | java.io.Serializable |
package todo.domain.model;
import java.io.Serializable;
import java.util.Date;
public class Todo implements Serializable {
private static final long serialVersionUID = 1L;
private String todoId;
private String todoTitle;
private boolean finished;
private Date createdAt;
public String getTodoId() {
return todoId;
}
public void setTodoId(String todoId) {
this.todoId = todoId;
}
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
public boolean isFinished() {
return finished;
}
public void setFinished(boolean finished) {
this.finished = finished;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
}
3.4.1.2. Repositoryの作成¶
今回のアプリケーションで、必要なTODOオブジェクトに対するCRUD系操作は、
- TODOの1件取得
- TODOの全件取得
- TODOの1件削除
- TODOの1件更新
- 完了済みTODO件数の取得
である。これらの操作を定義するインタフェースTodoRepositoryを作成する。 FQCNは、todo.domain.repository.todo.TodoRepositoryとする。
package todo.domain.repository.todo;
import java.util.Collection;
import todo.domain.model.Todo;
public interface TodoRepository {
Todo findOne(String todoId);
Collection<Todo> findAll();
Todo save(Todo todo);
void delete(Todo todo);
long countByFinished(boolean finished);
}
Note
ここで、TodoRepositoryの汎用性を上げるため、「完了済み件数の取得」ではなく、「完了状態がxである件数」を取得するメソッドとして定義した。
3.4.1.3. RepositoryImplの作成(インフラストラクチャ層)¶
@Repository
アノテーションをつけること。package todo.domain.repository.todo;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Repository;
import todo.domain.model.Todo;
@Repository // (1)
public class TodoRepositoryImpl implements TodoRepository {
private static final Map<String, Todo> TODO_MAP = new ConcurrentHashMap<String, Todo>();
@Override
public Todo findOne(String todoId) {
return TODO_MAP.get(todoId);
}
@Override
public Collection<Todo> findAll() {
return TODO_MAP.values();
}
@Override
public Todo save(Todo todo) {
return TODO_MAP.put(todo.getTodoId(), todo);
}
@Override
public void delete(Todo todo) {
TODO_MAP.remove(todo.getTodoId());
}
@Override
public long countByFinished(boolean finished) {
long count = 0;
for (Map.Entry<String, Todo> e : TODO_MAP.entrySet()) {
Todo todo = e.getValue();
if (finished == todo.isFinished()) {
count++;
}
}
return count;
}
}
項番 | 説明 |
---|---|
(1)
|
Repositoryとして、component-scan対象とするため、クラスレベルに
@Repository アノテーションをつける。 |
Repositoryは、業務ルールを含まないので、保存先(この場合は、Map)への出し入れに終始することに注意する。
Note
完全に層別パッケージを分けるのであれば、インフラストラクチャ層のクラスは、todo.infrastructure以下に作成した方が良い。
ただし、通常のプロジェクトでは、インフラストラクチャ層が変更されることを前提としていない(そのような前提で進めるプロジェクトは、少ない)。 そこで、作業効率向上のために、ドメイン層のrepositotyと同じ階層に、RepositoryImplを作成しても良い。
3.4.1.4. Serviceの作成¶
業務処理を実装する。必要な処理は、
- Todoの全件取得
- Todoの新規作成
- Todoの完了
- Todoの削除
である。まずは、TodoServiceインタフェースを作成して、これらを定義する。 FQCNは、todo.domain.serivce.todo.TodoServiceとする。
package todo.domain.service.todo;
import java.util.Collection;
import todo.domain.model.Todo;
public interface TodoService {
Collection<Todo> findAll();
Todo create(Todo todo);
Todo finish(String todoId);
void delete(String todoId);
}
必要な処理と、実装するメソッドの対応は、以下の通りである。
- Todoの全件取得→findAllメソッド
- Todoの新規作成→createメソッド
- Todoの完了→finishメソッド
- Todoの削除→deleteメソッド
実装クラスのFQCNを、todo.domain.service.TodoServiceImplとする。
package todo.domain.service.todo;
import java.util.Collection;
import java.util.Date;
import java.util.UUID;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
//import org.springframework.transaction.annotation.Transactional;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.domain.model.Todo;
import todo.domain.repository.todo.TodoRepository;
@Service// (1)
// @Transactional // (2)l
public class TodoServiceImpl implements TodoService {
@Inject// (3)
protected TodoRepository todoRepository;
private static final long MAX_UNFINISHED_COUNT = 5;
// (4)
public Todo findOne(String todoId) {
Todo todo = todoRepository.findOne(todoId);
if (todo == null) {
// (5)
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E404] The requested Todo is not found. (id="
+ todoId + ")"));
// (6)
throw new ResourceNotFoundException(messages);
}
return todo;
}
@Override
public Collection<Todo> findAll() {
return todoRepository.findAll();
}
@Override
public Todo create(Todo todo) {
long unfinishedCount = todoRepository.countByFinished(false);
if (unfinishedCount >= MAX_UNFINISHED_COUNT) {
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E001] The count of un-finished Todo must not be over "
+ MAX_UNFINISHED_COUNT + "."));
// (7)
throw new BusinessException(messages);
}
// (8)
String todoId = UUID.randomUUID().toString();
Date createdAt = new Date();
todo.setTodoId(todoId);
todo.setCreatedAt(createdAt);
todo.setFinished(false);
todoRepository.save(todo);
return todo;
}
@Override
public Todo finish(String todoId) {
Todo todo = findOne(todoId);
if (todo.isFinished()) {
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E002] The requested Todo is already finished. (id="
+ todoId + ")"));
throw new BusinessException(messages);
}
todo.setFinished(true);
todoRepository.save(todo);
return todo;
}
@Override
public void delete(String todoId) {
Todo todo = findOne(todoId);
todoRepository.delete(todo);
}
}
項番 | 説明 |
---|---|
(1)
|
Serviceとしてcomponent-scanの対象とするため、クラスレベルに
@Service アノテーションをつける。 |
(2)
|
今回の実装では、DBを使用しないため、トランザクション管理は不要であるが、DBを使用する場合は、クラスレベルに
@Transactional アノテーションをつけること。詳しくは、インフラストラクチャ層の変更で説明する。
|
(3)
|
@Inject アノテーションで、TodoRepositoryの実装をインジェクションする。 |
(4)
|
1件取得は、finishメソッドでもdeleteメソッドでも使用するため、メソッドとして用意しておく(interfaceに公開しても良い)。
|
(5)
|
結果メッセージを格納するクラスとして、共通ライブラリで用意されているorg.terasoluna.gfw.common.message.ResultMessageを用いる。
今回は、Errorメッセージをスローするために、ResultMessages.error()でメッセージ種別を指定して、ResultMessageを追加している。
|
(6)
|
対象のデータが存在しない場合、共通ライブラリで用意されているorg.terasoluna.gfw.common.exception.ResourceNotFoundExceptionをスローする。
|
(7)
|
業務エラーが発生した場合、共通ライブラリで用意されているorg.terasoluna.gfw.common.exception.BusinessExceptionをスローする。
|
(8)
|
一意性のある値を生成するために、UUIDを使用している。DBのシーケンスを用いてもよい。
|
Note
本節では、説明を単純化するため、エラーメッセージをハードコードしているが、メンテナンスの観点で本来は好ましくない。 通常、メッセージは、プロパティファイルに外部化することが推奨される。 プロパティファイルに外部化する方法は、プロパティ管理を参照されたい。
3.4.2. アプリケーション層の作成¶
ドメイン層の実装が完了したので、次はドメイン層を利用して、アプリケーション層の作成に取り掛かる。
3.4.2.1. Controllerの作成¶
package todo.app.todo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller // (1)
@RequestMapping("todo") // (2)
public class TodoController {
}
項番 | 説明 |
---|---|
(1)
|
Controllerとしてcomponent-scanの対象とするため、クラスレベルに、
@Controller アノテーションをつける。 |
(2)
|
TodoControllerが扱う画面遷移のパスを、すべて<contextPath>/todo配下にするため、クラスレベルに@RequestMapping(“todo”)を設定する。
|
3.4.2.1.1. Show all TODO¶
この画面では、
- 新規作成フォームの表示
- TODOの全件表示
を行う。
3.4.2.1.1.1. Formの作成¶
Formには、タイトル情報があればよいので、次のようなJavaBeanになる。FQCNは、todo.app.todo.TodoFormとする。
package todo.app.todo;
import java.io.Serializable;
public class TodoForm implements Serializable {
private static final long serialVersionUID = 1L;
private String todoTitle;
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
}
3.4.2.1.1.2. Controllerの実装¶
TodoControllerに、setUpFormメソッドと、listメソッドを実装する。
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject // (3)
protected TodoService todoService;
@ModelAttribute // (4)
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list") // (5)
public String list(Model model) {
Collection<Todo> todos = todoService.findAll();
model.addAttribute("todos", todos); // (6
return "todo/list"; // (7)
}
}
項番 | 説明 |
---|---|
(3)
|
TodoServiceを、DIコンテナによってインジェクションさせるために、
@Inject アノテーションをつける。DIコンテナの管理するTodoSerivce型インスタンスがインジェクションされるため、結果として、TodoServiceImplインスタンスがインジェクションされる。
|
(4)
|
Formを初期化する。
@ModelAttribute アノテーションをつけることで、このメソッドの返り値のformオブジェクトが、”todoForm”という名前でModelに追加される。TodoControllerの各処理で、model.addAttribute(“todoForm”, form)が実行されるのと同義。
|
(5)
|
listメソッドを”<contextPath>/todo/list”にマッピングされるための設定。クラスレベルで@RequestMapping(“todo”)が設定されているため、ここでは@RequestMapping(value = “list”)だけで良い。
|
(6)
|
ModelにTodoのリストを追加して、Viewに渡す。
|
(7)
|
View名として”todo/list”を返すと、spring-mvc.xmlに定義したInternalResourceViewResolverによって、WEB-INF/views/todo/list.jspがレンダリングされることになる。
|
3.4.2.1.1.3. JSPの作成¶
WEB-INF/views/todo/list.jspで、Controllerから渡されたModelを表示する。 まずは、”Finish”,”Delete”ボタン以外を作成する。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
<style type="text/css">
.strike {
text-decoration: line-through;
}
</style>
</head>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<!-- (1) -->
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<!-- (2) -->
<form:input path="todoTitle" />
<input type="submit" value="Create Todo" />
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<!-- (3) -->
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}"><!-- (4) -->
<span class="strike">
<!-- (5) -->
${f:h(todo.todoTitle)}
</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
</c:otherwise>
</c:choose></li>
</c:forEach>
</ul>
</div>
</body>
</html>
項番 | 説明 |
---|---|
(1)
|
<form:form>タグでフォームを表示する。modelAttribute属性に、ControllerでModelに追加したformの名前を指定する。
action属性に指定するcontextPathは、${pageContext.request.contextPath}で取得できる。
|
(2)
|
<form:input>タグでフォームのプロパティをバインドする。modelAttribute属性に指定したformのプロパティ名と、path属性の値が一致している必要がある。
|
(3)
|
<c:forEach>タグを用いて、Todoのリストを全て表示する。
|
(4)
|
完了かどうか(finished)で、打ち消し線(text-decoration: line-through;)を装飾するかどうかを判断する。
|
(5)
|
文字列値を出力する際は、XSS対策のため、必ずf:h()関数を使用してHTMLエスケープを行うこと。
XSS対策についての詳細は、XSS対策を参照されたい。
|
3.4.2.1.2. Create TODO¶
次に、一覧表示画面から”Create TODO”ボタンを押した後の、新規作成処理を実装する。
3.4.2.1.2.1. Controllerの修正¶
TodoControllerに、createメソッドを追加する。
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
import javax.validation.Valid;
import org.dozer.Mapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject
protected TodoService todoService;
// (8)
@Inject
protected Mapper beanMapper;
@ModelAttribute
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list")
public String list(Model model) {
Collection<Todo> todos = todoService.findAll();
model.addAttribute("todos", todos);
return "todo/list";
}
@RequestMapping(value = "create", method = RequestMethod.POST) // (9)
public String create(@Valid TodoForm todoForm, BindingResult bindingResult, // (10)
Model model, RedirectAttributes attributes) { // (11)
// (12)
if (bindingResult.hasErrors()) {
return list(model);
}
// (13)
Todo todo = beanMapper.map(todoForm, Todo.class);
try {
todoService.create(todo);
} catch (BusinessException e) {
// (14)
model.addAttribute(e.getResultMessages());
return list(model);
}
// (15)
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Created successfully!")));
return "redirect:/todo/list";
}
}
項番 | 説明 |
---|---|
(8)
|
Formオブジェクトを、DomainObjectに変換する際に、有用なMapperをインジェクションする。
|
(9)
|
パスが/todo/createで、HTTPメソッドがPOSTに対応するように、
@RequestMapping アノテーションを設定する。 |
(10)
|
フォームの入力チェックを行うため、Formの引数に
@Valid アノテーションをつける。入力チェック結果は、その直後の引数BindingResultに格納される。 |
(11)
|
正常に作成が完了した後、リダイレクトで一覧画面に戻る。リダイレクト先への情報を格納するために、引数にRedirectAttributesを加える。
|
(12)
|
入力エラーがあった場合、一覧画面に戻る。Todo全件取得を再度行う必要があるので、listメソッドを再実行する。
|
(13)
|
Mapperを用いて、TodoFormからTodoオブジェクトを作成する。変換元と変換先のプロパティ名が同じ場合は、設定不要である。
今回は、todoTitleプロパティのみ変換するため、Mapperを使用するメリットはほとんどない。プロパティの数が多い場合には、非常に便利である。
|
(14)
|
業務処理を実行して、BusinessExceptionが発生した場合、結果メッセージをModelに追加して、一覧画面に戻る。
|
(15)
|
正常に作成が完了したので、結果メッセージをflashスコープに追加して、一覧画面でリダイレクトする。
リダイレクトすることにより、ブラウザを再読み込みして、再び新規登録処理がPOSTされることがなくなる。なお、今回は成功メッセージであるため、ResultMessages.success()を使用している。
|
3.4.2.1.2.2. Formの修正¶
入力チェックのルールを定義するため、Formオブジェクトにアノテーションを追加する。
package todo.app.todo;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class TodoForm {
@NotNull // (1)
@Size(min = 1, max = 30) // (2)
private String todoTitle;
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
}
項番 | 説明 |
---|---|
(1)
|
必須項目であるので、
@NotNull アノテーションを付ける。 |
(2)
|
1文字以上30文字以下であるので、
@Size アノテーションで、範囲を指定する。 |
3.4.2.1.2.3. JSPの修正¶
結果メッセージ表示用のタグを追加する。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
<style type="text/css">
.strike {
text-decoration: line-through;
}
</style>
</head>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<!-- (6) -->
<t:messagesPanel />
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<form:input path="todoTitle" />
<form:errors path="todoTitle" /><!-- (7) -->
<input type="submit" value="Create Todo" />
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}">
<span style="text-decoration: line-through;">
${f:h(todo.todoTitle)}
</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
</c:otherwise>
</c:choose></li>
</c:forEach>
</ul>
</div>
</body>
</html>
項番 | 説明 |
---|---|
(6)
|
<t:messagesPanel>タグで、結果メッセージを表示する。
|
(7)
|
<form:errors>タグで、入力エラーがあった場合に表示する。path属性の値は、<form:input>タグと合わせる。
|
フォームに適切な値を入力してsubmitすると、以下のように、成功メッセージが表示される。
6件以上登録した場合は、業務エラーとなり、エラーメッセージが表示される。
入力フォームを、空文字にしてsubmitすると、以下のように、エラーメッセージが表示される。
3.4.2.1.2.4. メッセージ表示のカスタマイズ¶
<t:messagesPanel>の結果はデフォルトで、
<div class="alert alert-success"><ul><li>Created successfully!</li></ul></div>
と出力される。 スタイルシート(list.jspの<style>タグ内)に、以下の修正を加えて、結果メッセージの見た目をカスタマイズする。
.alert {
border: 1px solid;
}
.alert-error {
background-color: #c60f13;
border-color: #970b0e;
color: white;
}
.alert-success {
background-color: #5da423;
border-color: #457a1a;
color: white;
}
メッセージは、以下のように装飾される。
また、<form:errors>タグのcssClass属性で、入力エラーメッセージのclassを指定できる。JSPを次のように修正し、
<form:errors path="todoTitle" cssClass="text-error" />
スタイルシートに、以下を追加する。
.text-error {
color: #c60f13;
}
入力エラーは、以下のように装飾される。
3.4.2.1.3. Finish TODO¶
一覧表示画面に”Finish”ボタンを追加して、ボタンをsubmitすると、hiddenで対象のtodoIdが送られ、Todoを完了するように実装する。
3.4.2.1.3.1. JSPの修正¶
完了用のformを追加する。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
</head>
<style type="text/css">
.strike {
text-decoration: line-through;
}
.alert {
border: 1px solid;
}
.alert-error {
background-color: #c60f13;
border-color: #970b0e;
color: white;
}
.alert-success {
background-color: #5da423;
border-color: #457a1a;
color: white;
}
.text-error {
color: #c60f13;
}
</style>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<t:messagesPanel />
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<form:input path="todoTitle" />
<form:errors path="todoTitle" cssClass="text-error" />
<input type="submit" value="Create Todo" />
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}">
<span class="strike">${f:h(todo.todoTitle)}</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
<!-- (8) -->
<form:form
action="${pageContext.request.contextPath}/todo/finish"
method="post"
modelAttribute="todoForm"
cssStyle="display: inline-block;">
<!-- (9) -->
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<input type="submit" name="finish"
value="Finish" />
</form:form>
</c:otherwise>
</c:choose></li>
</c:forEach>
</ul>
</div>
</body>
</html>
項番 | 説明 |
---|---|
(8)
|
未完了の場合に、完了用のformを表示する。<contextPath>/todo/finishに対して、POSTでtodoIdを送信する。
|
(9)
|
<form:hidden>タグでtodoIdを渡す。value属性に値を設定する場合も、 必ずf:h()関数でHTMLエスケープすること。
|
3.4.2.1.3.2. Formの修正¶
完了用のフォームも、TodoFormを用いる。 TodoFormに、todoIdプロパティを追加する必要があるが、そのままだと、新規作成用の入力チェックルールが適用されてしまう。 一つのFormに、新規作成用と完了用で、別々のルールを指定するために、group属性を設定する。
package todo.app.todo;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class TodoForm {
// (3)
public static interface TodoCreate {
};
public static interface TodoFinish {
};
// (4)
@NotNull(groups = { TodoFinish.class })
private String todoId;
// (5)
@NotNull(groups = { TodoCreate.class })
@Size(min = 1, max = 30, groups = { TodoCreate.class })
private String todoTitle;
public String getTodoId() {
return todoId;
}
public void setTodoId(String todoId) {
this.todoId = todoId;
}
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
}
項番 | 説明 |
---|---|
(3)
|
グループ化したバリデーションを行うためのグループ名となるクラスを作成する。クラスは空でよいため、ここでは、インタフェースを定義する。
グループ化バリデーションについては、入力チェックを参照されたい。
|
(4)
|
todoIdは、完了処理には必須であるため、
@NotNull アノテーションをつける。完了時にのみ必要なルールであるので、group属性にTodoFinish.classを設定する。 |
(5)
|
新規作成用のルールは、完了処理には不要であるので、
@NotNull アノテーション、@Size アノテーション、それぞれのgroup属性にTodoCreate.classを設定する。 |
3.4.2.1.3.3. Controllerの修正¶
完了処理をTodoControllerに追加する。 グループ化したバリデーションを実行するために、@Valid アノテーションの代わりに、@Validated アノテーションを使用することに注意する。
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
import javax.validation.groups.Default;
import org.dozer.Mapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.app.todo.TodoForm.TodoCreate;
import todo.app.todo.TodoForm.TodoFinish;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject
protected TodoService todoService;
@Inject
protected Mapper beanMapper;
@ModelAttribute
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list")
public String list(Model model) {
Collection<Todo> todos = todoService.findAll();
model.addAttribute("todos", todos);
return "todo/list";
}
@RequestMapping(value = "create", method = RequestMethod.POST)
public String create(
@Validated({ Default.class, TodoCreate.class }) TodoForm todoForm, // (16)
BindingResult bindingResult, Model model,
RedirectAttributes attributes) {
if (bindingResult.hasErrors()) {
return list(model);
}
Todo todo = beanMapper.map(todoForm, Todo.class);
try {
todoService.create(todo);
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return list(model);
}
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Created successfully!")));
return "redirect:/todo/list";
}
@RequestMapping(value = "finish", method = RequestMethod.POST) // (17)
public String finish(
@Validated({ Default.class, TodoFinish.class }) TodoForm form, // (18)
BindingResult bindingResult, Model model,
RedirectAttributes attributes) {
// (19)
if (bindingResult.hasErrors()) {
return list(model);
}
try {
todoService.finish(form.getTodoId());
} catch (BusinessException e) {
// (20)
model.addAttribute(e.getResultMessages());
return list(model);
}
// (21)
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Finished successfully!")));
return "redirect:/todo/list";
}
}
項番 | 説明 |
---|---|
(16)
|
グループ化したバリデーションを実施するために、
@Valid アノテーションから@Validated アノテーションに変更する。valueには、対象のグループクラスを複数指定できる。Default.classはバリデーションルールにgroupが指定されていない場合のグループである。
@Validated アノテーションを使用する際は、Default.classも指定しておくのがよい。 |
(17)
|
パスが、/todo/finishで、HTTPメソッドがPOSTに対応するように、
@RequestMapping アノテーションを設定する。 |
(18)
|
Finish用のグループとして、TodoFinish.classを指定する。
|
(19)
|
入力エラーがあった場合、一覧画面に戻る。
|
(20)
|
業務処理を実行して、BusinessExceptionが発生した場合は、結果メッセージをModelに追加して、一覧画面に戻る。
|
(21)
|
正常に作成が完了したので、結果メッセージをflashスコープに追加して、一覧画面でリダイレクトする。
|
Note
Create用、Finish用に、別々のFormを作成しても良い。その場合は、必要なパラメータだけが、Formのプロパティになる。 ただし、クラス数が増え、プロパティも重複することが多いので、仕様変更が発生した場合に、修正コストが高くなる。 また、同一のController内で、複数のFormオブジェクトを、
@ModelAttribute
メソッドによって初期化すると、 毎回すべてのFormが初期化されてしまうので、不要なインスタンスが生成されてしまう。そのため、 基本的に、一つのControllerで利用するFormは、できるだけ集約し、グループ化したバリデーションの設定を行うことを推奨する。
Todoを新規作成した後に、FinishボタンをSubmitすると、以下のように打ち消し線が入り、完了したことがわかる。
3.4.2.1.4. Delete TODO¶
一覧表示画面に”Delete”ボタンを追加して、ボタンをsubmitすると、hiddenで対象のtodoIdが送られ、Todoを完了するように実装する。
3.4.2.1.4.1. JSPの修正¶
削除用のformを追加する。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
</head>
<style type="text/css">
.strike {
text-decoration: line-through;
}
.alert {
border: 1px solid;
}
.alert-error {
background-color: #c60f13;
border-color: #970b0e;
color: white;
}
.alert-success {
background-color: #5da423;
border-color: #457a1a;
color: white;
}
.text-error {
color: #c60f13;
}
</style>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<t:messagesPanel />
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<form:input path="todoTitle" />
<form:errors path="todoTitle" cssClass="text-error" />
<input type="submit" value="Create Todo" />
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}">
<span class="strike">${f:h(todo.todoTitle)}</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
<form:form
action="${pageContext.request.contextPath}/todo/finish"
method="post"
modelAttribute="todoForm"
cssStyle="display: inline-block;">
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<input type="submit" name="finish"
value="Finish" />
</form:form>
</c:otherwise>
</c:choose>
<!-- (10) -->
<form:form
action="${pageContext.request.contextPath}/todo/delete"
method="post" modelAttribute="todoForm"
cssStyle="display: inline-block;">
<!-- (11) -->
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<input type="submit" value="Delete" />
</form:form>
</li>
</c:forEach>
</ul>
</div>
</body>
</html>
項番 | 説明 |
---|---|
(10)
|
削除用のformを表示する。<contextPath>/todo/deleteに対して、POSTでtodoIdを送信する。
|
(11)
|
<form:hidden>タグで、todoIdを渡す。value属性に値を設定する場合も、必ずf:h()関数でHTMLエスケープすること。
|
3.4.2.1.4.2. Formの修正¶
Delete用のグループを、TodoFormに追加する。ルールは、Finish用と同じである。
package todo.app.todo;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class TodoForm {
public static interface TodoCreate {
};
public static interface TodoFinish {
};
// (6)
public static interface TodoDelete {
}
// (7)
@NotNull(groups = { TodoFinish.class, TodoDelete.class })
private String todoId;
@NotNull(groups = { TodoCreate.class })
@Size(min = 1, max = 30, groups = { TodoCreate.class })
private String todoTitle;
public String getTodoId() {
return todoId;
}
public void setTodoId(String todoId) {
this.todoId = todoId;
}
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
}
項番 | 説明 |
---|---|
(6)
|
Delete用のグループTodoDeleteを定義する。
|
(7)
|
todoIdプロパティに対して、TodoDeleteグループのバリデーションを行うように設定する。
|
3.4.2.1.4.3. Controllerの修正¶
削除処理を、TodoControllerに追加する。完了処理とほぼ同じである。
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
import javax.validation.groups.Default;
import org.dozer.Mapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.app.todo.TodoForm.TodoDelete;
import todo.app.todo.TodoForm.TodoCreate;
import todo.app.todo.TodoForm.TodoFinish;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject
protected TodoService todoService;
@Inject
protected Mapper beanMapper;
@ModelAttribute
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list")
public String list(Model model) {
Collection<Todo> todos = todoService.findAll();
model.addAttribute("todos", todos);
return "todo/list";
}
@RequestMapping(value = "create", method = RequestMethod.POST)
public String create(
@Validated({ Default.class, TodoCreate.class }) TodoForm todoForm,
BindingResult bindingResult, Model model,
RedirectAttributes attributes) {
if (bindingResult.hasErrors()) {
return list(model);
}
Todo todo = beanMapper.map(todoForm, Todo.class);
try {
todoService.create(todo);
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return list(model);
}
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Created successfully!")));
return "redirect:/todo/list";
}
@RequestMapping(value = "finish", method = RequestMethod.POST)
public String finish(
@Validated({ Default.class, TodoFinish.class }) TodoForm form,
BindingResult bindingResult, Model model,
RedirectAttributes attributes) {
if (bindingResult.hasErrors()) {
return list(model);
}
try {
todoService.finish(form.getTodoId());
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return list(model);
}
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Finished successfully!")));
return "redirect:/todo/list";
}
@RequestMapping(value = "delete", method = RequestMethod.POST)
public String delete(
@Validated({ Default.class, TodoDelete.class }) TodoForm form,
BindingResult bindingResult, Model model,
RedirectAttributes attributes) {
if (bindingResult.hasErrors()) {
return list(model);
}
try {
todoService.delete(form.getTodoId());
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return list(model);
}
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Deleted successfully!")));
return "redirect:/todo/list";
}
}
Todoに対して、”Delete”ボタンをsubmitすると、以下のように、対象のTODOが削除される。
3.5. インフラストラクチャ層の変更¶
3.5.1. 共通設定¶
3.5.1.1. pom.xmlの修正¶
pom.xmlに、H2Databaseを使用するためのdependencyを定義する。
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.172</version>
<scope>compile</scope>
</dependency>
Warning
この設定用はサンプルアプリケーションを簡単試すためのものであり、実際の開発で使用されることを想定していない。実際のプロジェクトでは削除すること。
また、JDBCドライバの<scope>
はprovided
にすべきである。
3.5.1.2. データソースの定義¶
3.5.1.2.1. todo-infra.xmlの修正¶
データソースの定義は、インフラストラクチャ層に関わるので、todo-infra.xmlに定義すべきであるが、 データベースのユーザー名や、パスワードなど、環境に依存する情報を含む定義は、別のBean定義ファイル(todo-env.xml)に定義することを推奨する。
ここでは、todo-env.xmlのインポートのみ行う。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="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.xsd"> <import resource="classpath:/META-INF/spring/todo-env.xml" /> </beans>Note
xxx-env.xmlを別ファイルにし、Mavenなどのビルドツールでこのファイルだけ差し替えることにより、環境ごと(開発環境、テスト環境など)で異なる設定値を管理できる。 また、特定の環境だけに対して、データソースをJNDIから取得するような設定ファイルの管理もできる。
3.5.1.2.2. todo-env.xmlの作成¶
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${database.driverClassName}" />
<property name="url" value="${database.url}" />
<property name="username" value="${database.username}" />
<property name="password" value="${database.password}" />
<property name="defaultAutoCommit" value="false" />
<property name="maxActive" value="${cp.maxActive}" />
<property name="maxIdle" value="${cp.maxIdle}" />
<property name="minIdle" value="${cp.minIdle}" />
<property name="maxWait" value="${cp.maxWait}" />
</bean>
</beans>
メンテナンス性向上のため、プロパティ値は外部化し、プロパティファイルに定義する。
Note
環境(Application Server)によっては、DataSourceをJNDIで取得したほうがよい。 その場合は<jee:jndi-lookup id=”dataSource” jndi-name=”JNDI名” />という定義を行う。 ビルド時に開発環境ではcommons-dbcpを使用し、テスト環境ではJNDIを使用する、というような切り替えができるように、envファイルを作成している。
3.5.1.2.3. todo-infra.properties¶
src/main/resources/META-INF/spring/todo-infra.propertiesに、インフラストラクチャ層に関するプロパティ値を定義する。
database=H2
## (1)
database.url=jdbc:h2:mem:todo;DB_CLOSE_DELAY=-1;INIT=create table if not exists todo(todo_id varchar(36) primary key, todo_title varchar(30), finished boolean, created_at timestamp)
database.username=sa
database.password=
database.driverClassName=org.h2.Driver
# connection pool
## (2)
cp.maxActive=96
cp.maxIdle=16
cp.minIdle=0
cp.maxWait=60000
項番 | 説明 |
---|---|
(1)
|
データベースに関する設定を行う。H2のURL,ドライバを設定する。
ここでは、説明を単純化するため、インメモリDBを使用して、APサーバーが起動するたびに初期化DDLが実行されるように設定している。
|
(2)
|
コネクションプールに関する設定。ここでは、サンプルの値を設定している。実際の値は、サーバーの性能によって異なることに注意する。
|
3.5.1.3. todo-domain.xmlの修正¶
@Transactional
アノテーションによるトランザクション管理を有効にするために、<tx:annotation-driven>タグを設定する。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="todo.domain" />
<import resource="classpath:META-INF/spring/todo-infra.xml"/>
<tx:annotation-driven/>
</beans>
3.5.1.4. TodoServiceImplの修正¶
package todo.domain.service.todo;
import java.util.Collection;
import java.util.Date;
import java.util.UUID;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.domain.model.Todo;
import todo.domain.repository.todo.TodoRepository;
@Service
@Transactional // (9)
public class TodoServiceImpl implements TodoService {
@Inject
protected TodoRepository todoRepository;
private static final long MAX_UNFINISHED_COUNT = 5;
public Todo findOne(String todoId) {
Todo todo = todoRepository.findOne(todoId);
if (todo == null) {
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E404] The requested Todo is not found. (id="
+ todoId + ")"));
throw new ResourceNotFoundException(messages);
}
return todo;
}
@Override
@Transactional(readOnly = true) // (10)
public Collection<Todo> findAll() {
return todoRepository.findAll();
}
@Override
public Todo create(Todo todo) {
long unfinishedCount = todoRepository.countByFinished(false);
if (unfinishedCount >= MAX_UNFINISHED_COUNT) {
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E001] The count of un-finished Todo must not be over "
+ MAX_UNFINISHED_COUNT + "."));
throw new BusinessException(messages);
}
String todoId = UUID.randomUUID().toString();
Date createdAt = new Date();
todo.setTodoId(todoId);
todo.setCreatedAt(createdAt);
todo.setFinished(false);
todoRepository.save(todo);
return todo;
}
@Override
public Todo finish(String todoId) {
Todo todo = findOne(todoId);
if (todo.isFinished()) {
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E002] The requested Todo is already finished. (id="
+ todoId + ")"));
throw new BusinessException(messages);
}
todo.setFinished(true);
todoRepository.save(todo);
return todo;
}
@Override
public void delete(String todoId) {
Todo todo = findOne(todoId);
todoRepository.delete(todo);
}
}
項番 | 説明 |
---|---|
(9)
|
クラスレベルに、
@Transactional アノテーションをつけることで、公開メソッドをすべてトランザクション管理する。これにより、メソッド開始時にトランザクションを開始し、メソッド正常終了時に、トランザクションをコミットする。
途中で非検査例外が発生した場合は、トランザクションをロールバックする。
|
(10)
|
参照のみ行う処理に関しては、readOnly=trueをつける。
O/R Mapperによっては、この設定により、参照時に最適化が行われる(JPAを使用する場合、効果はない)。
|
3.5.2. Spring Data JPAを使用する¶
本節では、インフラストラクチャ層において、 Spring Data JPA を使用する場合の設定方法について、説明する。 TERASOLUNA DAOを使用する場合は、本節を読み飛ばして、TERASOLUNA DAOを使用するに進んでよい。
3.5.2.1. Spring Data JPAを使用するための設定ファイルの修正¶
3.5.2.1.1. pom.xmlの修正¶
Spring Data JPAに関する依存ライブラリを追加するために、pom.xmlに、以下を追加する。
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-jpa</artifactId>
</dependency>
3.5.2.1.2. todo-infra.xmlの修正¶
todo-infra.xmlに、JPA、およびSpring Data JPAを使用するための設定を行う。JPAのEntityManagerFactoryは、ここで定義する。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<import resource="classpath:/META-INF/spring/todo-env.xml" />
<!-- (1) -->
<jpa:repositories base-package="todo.domain.repository"></jpa:repositories>
<!-- (2) -->
<bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false" />
<property name="database" value="${database}" />
</bean>
<!-- (3) -->
<bean
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
id="entityManagerFactory">
<!-- (4) -->
<property name="packagesToScan" value="todo.domain.model" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
<!-- (5) -->
<property name="jpaPropertyMap">
<util:map>
<entry key="hibernate.hbm2ddl.auto" value="none" />
<entry key="hibernate.ejb.naming_strategy"
value="org.hibernate.cfg.ImprovedNamingStrategy" />
<entry key="hibernate.connection.charSet" value="UTF-8" />
<entry key="hibernate.show_sql" value="false" />
<entry key="hibernate.format_sql" value="false" />
<entry key="hibernate.use_sql_comments" value="true" />
<entry key="hibernate.jdbc.batch_size" value="30" />
<entry key="hibernate.jdbc.fetch_size" value="100" />
</util:map>
</property>
</bean>
</beans>
項番 | 説明 |
---|---|
(1)
|
Spring Data JPAを使用すると、Repositoryインタフェースから実装クラスを自動生成する。
<jpa:repository>タグのbase-package属性で、対象のRepositoryを含むパッケージを指定する。
|
(2)
|
JPAの実装ベンダの設定を行う。JPA実装として、Hibernateを使うため、HibernateJpaVendorAdapterを定義する。
|
(3)
|
EntityManagerの定義を行う。
|
(4)
|
エンティティのパッケージ名を指定する。
|
(5)
|
Hibernateに関する詳細な設定を行う。
|
3.5.2.1.3. todo-env.xmlの修正¶
トランザクションマネージャに関連するBean定義を追加する。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${database.driverClassName}" />
<property name="url" value="${database.url}" />
<property name="username" value="${database.username}" />
<property name="password" value="${database.password}" />
<property name="defaultAutoCommit" value="false" />
<property name="maxActive" value="${cp.maxActive}" />
<property name="maxIdle" value="${cp.maxIdle}" />
<property name="minIdle" value="${cp.minIdle}" />
<property name="maxWait" value="${cp.maxWait}" />
</bean>
<!-- (6) -->
<bean class="org.springframework.orm.jpa.JpaTransactionManager"
id="transactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
</beans>
項番 | 説明 |
---|---|
(6)
|
トランザクションマネージャの設定。idは、transactionManagerにすること。
別の名前を指定する場合は、todo-domain.xmlの<tx:annotation-driven>タグと、todo-infra.xmlの<jpa:repository>タグにもトランザクションマネージャ名を指定する必要がある。
|
Note
JavaEEコンテナ上で、トランザクションマネージャは、JtaTransactionManagerを使用したほうがよい。この場合、<tx:jta-transaction-manager />でトランザクションマネージャの定義を行う。
これらの設定が、環境によって変わらないプロジェクト(例えば、Tomcatを使用する場合など)は、todo-infra.xmlに定義してもよい。
3.5.2.1.4. spring-mvc.xml¶
spring-mvc.xmlにOpenEntityManagerInViewInterceptorを追加し、Interceptorで、EntityManagerのライフサイクルの開始と終了を行う。 この設定を追加することで、アプリケーション層(Contollerや、Viewクラス)でのLazy Loadが、サポートされる。
<mvc:interceptors>
<!-- ... -->
<!-- (6) -->
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<bean
class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
項番 | 説明 |
---|---|
(6)
|
静的リソース(css, js, imageなど)へのアクセス(
/resources/** )の場合は、データアクセスが確実に発生しないため、Interceptorの適用対象外としている。 |
3.5.2.1.5. logback.xmlの修正¶
<!DOCTYPE logback>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss} [%thread] [%-5level] [%-48logger{48}] - %msg%n]]></pattern>
</encoder>
</appender>
<!-- Application Loggers -->
<logger name="todo">
<level value="debug" />
</logger>
<!-- TERASOLUNA -->
<logger name="org.terasoluna.gfw">
<level value="info" />
</logger>
<logger name="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor">
<level value="trace" />
</logger>
<!-- 3rdparty Loggers -->
<logger name="org.springframework">
<level value="warn" />
</logger>
<logger name="org.springframework.web.servlet">
<level value="info" />
</logger>
<!-- (8) -->
<logger name="org.hibernate.SQL">
<level value="debug" />
</logger>
<!-- (9) -->
<logger name="org.hibernate.type.descriptor.sql.BasicBinder">
<level value="trace" />
</logger>
<!-- (10) -->
<logger name="org.hibernate.engine.transaction">
<level value="debug" />
</logger>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
項番 | 説明 |
---|---|
(8)
|
HibernateによるSQLログを、出力するための設定。
|
(9)
|
HibernateによるSQLのバインド変数を、出力するための設定。
|
(10)
|
Hibernateによるトランザクションのログを、出力するための設定。
|
3.5.2.2. Entityの設定¶
Todoクラスを、データベースとマッピングするために、JPAのアノテーションを設定する。
package todo.domain.model;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
// (1)
@Entity
@Table(name = "todo")
public class Todo implements Serializable {
private static final long serialVersionUID = 1L;
// (2)
@Id
// (3)
@Column(name = "todo_id")
private String todoId;
@Column(name = "todo_title")
private String todoTitle;
@Column(name = "finished")
private boolean finished;
@Column(name = "created_at")
// (4)
@Temporal(TemporalType.TIMESTAMP)
private Date createdAt;
public String getTodoId() {
return todoId;
}
public void setTodoId(String todoId) {
this.todoId = todoId;
}
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
public boolean isFinished() {
return finished;
}
public void setFinished(boolean finished) {
this.finished = finished;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
}
項番 | 説明 |
---|---|
(1)
|
JPAのエンティティであることを示す
@Entity アノテーションを付け、対応するテーブル名を@Table アノテーションで設定する。 |
(2)
|
主キーとなるカラムに対応するフィールドに、
@Id アノテーションをつける。 |
(3)
|
@Column アノテーションで、対応するカラム名を設定する。 |
(4)
|
Date型は、java.sql.Date, java.sql.Time, java.sql.Timestampのどれに対応するか、明示的に指定する必要がある。ここでは、Timestampを指定する。
|
3.5.2.3. TodoRepositoryの修正¶
package todo.domain.repository.todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import todo.domain.model.Todo;
// (1)
public interface TodoRepository extends JpaRepository<Todo, String> {
@Query(value = "SELECT COUNT(x) FROM Todo x WHERE x.finished = :finished") // (2)
long countByFinished(@Param("finished") boolean finished); // (3)
}
項番 | 説明 |
---|---|
(1)
|
JpaRepositoryを拡張したインタフェースにする。Genericsのパラメータには、順にEntityのクラス(Todo)、主キーのクラス(String)を指定する。
基本的なCRUD操作(findOne, findAll, save, deleteなど)は、上位のインタフェースに定義済みであるため、TodoRepositoryでは、countByFinishedのみ定義すればよい。
|
(2)
|
countByFinishedを呼び出した際に、実行されるJPQLを、
@Query アノテーションで指定する。 |
(3)
|
(2)で指定したJPQLのバインド変数を
@Param アノテーションで設定する。ここでは、JPQL中の
”:finished” を埋めるためのメソッド引数に、@Param(“finished”)を付けている。 |
3.5.2.4. TodoRepositoryImplの修正¶
3.5.3. TERASOLUNA DAOを使用する¶
本節では、インフラストラクチャ層において、TERASOLUNA DAOを使用する場合の設定方法について説明する。
Note
TERASOLUNA DAOとは、MyBatis2.3.5とSpringの連携クラスであるorg.springframework.orm.ibatis.support.SqlMapClientDaoSupportを、用途別に拡張した簡易SQLマッパーを提供するライブラリである。 以下4つのインタフェースをもつDAOが、提供されている。
- jp.terasoluna.fw.dao.QueryDAO
- jp.terasoluna.fw.dao.UpdateDAO
- jp.terasoluna.fw.dao.StoredProcedureDAO
- jp.terasoluna.fw.dao.QueryRowHandleDAO
それぞれのインタフェースに対して、
jp.terasoluna.fw.dao.ibatis.XxxDAOiBatisImpl
という実装を持つ。
3.5.3.1. TERASOLUNA DAOを使用するための設定¶
3.5.3.1.1. pom.xmlの修正¶
TERASOLUNA DAOに関する依存ライブラリを追加するために、pom.xmlに、以下の内容を追加する。
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-mybatis2</artifactId>
</dependency>
3.5.3.1.2. todo-infra.xmlの修正¶
todo-infra.xmlに、TERASOLUNA DAOを使用するための設定を行う。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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.xsd">
<import resource="classpath:/META-INF/spring/todo-env.xml" />
<!-- (1) -->
<bean id="sqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<!-- (2) -->
<property name="configLocations"
value="classpath*:/META-INF/mybatis/config/*sqlMapConfig.xml" />
<!-- (3) -->
<property name="mappingLocations"
value="classpath*:/META-INF/mybatis/sql/**/*-sqlmap.xml" />
<property name="dataSource" ref="dataSource" />
</bean>
<!-- (4) -->
<bean id="queryDAO" class="jp.terasoluna.fw.dao.ibatis.QueryDAOiBatisImpl">
<property name="sqlMapClient" ref="sqlMapClient" />
</bean>
<bean id="updateDAO" class="jp.terasoluna.fw.dao.ibatis.UpdateDAOiBatisImpl">
<property name="sqlMapClient" ref="sqlMapClient" />
</bean>
<bean id="spDAO"
class="jp.terasoluna.fw.dao.ibatis.StoredProcedureDAOiBatisImpl">
<property name="sqlMapClient" ref="sqlMapClient" />
</bean>
<bean id="queryRowHandleDAO"
class="jp.terasoluna.fw.dao.ibatis.QueryRowHandleDAOiBatisImpl">
<property name="sqlMapClient" ref="sqlMapClient" />
</bean>
</beans>
項番 | 説明 |
---|---|
(1)
|
SqlMapClientの定義を行う。
|
(2)
|
SqlMap設定ファイルのパスを設定する。ここでは、META-INF/mybatis/config以下の、*sqlMapConfig.xmlを読み込む。
|
(3)
|
SqlMapファイルのパスを設定する。ここでは、META-INF/mybatis/sql以下の、任意のフォルダの*-sqlmap.xmlを読み込む。
|
(4)
|
TERASOLUNA DAOの定義を行う。
|
3.5.3.1.3. todo-env.xmlの修正¶
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${database.driverClassName}" />
<property name="url" value="${database.url}" />
<property name="username" value="${database.username}" />
<property name="password" value="${database.password}" />
<property name="defaultAutoCommit" value="false" />
<property name="maxActive" value="${cp.maxActive}" />
<property name="maxIdle" value="${cp.maxIdle}" />
<property name="minIdle" value="${cp.minIdle}" />
<property name="maxWait" value="${cp.maxWait}" />
</bean>
<!-- (1) -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
項番 | 説明 |
---|---|
(1)
|
トランザクションマネージャの設定。idは、transactionManagerにすること。
別の名前を指定する場合は、<tx:annotation-driven>タグにも、トランザクションマネージャ名を指定する必要がある。
|
3.5.3.1.4. logback.xmlの修正¶
<!DOCTYPE logback>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss} [%thread] [%-5level] [%-48logger{48}] - %msg%n]]></pattern>
</encoder>
</appender>
<!-- Application Loggers -->
<logger name="todo">
<level value="debug" />
</logger>
<!-- TERASOLUNA -->
<logger name="org.terasoluna.gfw">
<level value="info" />
</logger>
<logger name="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor">
<level value="trace" />
</logger>
<!-- 3rdparty Loggers -->
<logger name="org.springframework">
<level value="warn" />
</logger>
<logger name="org.springframework.web.servlet">
<level value="info" />
</logger>
<!-- (8) -->
<logger name="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<level value="debug" />
</logger>
<!-- (9) -->
<logger name="java.sql.Connection">
<level value="trace" />
</logger>
<logger name="java.sql.PreparedStatement">
<level value="debug" />
</logger>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
項番 | 説明 |
---|---|
(8)
|
DataSourceTransactionManagerによる、トランザクションのログを出力するための設定。
|
(9)
|
SQLログを出力するための設定。
|
3.5.3.2. sqlMapConfigの作成¶
src/main/resources/META-INF/mybatis/config/sqlMapConfig.xmlを作成し、以下のように記述する。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<!-- (1) -->
<settings useStatementNamespaces="true" />
</sqlMapConfig>
項番 | 説明 |
---|---|
(1)
|
SQLIDに、名前空間を与える設定を行う。
|
3.5.3.3. RepositoryImplの修正¶
package todo.domain.repository.todo;
import java.util.Collection;
import javax.inject.Inject;
import jp.terasoluna.fw.dao.QueryDAO;
import jp.terasoluna.fw.dao.UpdateDAO;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import todo.domain.model.Todo;
@Repository
// (1)
@Transactional
public class TodoRepositoryImpl implements TodoRepository {
// (2)
@Inject
protected QueryDAO queryDAO;
@Inject
protected UpdateDAO updateDAO;
// (3)
@Override
@Transactional(readOnly = true)
public Todo findOne(String todoId) {
return queryDAO.executeForObject("todo.findOne", todoId, Todo.class);
}
@Override
@Transactional(readOnly = true)
public Collection<Todo> findAll() {
return queryDAO.executeForObjectList("todo.findAll", null);
}
@Override
public Todo save(Todo todo) {
// (4)
if (exists(todo.getTodoId())) {
updateDAO.execute("todo.update", todo);
} else {
updateDAO.execute("todo.create", todo);
}
return todo;
}
@Transactional(readOnly = true)
public boolean exists(String todoId) {
long count = queryDAO.executeForObject("todo.exists", todoId,
Long.class);
return count > 0;
}
@Override
public void delete(Todo todo) {
updateDAO.execute("todo.delete", todo);
}
@Override
@Transactional(readOnly = true)
public long countByFinished(boolean finished) {
return queryDAO.executeForObject("todo.countByFinished", finished,
Long.class);
}
}
項番 | 説明 |
---|---|
(1)
|
クラスレベルに、
@Transactional アノテーションをつけることで、公開メソッドをすべてトランザクション管理する。Repositoryを呼び出すService側でも設定しているため、
@Transactional をつけなくともトランザクション管理になるが、propagation属性は、デフォルトのREQUIREDであるため、トランザクションがネストした場合、内側(Repository側)のトランザクションは、外側(Service側)のトランザクションに参加する。 |
(2)
|
@Inject アノテーションで、QueryDAO, UpdateDAOをインジェクションする。 |
(3)
|
Repositoryのメソッド実装は、基本的には、TERASOLUNA DAOにSQLIDと、パラメータを渡すことになる。
参照系の場合はQueryDAO、更新系の場合はUpdateDAOを使用する。SQLIDに対応するSQLの設定は、次に行う。
|
(4)
|
saveメソッドで新規作成と、更新の両方を実装している。
どちらの処理を行うか判断するために、existsメソッドを作成する。
このメソッドでは、対象のtodoIdの件数を取得し、件数が0より大きいかどうかで存在を確認する。
|
Note
saveメソッドは、新規作成でも更新でも利用できるメリットがある。 しかしながら、2回SQLが実行されるという性能面でのデメリットもある。 性能を重視する場合は、新規作成用にcreateメソッドを、更新用にupdateメソッドを作成すること。
3.5.3.4. SQLMapファイルの作成¶
src/main/resources/META-INF/mybatis/sql/todo-sqlmap.xmlを作成し、TodoRepositoryImplで使用したSQLIDに対応するsqlを、以下のように記述する。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="todo">
<resultMap id="todo" class="todo.domain.model.Todo">
<result property="todoId" column="todo_id" />
<result property="todoTitle" column="todo_title" />
<result property="finished" column="finished" />
<result property="createdAt" column="created_at" />
</resultMap>
<select id="findOne" parameterClass="java.lang.String"
resultMap="todo"><![CDATA[
SELECT todo_id,
todo_title,
finished,
created_at
FROM todo
WHERE todo_id = #value#
]]></select>
<select id="findAll" resultMap="todo"><![CDATA[
SELECT todo_id,
todo_title,
finished,
created_at
FROM todo
]]></select>
<insert id="create" parameterClass="todo.domain.model.Todo"><![CDATA[
INSERT INTO todo
(todo_id,
todo_title,
finished,
created_at)
VALUES ( #todoId#,
#todoTitle#,
#finished#,
#createdAt# )
]]></insert>
<update id="update" parameterClass="todo.domain.model.Todo"><![CDATA[
UPDATE todo
SET todo_title = #todoTitle#,
finished = #finished#,
created_at = #createdAt#
WHERE todo_id = #todoId#
]]></update>
<delete id="delete" parameterClass="todo.domain.model.Todo"><![CDATA[
DELETE FROM todo
WHERE todo_id = #todoId#
]]></delete>
<select id="countByFinished" parameterClass="java.lang.Boolean"
resultClass="java.lang.Long"><![CDATA[
SELECT COUNT(*)
FROM todo
WHERE finished = #value#
]]></select>
<select id="exists" parameterClass="java.lang.String"
resultClass="java.lang.Long"><![CDATA[
SELECT COUNT(*)
FROM todo
WHERE todo_id = #value#
]]></select>
</sqlMap>
以上で、TERASOLUNA DAOを使う対応が完了した。APサーバーを起動し、 Todoの表示や、新規作成を行うと、以下のようなSQLログやトランザクションログが出力される。
2013-11-28 14:15:37 [tomcat-http--3] [TRACE] [o.t.gfw.web.logging.TraceLoggingInterceptor ] - [START CONTROLLER] TodoController.list(Model)
2013-11-28 14:15:37 [tomcat-http--3] [DEBUG] [o.s.jdbc.datasource.DataSourceTransactionManager] - Creating new transaction with name [todo.domain.serivce.todo.TodoServiceImpl.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly; ''
2013-11-28 14:15:37 [tomcat-http--3] [DEBUG] [o.s.jdbc.datasource.DataSourceTransactionManager] - Acquired Connection [jdbc:h2:mem:todo, UserName=SA, H2 JDBC Driver] for JDBC transaction
2013-11-28 14:15:37 [tomcat-http--3] [DEBUG] [o.s.jdbc.datasource.DataSourceTransactionManager] - Participating in existing transaction
2013-11-28 14:15:37 [tomcat-http--3] [DEBUG] [java.sql.Connection ] - {conn-100014} Connection
2013-11-28 14:15:37 [tomcat-http--3] [DEBUG] [java.sql.Connection ] - {conn-100014} Preparing Statement: SELECT todo_id, todo_title, finished, created_at FROM todo
2013-11-28 14:15:37 [tomcat-http--3] [DEBUG] [java.sql.PreparedStatement ] - {pstm-100015} Executing Statement: SELECT todo_id, todo_title, finished, created_at FROM todo
2013-11-28 14:15:37 [tomcat-http--3] [DEBUG] [java.sql.PreparedStatement ] - {pstm-100015} Parameters: []
2013-11-28 14:15:37 [tomcat-http--3] [DEBUG] [java.sql.PreparedStatement ] - {pstm-100015} Types: []
2013-11-28 14:15:37 [tomcat-http--3] [DEBUG] [o.s.jdbc.datasource.DataSourceTransactionManager] - Initiating transaction commit
2013-11-28 14:15:37 [tomcat-http--3] [DEBUG] [o.s.jdbc.datasource.DataSourceTransactionManager] - Committing JDBC transaction on Connection [jdbc:h2:mem:todo, UserName=SA, H2 JDBC Driver]
2013-11-28 14:15:37 [tomcat-http--3] [DEBUG] [o.s.jdbc.datasource.DataSourceTransactionManager] - Releasing JDBC Connection [jdbc:h2:mem:todo, UserName=SA, H2 JDBC Driver] after transaction
2013-11-28 14:15:37 [tomcat-http--3] [TRACE] [o.t.gfw.web.logging.TraceLoggingInterceptor ] - [END CONTROLLER ] TodoController.list(Model)-> view=todo/list, model={todoForm=todo.app.todo.TodoForm@1386751, todos=[todo.domain.model.Todo@72edc], org.springframework.validation.BindingResult.todoForm=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
2013-11-28 14:15:37 [tomcat-http--3] [TRACE] [o.t.gfw.web.logging.TraceLoggingInterceptor ] - [HANDLING TIME ] TodoController.list(Model)-> 2,461,131 ns
3.6. おわりに¶
このチュートリアルでは、以下の内容を学習した。
- TERASOLUNA Global Frameworkによる基本的なアプリケーションの開発方法、およびEclipseプロジェクトの構築方法
- STSの使用方法
- MavenでTERASOLUNA Global Frameworkを使用する方法
- TERASOLUNA Global Frameworkのアプリケーションのレイヤ化に従った開発方法
- POJO(+ Spring)によるドメイン層の実装
- Spring MVCとJSPタグライブラリを使用したアプリケーション層の実装
- Spring Data JPAによるインフラストラクチャ層の実装
- MyBatis2によるインフラストラクチャ層の実装
ここで作成したTODO管理アプリケーションには、以下の改善点がある。 アプリケーションの修正を学習課題として、ガイドライン中の該当する説明を参照されたい。