Spring Bootによるウェブアプリ開発: データベース


Spring Bootのバージョンアップ

Spring Bootが1.5.4にバージョンアップしているので自分のプロジェクトの利用バージョンもあげよう。

springBootVersionを書き換えて、プロジェクトを右クリックし、「Reload Project」を選択すると、Dependenciesなどが1.5.4のものに変更される。

永続化

本日の内容: https://docs.spring.io/spring-boot/docs/1.5.4.RELEASE/reference/html/boot-features-sql.html

永続化とは

これまで作ったウェブアプリは、起動し直すとそれまでに入力したデータ(例えば掲示板のコメント等)は消えてしまっていた。今回は、入力されたデータを保存することを考えよう。このように、プログラムの実行を終了してもデータが存続する特性を永続性(Persistence)といい、永続性を持たせることを永続化という。

Javaプログラムにおける永続化の方法

色々な方法がある。

  • テキストファイルとして保存
    • 独自の書式(フォーマット)を開発・利用
    • 既存のフォーマットを利用
      • CSV
      • HTML、XML(をベースとしたマークアップ言語)
      • JSON(JavaScript Object Notation)
      • YML
      • OpenDocument
      • etc…
  • バイナリファイルとして保存
    • 独自の形式を開発・利用
    • 既存のフォーマットを利用
      • Rich Text Format
      • PNG、JPEG、TIFF、MPEG
      • PDF
      • etc…
  • データベースシステムを利用
    • 独自のシステムを開発・利用
    • リレーショナルデータベースベース管理システム(relational database management system, RDBMS)を利用
    • RDBMS以外のデータベース(NoSQL、オブジェクトデータベース等)を利用
  • etc…

掲示板のデータをRDBMSを使って永続化する

本講義はJavaに関する講義であるが、RDBMSは知っておいた方がいい。実際、ウェブアプリ開発ではRDBMSがめちゃくちゃ使われている。

RDBMSはSQLという言語を使う。様々なRDBMSが存在するが、どれもSQLを使うので、これを知っておけばどのRDBMSでも基本的な操作はできるようになる。

Spring Bootでデータベースを使う準備

まずはRDBMSを選定しよう。今回は、組み込みで使える(アプリケーションの中に入れてしまうことができる)H2を使う。

次にJavaのプログラムからどのような方法を使ってデータベースにアクセスするかを決めよう。その方法は主に次の2つがある。

JDBCは、SQLをJavaプログラムの中で使ってデータベースを操作する方法で、JPAはO/Rマッパーというもので、Javaのインスタンスをデータベースに格納できる形に変換してデータベースにアクセスする方法(データベースにアクセスするSQLを自動生成する方法)である。

こう説明すると「JPAを使えばJavaだけでOK」と思えるかもしれないが、現状、JPAはSQLをよく知らないと使いこなすことはできない。

というわけで、本講義では、JDBCを使っていく。

build.gradleのdependenciesに下記の2行を追記して「Reload Project」を実行する(前回からの差分)。

これで、必要なファイル等が追加される。

ウェブインターフェースでデータベースに接続する

H2には、ブラウザからデータベースを操作できるインターフェースが内蔵されている。これはSpring Bootアプリケーションに組み込んだ状態でも利用することができる。

ウェブインターフェースを利用可能にするため、下記の1行をapplication.propertiesに加えよう。

bootRunで実行し、http://localhost:18080/h2-consoleにアクセスする。

h2-console

次に各設定項目を入力しよう。

  • JDBC URL: データベースの接続を表すURL。これにはどのディレクトリにデータを置くか、という情報が含まれる。今回は、ホームディレクトリに「webappdb」というディレクトリを作りそこを指定する。この場合、JDBC URLは「jdbc:h2:~/webappdb」となる。
  • User Name: データベースに接続する時のユーザ名を表す。今回はデフォルトのまま「sa」とする。
  • Password: データベースに接続する時のパスワードを表す。今回は「sa」とする。

入力して、Connectボタンをクリックすると、SQLの入力画面となる。

テーブルを作成する

掲示板のコメントを格納するためのテーブルを作成しよう。

次のSQL文を入力して、Runボタンをクリックするとテーブルが作成される。

「bbs_comment」はテーブル名。Javaのクラスに例えるとクラス名に相当する。

id、body、name、dateはカラム名。Javaのクラスに例えるとフィールド名に相当する。

INT、TEXT、TIMESTAMPは型で、それぞれJavaのint、String、Dateに対応している(参考: Data Types)。

AUTO_INCREMENTは、自動的に番号をつけることを意味している。だから、保存するときにidには値を入れないで良い。保存されたときに自動的に番号がつく。PRIMARY KEYは、そのレコード(Javaでいうところのインスタンス)における「テーブルの中に保存されているデータのある1行を識別するために必要な情報」を表し、主キーという。Javaにはそういうものはないが(無理やりこじつけるとすればhashCodeかな?)、データベースではレコードを区別するためにあったほうが良い(ほとんどの場合設定するが、なくても大丈夫)。

左側に表のアイコンがついた「BBS_COMMENT」というのができれば作成成功。

テーブルにレコードを登録する

登録するには、INSERT命令を使う。

うまくいったら、「Update count: 1」と表示される。適当に値を変えて、さらにデータを入力してみよう。

テーブルからレコードを検索・取得して表示する

データの検索・取得には、SELECTを使う。先ずは全件取得してみよう。

「*」は全てのカラムの情報(ここでは、id、name、body、date)を取得することを示す。

一部のデータを表示(検索)するには、WHERE節を追加する。

これは、nameカラムの値が「たろう」であるレコードだけが表示される。

プログラムから扱う

プログラムから扱うために、接続についての情報をapplication.propertiesに加えよう。ウェブインターフェースでデータベースに接続する時の情報と同じにする。

コントローラは次のようになる(GitHub)。
ベースとなっているLecture05Controllerと比較しながらみていこう。

24〜32行目

プログラムからJDBCを使いデータベースにアクセスするには、JdbcTemplateクラスを用いる。このクラスのインスタンスを用意し、データベースにアクセスするための各種メソッドを利用すれば良い。

この「JdbcTemplateクラスのインスタンス」をどのようにして用意すればいいだろうか?これまでと同様にnew演算子を使えばいいのだろうか?

Spring Bootの環境においては、一部のクラスは、自分でnew演算子を使ってインスタンス化するのではなくシステムにインスタンス化してもらって埋め込むという「依存性注入(dependency injection、DI)」によって行う。

JdbcTemplateクラスのインスタンスはDIで用意する。これを行なっているのが、コンストラクタである。コンストラクタに@Autowiredをつけると、引数はDIコンテナによってインスタンス化されたものが渡される。それをフィールドとして保持する。

52〜60行目

postされたデータをデータベースに登録する。

データを登録するINSERT文を実行するには、updateメソッドを利用する。

第1引数にSQL文を指定する。SQL文の中に「?」をかくと、そこには変数を埋め込むことができる。この「?」をプレースホルダ(placeholder)という。

第2引数以降はプレースホルダに対応した変数を指定する。

最後に、同一サーバの「/lecture10/bbs」にリダイレクトする。

34〜50行目

データベースからデータを全件取り出して表示する。

データを取り出すSELECT文を実行するには、queryForListメソッドを利用する。このメソッドの戻り値は、レコードのListである。レコードは、カラム名がキーでデータが値となるMapの形となっている。よって、queryForListの戻り値の型は「List<Map<String, Object>>」となる。

取り出したデータからBbsCommentのインスタンスを生成し、表示用のリストに入れていく。

拡張しよう(次回までの課題)

  • BbsCommentにIDを加え、IDも表示するようにしてみよう(第5回の時の掲示板を壊さないように、テンプレートを新たに作る等、工夫しよう)。
  • SQLのDELETE文を利用してデータベースから削除する機能を付け加えよう。

Spring Bootによるウェブアプリ開発: コレクション(2)


辞書を作る: Mapの利用

Mapとは

  • 「キー」と「値」をペアで扱うコレクション(Listは値だけを扱う)
  • キーはユニーク(unique、唯一の)になる。同じキーで違う値は登録できない。
  • キーと値を保存するにはputメソッドを使う。
  • キーで検索して値を取り出すにはgetメソッドを使う。
  • Mapはインターフェース。Mapを実装したクラスとしてはHashMapがよく使われる。
  • 教科書P.215〜227

Mapを利用する例として、次の機能を持つ辞書アプリを作ってみよう。

  • 英語をキー、日本語を値として登録できる。
  • 英語から日本語を検索できる。
  • 登録されたデータ一覧を見ることができる。

登録

  • コントローラ、22行目: 辞書の情報を格納するHashSetをフィールドとして定義する。
  • コントローラ、37行目: putメソッドでMapに登録する。

検索

  • コントローラ、55行目: getメソッドでキーから値を得る。登録されていない場合はnullが返ってくる。

一覧

  • テンプレート、24行目: keySetメソッドでキーのSetを取得する。Setについては教科書P.205〜214を参照。
  • テンプレート、26行目: getメソッドでキーから値を得る。

資料

ソースコード
オンラインドキュメント
教科書

  • Map: P.215〜227
  • Set: P.205〜214

Spring Bootによるウェブアプリ開発: コレクション(1)


Formの基礎(前回)のまとめ

前回のプログラム(おみくじ及び例1〜3)の動作を、下記の観点で確認しよう(記述の意味を理解すること)。

  • @RequestMappingアノテーション
  • @RequestParamアノテーション
  • ModelMap型のaddAttributeメソッド
  • postとgetの違い
  • @GetMappingアノテーションと@PostMappingアノテーション
  • th:text属性
  • th:each属性
  • th:if属性
  • @RequestParamアノテーションのrequiredオプション
  • メソッドのオーバーロード

掲示板の改良: 名前や日付も保存するList

第4回で作った掲示板はコメントだけを保存・表示するものであった。より実用的にするために、投稿者の名前と投稿時間も保存・表示するようにしてみよう。

まず第4回の掲示板を第5回で実行できるようにコピーする(テンプレートのパス等を修正しなければいけないことに注意する)。

どこをどのように変更するか?

  1. テンプレート: フォームに名前の入力欄を加える。
  2. コントローラ: 名前を受け取れるようにする。
  3. コメントを表すBbsCommentクラスを作成: 本文と名前と投稿日時のフィールドを作る。
  4. コントローラ: BbsComment型のインスタンスを作成
  5. コントローラ: コメントを保存するリストの型をList<BbsComment>にする。
  6. コントローラ: BbsComment型のインスタンスを作成し、リストに追加する。
  7. テンプレート: コメントの表示部分を修正する。

資料

ソースコード
オンラインドキュメント
教科書

  • Map: P.215〜227
  • Set: P.205〜214

Spring Bootによるウェブアプリ開発: Formの基礎


前回の要点

  • http://start.spring.ioでプロジェクトを作成する。
  • 処理の流れ
  • テンプレートとコントローラを作る。
  • テンプレート
    • HTMLで記述する。ただし単独で現れるタグは後ろに「/」をつける。
    • コントローラから送られてきた値を表示するところで、th:text属性を使う。
  • コントローラ
    • クラスに@Controllerアノテーションをつける。
    • クラスとメソッドにつける@RequestMappingアノテーションで担当するURLを指定する。
    • メソッドの返り値で利用するテンプレートを指定する。
    • テンプレートに値を送り込みたい場合は、メソッドにModelMap型の引数を加え、そのaddAttributeメソッドで名前と値を指定する。
  • その他
    • ポート番号を変える: resourcesにあるapplication.propertiesに「server.port = 18080」などと記述する。

ドキュメント

Formの作成と表示

おみくじアプリを作ってみよう。

処理のシーケンスは次のようになる。

おみくじのシーケンス

名前を入力する(フォームの利用)

  • formタグ: どこからどこまでがフォームかを表す。action属性でどこに送るか指定する。method属性でどのような形で送るかを指定する。
  • inputタグ: name属性で区別のための名前をつける。この例ではその名前にnameとつけて紛らわしいので注意すること。
  • buttonタグ: type属性をsubmitとすることで、formの送信ボタンにすることができる。

コントローラ

  • 18〜21行目:「/lecture04/omikuji_input」にアクセスがあると、「lecture04/omikuji_input」がブラウザに送られる。
  • 26〜34行目:「/lecture04/omikuji_output」にアクセスがあると、「lecture04/omikuji_output」がブラウザに送られる。
    • 27行目:「フォームから送られてくるパラメータを受け取るための引数」を指定する為に@RequestParamアノテーションをつける。
    • 28〜31行目: String型の変数を乱数によって「吉」か「大吉」にする。
    • 32行目: 「result」という名前で変数resultをテンプレートに送る。
    • 32行目: 「name」という名前で変数nameをテンプレートに送る。

結果の表示

  • コントローラからは「name」と「result」という名前で値が送られてきている。それぞれ、th:text属性で表示する。

例1: 掲示板

テンプレート

  • 最初の表示(get)とコメントを書き込む時(post)の両方でこのテンプレートを使う。
  • formのaction属性は省略すると、同じURLにアクセスすることになる。
  • リストから一つづつ取り出すのは、th:each属性を使う。コロンの左側でつけた名前でアクセスできる(スコープは、属性をつけた要素の内側)。注意しなければならないのは、繰り返し出力されるのは、th:each属性がついた要素(タグ)だということ。例えば、commentListに3つの文字列が入っていたとしたら、li要素が3つ出力される。
  • タグ無しで値を出力するのはth:blockタグを使う。

コントローラ

  • @RequestMappingはgetとpostの両方で実行されるが、@GetMappingと@PostMappingを使うことで、同じURLでgetとpostの処理を別のメソッドに分けて書くことができる。
  • コントローラ全体

例2: じゃんけん

テンプレート

  • 最初の表示(get)とじゃんけんをした時(post)の両方でこのテンプレートを使う。
  • th:ifで条件が真(true)の時だけ要素を表示させることができる。ここでは、コントローラからresultが送られてきた時(つまりresultがnullでない)にそれを表示している。
  • input要素のtype属性は「radio」。checked=”checked”をつけた要素がデフォルトで選択される。
  • name属性は全て同じにする(nameを同じにすると同一グループとなり排他的選択ができる)。
  • 選択されたボタンのvalue属性がコントローラに送られる。

コントローラ

  • getとpostで同じメソッド名となっているが、これをオーバーロードという(教科書P.132)。
  • StringBuilderは文字列を繋げる時に使える(教科書P.82)。
  • 律儀にコンピュータの手を決定して結果を出力してもいいが、ここでは選択した手に関わらずランダムで結果を出力している。
  • コントローラ全体

例3: アンケート

テンプレート

  • チェックボックスはname属性で区別する。

コントローラ

  • チェックボックスは、チェックをしたときにしかパラメータが送信されない。そのため、デフォルトの@RequestParamではエラーになる。
  • パラメータが送信されなくてもエラーにならないようにするには、「required = false」とする。
  • チェックされているときはtrue、チェックされてない時(パラメータが無い時)は、falseになる。
  • コントローラ全体

教科書

Spring Bootによるウェブアプリ開発: プロジェクトの作成からページの表示まで


プロジェクトの雛形を作成

  1. ブラウザで「http://start.spring.io」にアクセスする。
  2. 「Cradle Project」を選択する。
  3. グループIDとして、パッケージ名を入力する。ここでは「ユーザ名.webapp」とする。
  4. アーティファクトIDとしてプロジェクト名を入力する。ここでは「webapp」とする。
  5. 利用する依存ライブラリを入力する。名前の一部を入力すると選択肢が現れるのでクリックして選択する。ここでは「Web」と「Thymeleaf」を選択する。
  6. 選択されたものが表示されているので「Web」と「Thymeleaf」が選択されていることを確認する。
  7. クリックするとプロジェクトのファイル(ZIPファイル)のダウンロードが始まる。

start.spring.ioでプロジェクト作成

NetBeansでインポート

メニューから[ファイル]→[プロジェクトをインポート]→[ZIPから]を選択し、ダウンロードしたファイル(webapp.zip)を指定してインポートする。

インポート直後の構成

Spring Bootアプリケーションの実行

アプリケーションを実行するためのタスクが定義されているため、それを使う。

プロジェクトを右クリックして、[Task]→[boot]→[bootRun]を選択する。

アプリケーションが立ち上がると、出力ウィンドウの最後に「Started WebappApplication」と表示される。

その1行前に「Tomcat started on port(s): 8080 (http)」と出力されているということは、8080番ポートにアクセスすれば何らかの反応があるはずである。http://localhost:8080/にアクセスしてみよう。

サーバが出力したエラーページ

エラーメッセージが表示されたが、これは、まだコンテンツを返すプログラムを作っていないために表示されるもので、正常な動作である。アプリケーションを終了するには、出力ウィンドウの左側にあるStopボタンをクリックする。

Spring Web MVC frameworkの基礎中の基礎

処理の流れ

これから作るウェブアプリケーションはSpringのWeb MVCフレームワークを利用している。このフレームワークにおける処理の流れを次の図に示す(http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-servletより引用)。

Mvc

フロントコントローラがリクエストを適切なコントローラに渡し、コントローラが出力するデータをビュー(テンプレート)が表示するという仕組み。このうち、自分で作るのは、「コントローラ」と「ビューテンプレート」である。

まずは、単純に「こんにちは」と表示するようにコントローラとテンプレートを作ってみよう。

テンプレートを作る

テンプレートとは雛形のことであり、そこに流し込まれるデータとともに使うことが一般的な使い方であるが、まずはデータの流し込みを想定しないテンプレートを作成してみよう。

まず、テンプレートを格納するディレクトリを作成しよう。Resourcesのtemplatesにlecture03というディレクトリ(パッケージ)を作る。NetBeansのパッケージの表示の仕方によっては、作業が難しい。プロジェクトウィンドウの背景部分を右クリックし、「Javaパッケージの表示方法」を「ツリー」にすると階層構造がわかりやすい。

パッケージをツリーで表示

lecture03を右クリックして新規から「HTMLファイル」を選択する。ファイル名を「hello」と入力する。拡張子は自動でつくので入力しない。作成したら、次のように書き換えよう。

今の所、次の2点が注意する点で、これ以外は普通のHTMLファイルである。

  • 2行目: xmlns属性はXML名前空間の宣言である。テンプレートエンジンであるThymeleafはHTMLへの埋め込みをXMLによって行うことが大きな特徴である。名前宣言によってThymeleafによる操作をthというプレフィックスで行うことができる。
  • 4行目: 単独で使われるタグ(brやhr等)は後ろに/をつける(XMLのルール)。

コントローラを作る

コントローラは、リクエストに対してどのテンプレートを使うか(そしてテンプレートにどのような値を埋め込むか)ということを決定するためのものである。

具体的には@Controllerアノテーションをつけたクラスで、リクエストに対応してメソッドを定義する。メソッドの返り値としてテンプレートを指定する。

  • 12行目: コントローラを表すアノテーション
  • 13行目: このコントローラが、「/lecture03」のURLを担当することを表す。
  • 16〜19行目: /hello(つまりhttp://localhost:8080/lecture03/hello)に対応するメソッド
    • 16行目: /helloに対応すること表す
    • 17行目: 戻り値の型はStringにする。メソッド名はなんでも良い。
    • 18行目: テンプレートのルート(resources/templates)から利用するテンプレートの相対パス(拡張子は除く)

20行目以降は次節以降で解説する。

動作を確認

http://localhost:8080/lecture03/helloにブラウザでアクセスしよう。

テンプレートに値を渡す

  • メソッドにModelMap型の引数を加える。
  • そのaddAttributeメソッドで名前と値を登録する。
  • テンプレートでth:text属性を使う。

例1: サイコロ

アクセスするたびに表示される数が変化する。乱数については教科書P.108を参照。

例2: 挨拶

現在時刻によって表示されれる挨拶が変化する。時刻の扱いについては、教科書P.257を参照。

Gradleの利用


ビルドとは

ソフトウェアのビルド(英: build)は、ソースコードファイルを独立したソフトウェア生成物に変換するコンピュータ上で実行されるプロセス、またはその結果を指す。ビルドにおいて最も重要なのはコンパイルプロセスであり、ソースコードファイルを実行ファイルに変換する。

ビルド (ソフトウェア) – Wikipedia

要するに「コードを書いた後に実行できるようするまでにする作業」のこと。これまでは、NetBeansで行ってきた(「ファイルの実行」をクリックすることで、コンパイルが行われ実行された)。

Javaを用いた開発に使われるビルドツール

  • Ant
  • Maven
  • Gradle
  • etc…

Android用の開発環境であるAndroid StudioではGradleが採用されている。

NetBeansでGradleを使う

プラグイン

Gradle Supportプラグインをインストールすることで、Gradleベースのプロジェクトを作成・管理することができる。

プラグインを確認

インストール済みにあることを確認する。

プロジェクトの作成

Single Gradle Projectを選択。

プロジェクトを選択

プロジェクト名とメインクラス(メインメソッドを作るクラス)を指定する。メインクラスは完全修飾クラス名(パッケージ名を頭につける)で指定する。

プロジェクト名とメインクラス

プロジェクトには、メインクラス(Main.java)とビルドスクリプト(build.gradleとsettings.gradle)が作成される。

プロジェクトの構造

実行する

Main.javaに書き加える。

プロジェクトを右クリックして、Runをクリック。

runタスクの実行

出力ウィンドウに出力される。

出力ウィンドウ

外部のライブラリを使う

これまで、JDKに含まれるライブラリ(クラス)を使ってきたが、世の中には様々なライブラリが公開されている。ライブラリをプロジェクトに取り込んで使ってみよう。

GradleはMavenのリポジトリ(ライブラリが保管されているサーバ)からライブラリをダウンロードする機能があるので、Mavenのリポジトリから利用するライブラリを探すのが簡単だ。

今回は、HTMLパーサ(HTMLを解釈してあれこれするためのソフト)のJsoupを使ってみよう。MavenのリポジトリのJsoup 1.10.2のページにある、Gradle用の記述をコピーし、build.gradleのdependenciesにペーストする。

Mavenレポジトリからコピー

プロジェクトを右クリックして、Reload Projectをクリックすると、依存ライブラリがダウンロードされ、プロジェクトに追加される。

Dependenciesに追加される

Jsoupの利用例

ニュースのサイトからニュースのタイトル一覧を取得しよう。

注意: ブラウザで人間が操作するのではなく、機械的にページを取得する行為は一般に「ウェブスクレイピング(Web scraping)」というが、スクレイピングが想定されていない場合が多い(あるいは明示的に禁止されている)。ウェブから情報を取得するときには、まず専用のAPIがあるかどうか確認しよう。またスクレイピングによって情報を取得する場合も、その頻度を調整するなど、迷惑をかけないようにしよう(提供する側の立場としては、そもそもスクレイピングして欲しくない場合が多い)。

例外処理

教科書[P.65]を参照。

ポイント:

  • 例外(exception、エラーのこと)が発生したらcatch節に飛ぶ。
  • 例外の種類
    • チェック例外: 必ずtryでチェックしなければならない。
    • 非チェック例外: チェックしなくて良い。例えば、0で除算など。