Spring JpaRepository
Spring Data JPAは、JpaRepositoryという機能を使用すると、とても簡単にデータを検索できるようになります。ここでは、その基本的な使い方を説明します。
JpaRepositoryインターフェースの作成
以前、基本的なデータベースアクセスについて大まかに説明しました。そのとき、データ検索にはJPQLというクエリ文を使用できることを説明しましたが、覚えていますか。
この説明を読んで、思わずため息をついた人もきっと多かったでしょう。SQLを見たくないからフレームワークを使おうとしたのに、まさかSQLのようなものがあるとは思わなかったかもしれません。
それでも、まだ安心して大丈夫です。Spring Frameworkには、もっと賢くデータ検索を行う仕組みがきちんと用意されています。それがJpaRepositoryです。
JpaRepositoryはインターフェースです。インターフェースにあらかじめ検索メソッドを定義しておくことで、メソッドを呼び出すだけでスマートにデータ検索ができるようになります。「結局、そのメソッドの中にJPQLを使った検索処理を書くのでは」と思ったなら、もう少し待ってください。決して損にはなりません。
まず、JpaRepositoryインターフェースを作成してみましょう。com.devkuma.spring.dbパッケージにSampleEntityRepository.javaというファイル名でソースコードファイルを作成します。そして、次のようにインターフェースを書きます。
package com.devkuma.spring.db;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SampleEntityRepository
extends JpaRepository <SampleEntity, Long> {
}
JpaRepositoryインターフェースは、org.springframework.data.jpa.repositoryパッケージのJpaRepositoryというインターフェースを継承して作成します。このインターフェースは汎用的に使用します。コードを見ると、次のような形で書かれていることがわかります。
public interface 名前 extends JpaRepository <エンティティ, ID型>
<>の中には、エンティティクラス名とIDフィールドの型を指定します。注意すべき点は、基本型の場合はラッパークラスを指定することです。サンプルのSampleEntityクラスはIDにlong型を指定しているため、ここでは<SampleEntity, Long>と書きます。
もう一つ注意すべき点はアノテーションです。クラス宣言の前に@Repositoryというアノテーションが付いています。これにより、このインターフェースがJpaRepositoryであることを示します。必ず付けておきましょう。
Bean設定ファイルの準備
では、Bean設定ファイルを準備してみましょう。すでにdbbean.xmlというファイルを用意しています。それをそのまま再利用します。
次のようにdbbean.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:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jdbc:embedded-database id="dataSource" type="H2" />
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="true" />
</bean>
</property>
</bean>
<jpa:repositories base-package="com.devkuma.spring.db" />
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
</beans>
今回はBean関連のタグが2つ追加されています。まず、JpaRepositoryに関するタグが1つ含まれています。
<jpa:repositories base-package="com.devkuma.spring.db"/>
この<jpa:repositories>タグにはbase-packageという属性が含まれています。これで検索する場所、つまりパッケージを指定します。こうすると、指定されたパッケージ内からJpaRepositoryを探し、Bean化します。
もう一つは、トランザクションマネージャーというBeanです。これはトランザクションを管理するためのもので、次のように書かれています。
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
ここで定義しているのはtransactionManagerという名前のBeanで、classにはJpaTransactionManagerクラスを指定します。ほかにentityManagerFactoryというプロパティがあり、これは作成するBeanで使用するEntityManagerFactoryを指定します。上にあるid="entityManagerFactory"をrefで指定しています。
これでJpaRepositoryを利用するために必要なBeanの準備ができました。「先ほど準備したSampleEntityRepositoryはBeanとして準備しなくてよいのか」と思うかもしれませんが、これは必要ありません。<jpa:repositories>によって指定されたパッケージを検索し、@Repositoryが指定されたJpaRepositoryをBeanとして自動登録します。したがって、別途<bean>を用意する必要はありません。
アプリケーションでJpaRepositoryを利用する
では、JpaRepositoryを利用してみましょう。
com.devkuma.spring.dbパッケージにあるAppクラスを次のように修正します。
package com.devkuma.spring.db;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
private static EntityManager manager;
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("dbbean.xml");
EntityManagerFactory factory = app.getBean(EntityManagerFactory.class);
manager = factory.createEntityManager();
SampleEntityRepository repository = app.getBean(SampleEntityRepository.class);
// make dummy
makeDummyData();
// get list
List list = repository.findAll();
printList(list);
System.out.println("...ok.");
}
// create dummy entity data.
public static void makeDummyData() {
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
manager.persist(new SampleEntity("tuyano", "syoda@tuyano.com"));
manager.persist(new SampleEntity("hanako", "hanako@flower"));
manager.persist(new SampleEntity("taro", "taro@yamada"));
manager.persist(new SampleEntity("sachiko", "sachico@happy"));
manager.flush();
transaction.commit();
}
// print all entity.
public static void printList(List list) {
for (Object item : list) {
System.out.println(item);
}
}
}
そして実行します。すると、ダミーとして作成しているすべてのエンティティが表示されます。
ここでは、まずApplicationContextからSampleEntityRepositoryインスタンスを取得します。
SampleEntityRepository repository =
app.getBean (SampleEntityRepository.class);
これでBeanを取得できます。dbbean.xmlにはBeanの定義は含まれていませんが、このようにきちんとBeanを取り出せます。これも@Repositoryのおかげです。
List list = repository.findAll ();
そしてfindAllメソッドを呼び出して、エンティティ一覧を検索します。「このようなメソッドはいったいどこにあるのか」と思った人もいるでしょう。これは、SampleEntityRepositoryが継承しているJpaRepositoryに用意されているためです。
クラスはいったいどこにあるのか
ところで、このコードを見て少し不思議に思った人もいるでしょう。SampleEntityRepositoryはインターフェースです。クラスではありません。それなのに、当然のようにBeanを取得できます。
JpaRepositoryはインターフェースを用意するだけで、自動的にクラスを作成し、Beanを生成します。必要なのはインターフェースだけです。
Bean設定クラスを利用する
Beanは設定ファイルを使用せず、クラスで定義できます。これも試してみましょう。
com.devkuma.spring.dbパッケージのSampleEntityConfigクラスのソースコードを次のように修正します。
package com.devkuma.spring.db;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
@Configuration
@EnableJpaRepositories("com.tuyano.libro.db")
@EnableTransactionManagement
class SampleEntityConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.H2).build();
}
@Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new
LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.tuyano.libro.db");
factory.setDataSource(dataSource());
factory.afterPropertiesSet();
return factory.getObject();
}
@Bean
protected JpaTransactionManager transactionManager
(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
そして、Appで次の文を修正し、Bean設定ファイルではなくこのSampleEntityConfigクラスを利用するように変更します。
ApplicationContext app = new ClassPathXmlApplicationContext("dbbean.xml");
↓
ApplicationContext app = new AnnotationConfigApplicationContext(SampleEntityConfig.class);
修正したらAppを実行してみましょう。先ほど見たように、ダミーデータが一覧表示されます。ここでまず目を引くのは、クラス宣言の前にあるアノテーションです。
@EnableJpaRepositories("com.devkuma.spring.db")
これはJpaRepositoryを有効にするためのものです。このアノテーションを書くことで、指定されたパッケージを検索し、@Repositoryを付けたクラスをBeanとして登録します。括弧の中には検索するパッケージを指定する必要があります。<jpa:repositories>に相当するものと考えてよいでしょう。
@EnableTransactionManagement
これはTransactionManagementを使用するためのものです。これを用意しておくことで、TransactionManagerが有効になります。
@Beanアノテーションを付けたBean生成用メソッドには、JpaTransactionManagerというメソッドが追加されています。これによりJpaTransactionManagerインスタンスを作成して返します。見ればわかるように、new JpaTransactionManagerしたものを返しているだけです。引数にはEntityManagerFactoryインスタンスを渡すようにしています。
見てわかるように、用意されているBean生成メソッドにはリポジトリ生成用のものはありません。リポジトリは自動的にBean化されることがよくわかります。
JpaRepositoryの標準メソッド
ここではSampleEntityRepositoryインターフェースを用意しましたが、その内容は空のままでした。それでもfindAllというメソッドを呼び出して、全エンティティを検索できました。
継承元のJpaRepositoryには、標準でいくつかのメソッドが定義されています。まずそれらを使用すれば、データベースの基本的な操作が可能です。ここで主なメソッドを簡単に整理します。
findAll()
すでに登場しました。全エンティティをまとめたListを返します。
getOne("ID")
IDを指定してエンティティを1つ取得します。引数には、そのエンティティのIDに指定された型の値が入ります。
saveAndFlush(エンティティ)
引数に指定されたエンティティをデータベースに保存します。
delete("ID")
引数に指定されたIDのエンティティをデータベースから削除します。
count()
エンティティの数をint値として返します。
検索についてはfindAllしかありませんが、エンティティの保存や削除などのメソッドも備わっています。これらのメソッドは、トランザクション処理を開始する必要もありません。単にメソッドを呼び出すだけで、簡単に保存や削除ができます。
JpaRepositoryにメソッドを追加する
データベースの中核となるのは、何といっても検索です。必要に応じてデータの範囲を指定して検索できてこそ、データベースの力を発揮できます。
ところが、JpaRepositoryには検索関連メソッドがfindAllしかありません。これでは使えないと思ったかもしれません。しかし実は、JpaRepositoryの真の力は検索にあります。インターフェースに検索用メソッドを追加することで、必要な検索メソッドを増やせます。
実際にやってみましょう。次を参考に、SampleEntityRepositoryインターフェースを修正します。
package com.devkuma.spring.db;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SampleEntityRepository
extends JpaRepository<SampleEntity, Long> {
public List<SampleEntity> findByNameLike(String name);
public List<SampleEntity> findByMailEndingWith(String mail);
}
ここでは次の2つの文が追加されています。
public List <SampleEntity> findByNameLike(String name);
public List <SampleEntity> findByMailEndingWith(String mail);
1つ目のfindByNameLikeは、nameの値を使ってlike検索するためのものです。2つ目のfindByMailEndingWithは、mail値が指定されたテキストで終わるエンティティを検索します。
ただメソッド宣言を書いただけですが、注意すべきなのはメソッド名です。これは勝手に変更してはいけません。必ずここに挙げた名前どおりにメソッド宣言を書きましょう。このようなメソッド名には意味があります。変更すると正しく動作しません。
JpaRepositoryのメソッドを利用する
では、次を参考にApp.javaを変更しましょう。
package com.tuyano.libro.db;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
private static EntityManager manager;
public static void main(String[] args) {
//ApplicationContext app = new
AnnotationConfigApplicationContext(SampleEntityConfig.class);
ApplicationContext app = new ClassPathXmlApplicationContext("dbbean.xml");
EntityManagerFactory factory = app.getBean(EntityManagerFactory.class);
manager = factory.createEntityManager();
makeDummyData();
// get repository
SampleEntityRepository repository = app.getBean(SampleEntityRepository.class);
// get list
List list1 = repository.findByNameLike("%ko");
System.out.println("*Find By Name*");
printList(list1);
List list2 = repository.findByMailEndingWith(".com");
System.out.println("*Find By Mail*");
printList(list2);
System.out.println("...ok.");
}
// create dummy entity data.
public static void makeDummyData() {
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
manager.persist(new SampleEntity("tuyano", "syoda@tuyano.com"));
manager.persist(new SampleEntity("hanako", "hanako@flower.uk"));
manager.persist(new SampleEntity("taro", "taro@yamada.jp"));
manager.persist(new SampleEntity("sachiko", "sachico@happy.com"));
manager.flush();
transaction.commit();
}
// print all entity.
public static void printList(List list) { ...省略... }
}
コードを見ると、必要なエンティティを簡単に検索できることがわかります。
nameで検索
List list1 = repository.findByNameLike("%ko");
検索結果:
SampleEntity [id=2, name=hanako, mail=hanako@flower.uk]
SampleEntity [id=4, name=sachiko, mail=sachico@happy.com]
mailで検索
List list2 = repository.findByMailEndingWith(".com");
検索結果:
SampleEntity [id=1, name=tuyano, mail=syoda@tuyano.com]
SampleEntity [id=4, name=sachiko, mail=sachico@happy.com]
findByNameLike("%ko")は、name値がkoで終わるエンティティを検索します。また、findByMailEndingWith("com")では、mail値が.comで終わるエンティティが検索されることがわかります。必要なものだけをきちんと検索できます。
ここで注意して見てほしいのは、SampleEntityRepositoryにはメソッドの宣言をしただけだという点です。つまり、これらのメソッドで実際に行う処理はまったく書いていません。ただメソッドを宣言しただけで、正しく動作するメソッドが自動生成されます。
これがJpaRepositoryの力です。JpaRepositoryは、あらかじめ決められた形式のメソッド名を書くと、その名前をもとに実際に動作するメソッドを自動生成します。
JpaRepositoryのメソッド命名規則
JpaRepositoryは、メソッド名の書き方さえわかれば、必要なメソッドをすぐに書いて追加できます。処理コードは一切必要ありません。ただメソッド宣言文があればよいのです。ただし、あらかじめ決められたルールに従って、正しくメソッド名を付ける必要があります。そのルールに従って名前を付けるだけで、メソッドが自動生成されます。
では、どのような形でメソッド名を付ければよいのか、その命名規則を簡単に整理すると次のようになります。
- findByXX
基本はこれです。
findByの後にエンティティの属性名を付けます。この属性名は先頭文字を大文字にします。例えば、nameで検索するならfindByName、mailで探すならfindByMailになります。
この後には、基本形であるfindByXXの後ろに続けて書いていきます。
- Like / NotLike
あいまい検索に関するものです。Likeを付けると、引数に指定されたテキストを含むエンティティを検索します。また、NotLikeを書くと、引数のテキストを含まないものを検索します。findByNameLikeなら、nameから引数のテキストであいまい検索します。 - StartingWith / EndingWith
テキスト値で、引数に指定されたテキストから始まるもの、または終わるものを検索するためのものです。findByNameStartingWith("A")なら、nameの値がAで始まる項目を検索します。 - IsNull / IsNotNull
値がnull、またはnullではないものを検索します。引数は不要です。findByNameIsNull()なら、nameの値がnullのものだけを検索します。 - True / False
boolean値でtrueのもの、またはfalseのものを検索します。引数は不要です。findByCheckTrue()なら、checkという項目がtrueのものだけを検索します。 - Before / After
時間値で使用します。引数に指定した値より前のもの、または後のものを検索します。findByCreateBefore(new Date())とすると、createという項目の値が現在より前のものだけを探します。createがDateの場合です。 - LessThan / GreaterThan
数値で使用します。その項目の値が引数より小さい、または大きいものを検索します。
findByAgeLessThan(20)なら、ageの値が20より小さいものを探します。 - Between
2つの値を引数に取り、その2つの値の間にあるものを検索します。例えば、
findByAgeBetween(10, 20)なら、age値が10以上20以下のものを検索します。数値だけでなく、時間の項目などにも使用できます。
このほかにもまだありますが、ひとまずこれらを使えるようになれば、基本的な検索はおおむね可能になります。JpaRepositoryをうまく扱えるようになると、コーディングをあまりしなくてもさまざまなデータ検索ができます。とても簡単なので、サンプルをもとにいろいろ試してみてください。