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で検索するならfindByNamemailで探すなら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という項目の値が現在より前のものだけを探します。createDateの場合です。
  • LessThan / GreaterThan 数値で使用します。その項目の値が引数より小さい、または大きいものを検索します。findByAgeLessThan(20)なら、ageの値が20より小さいものを探します。
  • Between 2つの値を引数に取り、その2つの値の間にあるものを検索します。例えば、findByAgeBetween(10, 20)なら、age値が10以上20以下のものを検索します。数値だけでなく、時間の項目などにも使用できます。

このほかにもまだありますが、ひとまずこれらを使えるようになれば、基本的な検索はおおむね可能になります。JpaRepositoryをうまく扱えるようになると、コーディングをあまりしなくてもさまざまなデータ検索ができます。とても簡単なので、サンプルをもとにいろいろ試してみてください。