Spring Data JPAの利用

データベースアクセスのためのフレームワークとしてSpring Frameworkに含まれているのがSpring Data JPAです。これは名前のとおり、JPAを利用してデータアクセスを行うためのものです。ここでは、その基本的な使い方について説明します。

Spring Data JPAとpom.xml

データベース関連は、さまざまなライブラリやフレームワークが最も多く含まれている分野です。HibernateなどのORM(Object Relational Mapping)は、SQLなどを使用するデータベースアクセスとJavaのオブジェクトコードを一貫させるための技術として広く使用されています。

そのようなORM関連技術の中でも、Javaの標準的な技術として浸透しているのがJPA(Java Persistence API)です。もっとも、JPA自体はORMのための技術というより、SQLをJavaらしく簡単に使えるようにするものという印象ですが、データベースとJavaオブジェクトを仲介する基本的な技術として、多くのフレームワークでも使用されています。

Spring Frameworkにも、JPAを利用してデータベースへアクセスするSpring Data JPAというライブラリが含まれています。これはSpring Dataというデータベースアクセス関連ライブラリの一つです。ほかにも、非SQLのMongoDBを利用するSpring Data MongoDBや、Hadoop利用のためのSpring Data Hadoopなどが用意されています。Spring Data JPAは、一般的なSQLによるリレーショナルデータベース全般を利用するためのもので、Spring Dataの基本となるライブラリと言ってよいでしょう。

では、このSpring Data JPAを利用してみましょう。まずpom.xmlにライブラリを追加します。次のようにソースコードを書き換えます。例として、Spring Framework 4.1.7に合わせたバージョン構成になっています。

<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
 
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.springframework.samples</groupId>
    <artifactId>MySpringApp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
 
    <properties>
        <java.version>1.6</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
 
    <dependencies>
 
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.7.RELEASE</version>
        </dependency>
 
        <!-- Database (H2) -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.187</version>
        </dependency>
 
        <!-- JPA Provider (Hibernate) -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>4.3.10.Final</version>
        </dependency>
 
        <!-- Spring Data JPA -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.8.1.RELEASE</version>
        </dependency>
 
    </dependencies>
</project>

今回は合計3つの<dependency>タグを新しく追加しました。それぞれ簡単に説明します。

H2

グループIDにcom.h2database、アーティファクトIDにh2を指定しています。H2はJava SQLデータベースエンジンです。データベースファイルへ直接アクセスしてデータを保存できます。通常、データベースというとMySQLやPostgreSQLなどを思い浮かべるでしょう。H2のようにJavaライブラリとして実装されたデータベースエンジンは、データベースサーバーのインストールなど面倒な作業が不要で、気軽に利用できるため学習用に適しています。そこで今回は、H2をデータベースとして利用してSpring Data JPAを使います。

Hibernate EntityManager

JPAでは、データベースのデータをエンティティという形でオブジェクト化します。このエンティティを管理するのがエンティティマネージャー(EntityManager)です。これはいくつか種類がありますが、自分が使いやすいものを選んで構成できます。今回はHibernateが生成するエンティティマネージャーを使用します。グループIDはorg.hibernate、アーティファクトIDはhibernate-entitymanagerを指定します。

Spring Data JPA

これがSpring Data JPA本体のライブラリです。グループIDはorg.springframework.data、アーティファクトIDはspring-data-jpaをそれぞれ指定します。

以上3つのライブラリをpom.xmlに追加することで、Spring Data JPAを使用できるようになります。H2は、ほかのデータベースサーバーを使用する場合はもちろん不要です。

persistence.xmlの作成

まず、Bean設定ファイルを利用する形でSpring Data JPAを使ってみましょう。

Spring Data JPAを使用するには、プロジェクトにいくつか準備すべきものがあります。その一つがpersistence.xmlというファイルです。このファイルには、持続性単位(Persistence unit)と呼ばれる情報が含まれます。

JPAでデータベースアクセスを実行するために、持続性というものを提供します。つまり、このpersistence.xmlの中には、アクセスするデータベースに関する情報を記述しておくと考えればよいでしょう。

このファイルはWebアプリケーションのMETA-INFフォルダに保存します。Mavenプロジェクトの場合、src/mainにあるresourcesフォルダがWebアプリケーションのディレクトリです。このresourcesフォルダにMETA-INFというフォルダを作成し、その中にpersistence.xmlという名前でファイルを作成して、次の内容を書きます。

<?xml version="1.0" encoding="UTF-8"?>
<persistence
    xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence.xsd">
 
    <persistence-unit name="persistence-unit"
        transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <property name="hibernate.diarect" value="${hibernate.dialect}" />
            <property name="hibernate.hbm2ddl.auto" value="${hibernate.hbm2ddl.auto}" />
            <property name="javax.persistence.jdbc.driver" value="${db.driver}" />
            <property name="javax.persistence.jdbc.url" value="${db.url}" />
            <property name="javax.persistence.jdbc.user" value="${db.user}" />
            <property name="javax.persistence.jdbc.password" value="${db.password}" />
        </properties>
    </persistence-unit>
     
</persistence>

このpersistence.xmlでは、<persistence>タグ内に<persistence-unit>タグを入れます。これが持続性単位の情報を記述するタグです。この中で<provider>タグにプロバイダークラスを指定し、さらに<properties>タグに必要な情報を属性として記述しています。記述されている属性は次のとおりです。

hibernate.diarect
hibernate.hbm2ddl.auto

これらはHibernatePersistenceに必要です。Dialectはデータベースと相互作用するクラスで、起動時および終了時のデータベース処理に関する設定です。ここではH2を利用するためのDialectと、起動時に新しいデータベースを用意し、終了時に削除する設定をしています。

javax.persistence.jdbc.driver
javax.persistence.jdbc.url
javax.persistence.jdbc.user
javax.persistence.jdbc.password

これらはデータベースドライバー、アクセスURL、アクセスに使用するユーザー名とパスワードなどをそれぞれ設定するものです。

用意されている属性は、使用するエンティティマネージャーによって多少異なる場合があります。今回はHibernateライブラリを利用しているため、それ専用の値が属性に追加されています。ほかのエンティティマネージャーを使用する場合は、別の属性を使用する必要があるかもしれません。

application.propertiesの作成

作成したpersistence.xmlの内容を見ると、<property>タグに含まれている属性値は${XX}という形で書かれています。これは、プロパティファイルに含まれている値を差し込んで使用する形になっているということです。

プロジェクトのresourcesフォルダにapplication.propertiesというプロパティファイルを作成します。ここにpersitence.xmlに必要な値を記述します。次の内容で作成しましょう。

# Database Configuration
db.driver=org.h2.Driver
db.url=jdbc:h2:mem:datajpa
db.username=sa
db.password=
  
# Hibernate Configuration
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.hbm2ddl.auto=create-drop

ここではdb.XXのような項目とhibernate.XXのような項目が作成されています。dbで始まる項目は、データベースアクセスに関する基本設定です。データベースドライバー、クラスへアクセスするURL、ユーザー名、パスワードがあります。

H2へアクセスする場合、ドライバーにはorg.h2.Driverというクラスを指定します。また、アクセスURLはjdbc:h2:mem:datajpaとしました。これはH2のメモリ上にdatajpaという名前でデータベースを用意することを意味します。メモリ上に作るため、プログラム終了後にはデータベースはきれいに消えます。学習用には最適な使い方です。

もしメモリではなくハードディスクにファイルとして保存したい場合は、jdbc:h2:パスという形で書けばよいです。例えば、jdbc:h2:/db/dbdataと書くと、ディスクのdbフォルダにdbdataというファイルを作成し、そこへデータを保存します。

Hibernate関連では、Dialect値としてorg.hibernate.dialect.H2Dialectを指定しています。これはH2用のDialectクラスです。また、hibernate.hbm2ddl.autoの値にcreate-dropを指定しているため、プログラム開始時にデータベースを作成し、終了時に削除することを意味します。今回はH2のメモリに保存しているため、この設定にかかわらず終了するとデータベースは削除されます。

Bean設定ファイルの作成

次に準備するのはBean設定ファイルです。Spring Data JPAを使用する場合は、エンティティマネージャーというものを使用してアクセスします。これを生成するために、エンティティマネージャーファクトリというBeanを設定ファイルに追加する必要があります。

では、resourcesフォルダに新しい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>
         
</beans>

今回はJPAとJDBCタグを使用するための属性が、<beans>タグにいろいろ追加されています。この中には2つのBean設定タグが用意されています。

  • <jdbc:embedded-database>: これはデータベースのデータ情報、つまりデータソースオブジェクトを表します。id="dataSource"と明示されており、エンティティマネージャーファクトリでこのBeanを使用するためのIDです。また、type="H2"はデータベースとしてH2を使用していることを表します。

  • <bean id="entityManagerFactory">: エンティティマネージャーファクトリのBeanを定義します。クラスにはorg.springframework.orm.jpaパッケージのLocalContainerEntityManagerFactoryBeanというクラスを指定します。プロパティとして次の項目を使用できます。

    • dataSource: <jdbc:embedded-database>で用意したBeanを指定します。
    • jpaVendorAdapter: JPAベンダーアダプターというものを指定します。ここではHibernateJpaVendorAdapterクラスのBeanを使用します。また、このBeanにはgenerateDdlというプロパティを指定し、データベースを自動生成できるようにします。

いろいろなBeanが組み合わされていますが、結局すべてはエンティティマネージャーを使用できるようにするためのものだと理解しておきましょう。また、ここではentityManagerFactoryにHibernateのものを利用していますが、ほかのライブラリを使用することもできます。その場合は、使用するベンダーアダプターに合わせる必要があります。

エンティティクラスの作成

これでようやくSpring Data JPAを利用するための事前準備ができました。ここから実際にSpring Data JPAを利用するためのコーディングに入ります。

Spring Data JPAは、データベースに作成されるテーブルを扱うためにエンティティというクラスを使用します。エンティティは、テーブル構造をJavaクラスとして再構成したものです。同時に、テーブルから検索されるデータもエンティティのインスタンスとして処理されます。

簡単なエンティティクラスのサンプルとして、com.devkuma.spring.dbパッケージにSampleEntityというクラスを作成しましょう。

package com.devkuma.spring.db;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
 
@Entity
public class SampleEntity {
     
    @Id
    @Column
    @GeneratedValue(strategy=GenerationType.AUTO)
    private long id;
     
    @Column(length=50, nullable=false)
    private String name;
     
    @Column(length=100, nullable=true)
    private String mail;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getMail() {
        return mail;
    }
 
    public void setMail(String mail) {
        this.mail = mail;
    }
 
    public long getId() {
        return id;
    }
     
    public SampleEntity() {
        super();
    }
     
    public SampleEntity(String name, String mail) {
        this();
        this.name = name;
        this.mail = mail;
    }
     
    public String toString() {
        return "SampleEntity [id=" + id + ", name=" + name + ", mail=" + mail + "]";
    }
}

エンティティクラスでは、テーブルに含まれる項目をそのままフィールドとして用意します。つまり、テーブルはクラス、カラムはフィールドという感覚で定義します。注意すべき点は、単に定義するだけでなく、それぞれにアノテーションを付けていることです。次にまとめます。

  • @Entity
    エンティティクラスに付けます。このアノテーションを付けることで、このクラスがエンティティクラスであることを示します。
  • @Column
    フィールドに付けます。テーブルのカラムを表すためのものです。これを付けたフィールドは、その名前と同じカラムの値を保存するものと見なされます。したがって、カラムの型と同じ型の値でなければなりません。サンプルには次のようなものもあります。
    @Column(length=50, nullable=false)
    
    このように、括弧の中にカラムの特性を記述しています。この方式で、そのカラムに設定すべき属性もアノテーションに一緒に書けます。
  • @Id
    このアノテーションは最初のidフィールドにだけ付けます。主キーを表すためのもので、これを付けたフィールドがそのテーブルの主キーカラムであることを示します。
  • @GeneratedValue(strategy=GenerationType.AUTO)
    これもidフィールドに付けています。これは値の自動生成に関するものです。strategy=GenerationType.AUTOを指定すると、値を自動設定します。

追加されているフィールドはすべてprivateで、Setter/Getterメソッドでアクセスできるようになっています。そのほかに2種類のコンストラクタとtoStringメソッドがありますが、これらはエンティティに必須ではありません。必要に応じて用意すればよいです。

データベースの利用

それでは実際にデータベースを利用してみましょう。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 javax.persistence.Query;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class App {
 
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("dbbean.xml");
 
        EntityManagerFactory factory = app.getBean(EntityManagerFactory.class);
        EntityManager manager = factory.createEntityManager();
 
        makeDummyData(manager);
 
        // get list
        Query query = manager.createQuery("from SampleEntity");
        List list = query.getResultList();
        printList(list);
 
        System.out.println("...ok.");
    }
 
    // create dummy entity data.
    public static void makeDummyData(EntityManager manager) {
        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);
        }
    }
}

完成したら実際にAppクラスを実行してみると、次のようなテキストが出力されることがわかります。

SampleEntity [id = 1 name = tuyano, mail=syoda@tuyano.com]
SampleEntity [id = 2 name = hanako, mail = hanako @ flower]
SampleEntity [id = 3 name = taro, mail = taro @ yamada]
SampleEntity [id = 4 name = sachiko, mail = sachico @ happy]
... ok.

これは、ダミーとして用意したエンティティをデータベースから検索して表示しているものです。ここでは、ダミーデータを生成するmakeDummyDataメソッドと、取得したリストの内容を出力するprintListメソッドが用意されています。簡単に整理しましょう。

  1. ApplicationContextの作成
    ApplicationContext app = new ClassPathXmlApplicationContext( "dbbean.xml");
    
    まずは、いつも作成していたApplicationContextインスタンスを用意します。ここではdbbean.xmlファイルを指定しました。
  2. EntityManagerFactoryインスタンスの取得
    EntityManagerFactory factory = app.getBean(EntityManagerFactory.class);
    
    Bean設定ファイルに用意しておいたエンティティマネージャーファクトリBeanを取得します。ここではEntityManagerFactoryクラスのclass値を引数として指定しました。
  3. EntityManagerの作成
    EntityManager manager = factory.createEntityManager();
    
    用意したEntityManagerFactoryからEntityManagerを取得します。これはcreateEntityManagerというメソッドを呼び出すだけです。

これでEntityManagerが準備できました。次には、これを使用してダミーデータを保存したり、すべてのエンティティをリストとして取得したりできます。

まず今回は、データベース利用の基本として、重要なEntityManagerを取り出すところまで理解できれば十分です。具体的なデータベースアクセスは次に説明する予定です。

Bean設定クラスの利用

Bean設定ファイルを使用せず、クラスで定義することもできます。以前はdbbean.xmlでエンティティマネージャーファクトリを用意しましたが、クラスを使って同じことをしてみましょう。

ここでは、com.devkuma.spring.dbパッケージにSampleEntityConfigというクラスを作成し、そこにBean生成メソッドを用意します。サンプルとして次のようにソースコードを書き換えます。

package com.devkuma.spring.db;
 
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
 
@Configuration
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.devkuma.spring.db");
        factory.setDataSource(dataSource());
        factory.afterPropertiesSet();
        return factory.getObject();
    }
 
}

そして、Appクラスにある次のコードを修正します。

ApplicationContext app = new ClassPathXmlApplicationContext("dbbean.xml");

次のように修正します。

ApplicationContext app = new AnnotationConfigApplicationContext(SampleEntityConfig.class);

これでSampleEntityConfigクラスを使用してBeanを作成できます。dbbean.xmlは不要になりました。それだけでなく、persistence.xmlapplication.propertiesも実は不要になりました。application.propertiesなしでクラスに直接値を書いているためです。クラスを使用するほうが、よりすっきりとエンティティマネージャーを準備できることがわかります。では、ポイントを整理しましょう。

DataSourceの作成

EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.H2).build();

DataSourceEmbeddedDatabaseBuilderというクラスのインスタンスから作成します。setTypeでデータベースの種類を設定し、buildを呼び出すと生成されます。

EntityManagerFactoryを作る

これは少し面倒な作業が必要です。ここで行っている作業をざっくりまとめると、次のようになります。

  • HibernateJpaVendorAdapterインスタンスを生成する。
  • LocalContainerEntityManagerFactoryBeanインスタンスを生成する。
  • setJpaVendorAdapterLocalContainerEntityManagerFactoryBeanHibernateJpaVendorAdapterを設定する。
  • そのほか、setPackagesToScansetDataSourceなどのメソッドで属性を設定する。
  • afterPropertiesSetを呼び出す。
  • getObjectEntityManagerインスタンスを取得する。

いろいろ行うことがあって少し面倒に感じますが、Javaコードだけで、XMLファイルなどほかのものを一切必要とせずにBeanを生成できることを考えると、Javaプログラマーにとってはこちらの方式のほうが便利でしょう。