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
- 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」を実行する(前回からの差分)。
1 2 |
compile('org.springframework.boot:spring-boot-starter-jdbc') compile('com.h2database:h2') |
これで、必要なファイル等が追加される。
ウェブインターフェースでデータベースに接続する
H2には、ブラウザからデータベースを操作できるインターフェースが内蔵されている。これはSpring Bootアプリケーションに組み込んだ状態でも利用することができる。
ウェブインターフェースを利用可能にするため、下記の1行をapplication.propertiesに加えよう。
1 |
spring.h2.console.enabled = true |
bootRunで実行し、http://localhost:18080/h2-consoleにアクセスする。
次に各設定項目を入力しよう。
- JDBC URL: データベースの接続を表すURL。これにはどのディレクトリにデータを置くか、という情報が含まれる。今回は、ホームディレクトリに「webappdb」というディレクトリを作り、その中に「database」というファイル名で作るということを指定する。この場合、JDBC URLは「jdbc:h2:~/webappdb/database」となる(実際には「database.mv.db」等のファイルが作られる)。
- User Name: データベースに接続する時のユーザ名を表す。今回はデフォルトのまま「sa」とする。
- Password: データベースに接続する時のパスワードを表す。今回は「sa」とする。
入力して、Connectボタンをクリックすると、SQLの入力画面となる。
テーブルを作成する
掲示板のコメントを格納するためのテーブルを作成しよう。
次のSQL文を入力して、Runボタンをクリックするとテーブルが作成される。
1 2 3 4 5 6 |
CREATE TABLE bbs_comment ( id INT AUTO_INCREMENT PRIMARY KEY, body TEXT, name TEXT, date TIMESTAMP ) |
「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命令を使う。
1 |
INSERT INTO bbs_comment ( body, name, date ) VALUES ('こんにちは', 'たろう', '2017-06-21 13:05:00') |
うまくいったら、「Update count: 1」と表示される。適当に値を変えて、さらにデータを入力してみよう。
テーブルからレコードを検索・取得して表示する
データの検索・取得には、SELECTを使う。先ずは全件取得してみよう。
1 |
SELECT * FROM BBS_COMMENT |
「*」は全てのカラムの情報(ここでは、id、name、body、date)を取得することを示す。
一部のデータを表示(検索)するには、WHERE節を追加する。
1 |
SELECT * FROM BBS_COMMENT WHERE NAME = 'たろう' |
これは、nameカラムの値が「たろう」であるレコードだけが表示される。
プログラムから扱う
プログラムから扱うために、接続についての情報をapplication.propertiesに加えよう。ウェブインターフェースでデータベースに接続する時の情報と同じにする。
1 2 3 4 |
spring.datasource.driver-class-name = org.h2.Driver spring.datasource.url = jdbc:h2:~/webappdb/database spring.datasource.username = sa spring.datasource.password = sa |
コントローラは次のようになる(GitHub)。
ベースとなっているLecture05Controllerと比較しながらみていこう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
package net.teachingprogramming.webapp; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; /** * 第10回で使うコントローラ */ @Controller @RequestMapping("/lecture10") public class Lecture10Controller { private final JdbcTemplate jdbcTemplate; /** * コンストラクタ */ @Autowired public Lecture10Controller(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } /** * 掲示板: get */ @GetMapping("/bbs") public String bbsGet(ModelMap modelMap) { List<BbsComment> commentList = new ArrayList<>(); // 表示するコメントのリスト List<Map<String, Object>> dataList = jdbcTemplate.queryForList("SELECT * FROM bbs_comment"); // データベースから取り出す。 for (Map<String, Object> data : dataList) { BbsComment comment = new BbsComment(); comment.body = (String)data.get("body"); comment.name = (String)data.get("name"); comment.date = (Date)data.get("date"); commentList.add(comment); } modelMap.addAttribute("commentList", commentList); return "lecture05/bbs"; } /** * 掲示板: post */ @PostMapping("/bbs") public String bbsGet(@RequestParam("body") String body, @RequestParam("name") String name) { Date date = new Date(); jdbcTemplate.update("INSERT INTO bbs_comment ( body , name, date ) VALUES (?, ?, ?)", body, name, date); return "redirect:/lecture10/bbs"; // http://localhost:18080/lecture10/bbsにリダイレクト(GET) } } |
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文を利用してデータベースから削除する機能を付け加えよう。