メソッド名からクエリを作る
このレッスンでは、メソッド名からクエリが導き出される Spring Data JPA の規約を読み解き、同じ変換を自分の手で書けるようになります。
名前がそのまま仕様になります
リポジトリのインターフェースに、次のようなメソッドを1行足します。
Java
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByNameAndAge(String name, int age);
}本体はありません。それでもこのメソッドを呼ぶと name と age の両方で絞り込んだ結果が返ります。Spring Data JPA は起動時にメソッド名を単語に分解し、そこからクエリを組み立てて実装を生成しているからです。この仕組みをクエリメソッド、あるいは導出クエリと呼びます。
名前が仕様そのものになるので、綴りを間違えると起動時に落ちます。エンティティに存在しないプロパティ名を書いた場合、実行してみるまで気づかないのではなく、アプリケーションが立ち上がらないという形で即座に分かります。これは欠点ではなく利点です。
名前の組み立て方
メソッド名は、先頭の動詞、By、そして条件の並びという構造をしています。
| 部品 | 意味 | 例 |
|---|---|---|
findBy / readBy / getBy | 検索する | findByName |
countBy | 件数を数える | countByAge |
existsBy | 存在を確かめる | existsByEmail |
deleteBy | 削除する | deleteByName |
And / Or | 条件をつなぐ | findByNameAndAge |
OrderBy + プロパティ + Asc / Desc | 並び替える | findByAgeOrderByNameAsc |
条件そのものにも修飾が付けられます。よく使うものを挙げます。
| 書き方 | 生成される条件 |
|---|---|
findByName | name = ? |
findByNameContaining | name LIKE %?% |
findByNameStartingWith | name LIKE ?% |
findByAgeGreaterThan | age > ? |
findByAgeLessThan | age < ? |
findByAgeBetween | age BETWEEN ? AND ? |
findByNameIsNull | name IS NULL |
findByNameNot | name <> ? |
プロパティ名はキャメルケースで書き、先頭を小文字に戻したものがそのまま列に対応します。findByUserName なら userName プロパティを見に行きます。
この表は Spring Data JPA が実際に解釈できる語彙です。ただし今回の演習で自分の手で書くのは、このうち等値の条件を And と Or でつないだ形だけです。Containing や GreaterThan まで自分で実装しようとすると、それだけで演習が終わってしまうためです。残りは Spring がやってくれる範囲として、表で見て知っておけば十分です。
便利さの限界も知っておきます
条件が2つ3つのうちは読みやすい仕組みですが、増えるとメソッド名が壊滅的に長くなります。
Java
List<User> findByNameContainingAndAgeGreaterThanAndActiveTrueOrderByCreatedAtDesc(
String name, int age);こうなったら @Query で JPQL を直接書くか、後のレッスンで扱う動的な組み立てに切り替える合図です。目安として、条件が3つを超えたあたりから名前で表現するのをやめるとよいでしょう。
演習で書くもの
演習では、Spring Data JPA が起動時にやっている変換そのものを書きます。findByNameAndAge のようなメソッド名を文字列で受け取り、対応する SQL 相当の文字列を組み立てて返します。実装する規則は次の表の通りです。テーブル名は常に users とします。
| 入力 | 出力 |
|---|---|
findAll | SELECT * FROM users |
findByName | SELECT * FROM users WHERE name = ? |
findByNameAndAge | SELECT * FROM users WHERE name = ? AND age = ? |
findByNameOrEmail | SELECT * FROM users WHERE name = ? OR email = ? |
findByNameAndAgeAndEmail | SELECT * FROM users WHERE name = ? AND age = ? AND email = ? |
規則をまとめます。先頭の findBy を取り除いた残りを、区切り語 And と Or で分割します。分割して得られた各プロパティ名は先頭を小文字にして列名とし、列名 = ? の形にします。区切り語 And は SQL では AND、Or は OR として、元の並び順のまま条件の間に挟みます。入力が findAll のときだけ WHERE 句を付けません。入力が findBy で始まらない場合は UNSUPPORTED を返してください。
区切り語は大文字で始まる単語として現れるので、And と Or の位置を文字列として探せば分割できます。プロパティ名の途中に And を含む語は今回は考えなくてかまいません。
要件
- findAll のときは SELECT * FROM users を返すこと
- findBy で始まるときは条件を And と Or で分割し、列名 = ? を AND と OR でつないだ WHERE 句を組み立てること
- プロパティ名の先頭は小文字に変換すること。findAll でも findBy 始まりでもない場合は UNSUPPORTED を返すこと
入出力例
toSql("findAll") → "SELECT * FROM users"
toSql("findByName") → "SELECT * FROM users WHERE name = ?"
toSql("findByNameAndAge") → "SELECT * FROM users WHERE name = ? AND age = ?"
toSql("findByNameOrEmail") → "SELECT * FROM users WHERE name = ? OR email = ?"
toSql("findByNameAndAgeAndEmail") → "SELECT * FROM users WHERE name = ? AND age = ? AND email = ?"
toSql("findByUserNameAndAge") → "SELECT * FROM users WHERE userName = ? AND age = ?"
toSql("saveAll") → "UNSUPPORTED"