こんにちは。タグボート開発チームのワシヅカです。
最近冬に備えて少しずつ肥えてきました。越冬の準備です。
今日は、2回目から使っているサンプルプロジェクトのDBアクセス部分を中心に確認していきたいと思います。
1.ログ出力レベルの変更
…と作業の前に。ログレベルを設定しておきましょう。
下のように変更しておくと、データベースアクセスが発生した際にSQLログを出力してくれます。
application.propertiesに以下の1行を追加しておきましょう。
2.「UserRepository.java」を読み解く
それでは、DBアクセス部分を確認していきたいと思います。
まずは、「MainController」クラスの「getAllUsers」メソッドを覗いてみましょう。このメソッドは、たった1行です。
とだけコードが書いてあります。「UserRepository」インターフェースの「findAll」メソッドを呼び出してUSERテーブルからデータ抽出を行っています。また、「MainController」クラスの中で、「@Autowired」アノテーションとともに「UserRepository」インターフェースを宣言することで、「UserRepository」インターフェースは自動的に利用可能な状態になっています。
英語で書かれているコメント行にもそのようなことが書かれていますね。
続いて「UserRepository」インターフェースの中身を見てみましょう。
なんと、このインターフェースは中身がほとんど書かれていません。しかしよく見ると「CrudRepository」を継承していることがわかります。実はこの継承がポイントです。継承の階層構造を見てみましょう。
CrudRepositoryにポインタを合わせ、右クリック→[型階層を開く]とすると「型階層」タブにされます。
CrudRepositoryにはたくさんのメソッドがあることが分かります。
saveメソッドにctrlキー押しながらポインタをあわせ、[実装を開く]をクリックしてみましょう。
実際の実装を確認する事ができます。「SimpleJpaRepository」クラスで実装されているようです。細かい実装を追い続けると「AbstractEntityManagerImpl」あたりの深いところまで行ってしまいそうなので、今回はこの先は追いませんが、このような階層構造を引き継ぎながら動いていることが分かります。実はこのデータベース周りの処理はJPA(Java Persistence API)というAPIを利用して実現しています。
サンプルプロジェクトのpom.xmlにも「JPAを使っているよ」と書かれています。
3.条件を付けて検索可能にしてみる(WHERE句)
さて、この「UserRepository」クラスで名前(name)カラムを条件に検索可能なメソッドを定義してみましょう。
以下の1行を追加してください。
また「MainController」クラスには以下のような呼出メソッドを追加します。
追加し終わったら、アプリケーションを実行してブラウザで以下のURLにアクセスしてみましょう。
http://localhost:8080/demo/user-by-name?name=hogehoge
nameカラムに”hogehoge”を持つデータが取得できました。ログも確認してみましょう。
ログにもしっかりWHERE句がついて出力されていますね。なんて簡単で便利なんでしょう!
4.並び順をつけて検索可能にしてみる(ORDER BY句)
もう1つおまけに、並び順も付けてみましょう。
今度は、名前(nameカラム)を条件にし登録が新しい順(IDが大きい順)で取得できるメソッドを定義してみます。先ほどと同様にUserRepositoryクラスに以下のようにメソッドを定義してあげます。
コントロールクラスは、先ほどのメソッドを使ってしまいましょう。
呼出メソッドに「OrderByIdDesc」と追加してあげます。
データベースのデータの状態は以下のような感じです。
先ほどと同じURLを叩くことでデータが取得可能です。
http://localhost:8080/demo/user-by-name?name=hogehoge
見事にname=”hogehoge”を条件に、idを降順に並び替えたデータを取得することができました。このように、メソッド名の命名規則によって検索クエリを変更することが可能となっています。
5.データを更新(UPDATE)する
だいたい要領がつかめてきました。
さて、データの更新はどのメソッドを使えば良いのでしょうか。
これは、saveメソッドを使います。実は先ほど深追いしかけた際に「em.merge(entity);」というキーワードがありました。(このページの5枚目のスクリーンショットです。)saveメソッドは、テーブルキーに対してデータが無ければINSERT、データが有れば更新という動作をしてくれます。
試しに以下のようなデータを更新するメソッドをコントローラクラスに追加してみます。
サンプルのUserクラス(エンティティークラス)はidが@Idアノテーションによりキーとなっています。
「UserRepository」インターフェースの定義を少し修正します。
●変更前
public interface UserRepository extends CrudRepository<User, Long>
●変更後
public interface UserRepository extends CrudRepository<User, Integer>
書き終わったら実行してみます。実行前のDBの状態は以下のようになっています。
ブラウザから以下のURLにアクセスしてみましょう。
http://localhost:8080/demo/update-user?id=9&name=kyapikyapi&email=email99
ブレイクポイントを事前に張っておきました。パラメータが予定通りリクエストされています。F8キーを押して再開してみましょう。
データベースが更新されて、画面へ値が返却されました。
5.データ更新中にエラーが発生した場合のロールバック
更新中にエラーが発生した場合のロールバックについて確認してみましょう。
トランザクションの管理は@Transactionalアノテーションで実現できます。クラス(またはメソッド)の前に@Transactionalアノテーションを付けることで Exceptionが発生した場合、ロールバックしてくれるようになります。
今回はすべてのエラーと例外のスーパークラスであるThrowable.classが発生したのをトリガーにロールバックするように書いています。
では、実際にSQL実行エラーを発生させてみましょう。varchar(255)で定義されているnameカラムにそれ以上の文字列のデータを入力してみましょう。下は実行してブレークを張った状態です。nameプロパティーに長い文字列が代入されていることが分かります。そのままF8キーで再開します。
エラーが発生しました。nameカラムに長過ぎるデータが発生しているとログ出力されています。
画面を見てみると「Exceptionが発生して実行できません」と表示されています。
肝心のデータベースを確認してみましょう。データは更新されずに、実行前のままとなっています。
こんな風に書いて、意図的にRuntimeExceptionをスローしても良かったかもしれません。
余談ですが、継承元をJpaRepositoryに変更するとページャーのような一部データ取得のようなこともできそうです。これについては別の機会に検証してみたいと思います。
6.まとめ
- DBアクセスは、Entityクラス、Repositoryインターフェースを使ってアクセスする
- 今回のサンプルの場合、Entityクラス=Userクラス、Repositoryクラス=UserRepositoryインターフェースである
- RepositoryインターフェースはCrudRepository、(またはJpaRepository)を継承する
- CrudRepositoryは、エンティティークラスとキーとなるプロパティーの型を書く
- 命名規則に従ってRepositoryインターフェースにメソッドを追加することでWHERE句やORDER BY句の着いたSQLを実行することができる
- ロールバックは@Transactionalアノテーションを付与しトランザクション管理を行うことで実現できる
今回もだいぶ長くなってしまいました。
いかかでしたでしょうか?ここまでサンプルプロジェクトを少し拡張しながら見てきました。ここまでの話は、実はGetting Startedのサンプル解説ページ(英語)で紹介されていますが、少し機能追加しながら見てきました。
次回は、ThymeLeafを使ったHTML出力を紹介したいと思います。























