Spring Boot/第三回 Spring Bootでデータベース操作(JPA編)

Spring Bootでデータベース操作をする。

Spring Bootでのデータベース操作は

の2種類があるが、今回はORマッピングの方の話。
JavaのORマッピングの仕様「JPAJava Persistence APIJavaの永続化のAPI)」でデータベースを操作する。

依存関係の追加

まずはMavenの依存関係の変更。

  <dependencies>
    <!-- append start -->
    <dependency>  <!-- a -->
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>  <!-- b -->
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
    </dependency>
    <dependency>  <!-- c -->
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.14.0</version>
      <scope>provided</scope>
    </dependency>
    <!-- append end -->
  </dependencies>

a -> Spring BootでJPAを扱うための依存関係。
b -> H2の依存関係。
c -> Lombokの依存関係。

JDBCを使うときは「spring-boot-starter-jdbc」だが、JPAを使うときは「spring-boot-starter-data-jpa」を指定する。


依存関係を見てみる

mvn dependency:tree

結果は以下の通り。

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building SpringSample03 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ SpringSample03 ---
[INFO] tools.springsample.springsample03:SpringSample03:jar:1.0-SNAPSHOT
[INFO] +- junit:junit:jar:3.8.1:test
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:1.1.5.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:1.1.5.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:1.1.5.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:1.1.5.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:1.1.5.RELEASE:compile
[INFO] |  |  |  +- org.slf4j:jul-to-slf4j:jar:1.7.7:compile
[INFO] |  |  |  +- org.slf4j:log4j-over-slf4j:jar:1.7.7:compile
[INFO] |  |  |  \- ch.qos.logback:logback-classic:jar:1.1.2:compile
[INFO] |  |  |     \- ch.qos.logback:logback-core:jar:1.1.2:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.13:runtime
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:1.1.5.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:7.0.54:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:7.0.54:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-logging-juli:jar:7.0.54:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.3.3:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.3.3:compile
[INFO] |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.3.3:compile
[INFO] |  +- org.hibernate:hibernate-validator:jar:5.0.3.Final:compile
[INFO] |  |  +- javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging:jar:3.1.1.GA:compile
[INFO] |  |  \- com.fasterxml:classmate:jar:1.0.0:compile
[INFO] |  +- org.springframework:spring-core:jar:4.0.6.RELEASE:compile
[INFO] |  +- org.springframework:spring-web:jar:4.0.6.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-aop:jar:4.0.6.RELEASE:compile
[INFO] |  |  |  \- aopalliance:aopalliance:jar:1.0:compile
[INFO] |  |  +- org.springframework:spring-beans:jar:4.0.6.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-context:jar:4.0.6.RELEASE:compile
[INFO] |  \- org.springframework:spring-webmvc:jar:4.0.6.RELEASE:compile
[INFO] |     \- org.springframework:spring-expression:jar:4.0.6.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:1.1.5.RELEASE:test
[INFO] |  +- org.mockito:mockito-core:jar:1.9.5:test
[INFO] |  |  \- org.objenesis:objenesis:jar:1.0:test
[INFO] |  +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] |  +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] |  \- org.springframework:spring-test:jar:4.0.6.RELEASE:test
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:1.1.5.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-aop:jar:1.1.5.RELEASE:compile
[INFO] |  |  +- org.aspectj:aspectjrt:jar:1.8.1:compile
[INFO] |  |  \- org.aspectj:aspectjweaver:jar:1.8.1:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:1.1.5.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-jdbc:jar:4.0.6.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat:tomcat-jdbc:jar:7.0.54:compile
[INFO] |  |  |  \- org.apache.tomcat:tomcat-juli:jar:7.0.54:compile
[INFO] |  |  \- org.springframework:spring-tx:jar:4.0.6.RELEASE:compile
[INFO] |  +- org.hibernate:hibernate-entitymanager:jar:4.3.5.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging-annotations:jar:1.2.0.Beta1:compile
[INFO] |  |  +- org.hibernate:hibernate-core:jar:4.3.5.Final:compile
[INFO] |  |  |  +- antlr:antlr:jar:2.7.7:compile
[INFO] |  |  |  \- org.jboss:jandex:jar:1.1.0.Final:compile
[INFO] |  |  +- dom4j:dom4j:jar:1.6.1:compile
[INFO] |  |  |  \- xml-apis:xml-apis:jar:1.0.b2:compile
[INFO] |  |  +- org.hibernate.common:hibernate-commons-annotations:jar:4.0.4.Final:compile
[INFO] |  |  +- org.hibernate.javax.persistence:hibernate-jpa-2.1-api:jar:1.0.0.Final:compile
[INFO] |  |  +- org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:jar:1.0.0.Final:compile
[INFO] |  |  \- org.javassist:javassist:jar:3.18.1-GA:compile
[INFO] |  +- org.springframework:spring-orm:jar:4.0.6.RELEASE:compile
[INFO] |  +- org.springframework.data:spring-data-jpa:jar:1.6.2.RELEASE:compile
[INFO] |  |  +- org.springframework.data:spring-data-commons:jar:1.8.2.RELEASE:compile
[INFO] |  |  +- org.slf4j:slf4j-api:jar:1.7.7:compile
[INFO] |  |  \- org.slf4j:jcl-over-slf4j:jar:1.7.7:compile
[INFO] |  \- org.springframework:spring-aspects:jar:4.0.6.RELEASE:compile
[INFO] +- com.h2database:h2:jar:1.3.176:compile
[INFO] \- org.projectlombok:lombok:jar:1.14.0:provided
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.135 s
[INFO] Finished at: 2015-03-23T00:53:09+09:00
[INFO] Final Memory: 12M/29M
[INFO] ------------------------------------------------------------------------

org.springframework.boot:spring-boot-starter-data-jpaにぶら下がっているのがorg.hibernate:hibernate-entitymanagerであることから、JPAの実装としてHibernateであることがわかる。
JPAは仕様であって、それを実装したライブラリを実際には使用する。
実装したライブラリのことを「JPAプロバイダ」と言う。
なので、「今回のJPAプロバイダはHibernate」となる。

Hibernateの設定

Hibernateの設定をapplication.ymlに追加する。

src/main/resources/application.yml

このファイルに下記の内容を記載する。

spring:
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:file:testdb/testdb
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: update

datasourceの設定は準備編と同じ。
jpaからの設定を追加する。

ddl-autoはHibernateによるテーブル作成をどう制御するかを定義する。
以下のものがある。

  • create-drop -> テーブルがあったら削除してから作り直す。なかったら作る。つまりデータは毎回消える。(UTに便利?)
  • update -> テーブルの作り直しはしないが、なかったら作る。
  • none -> なくても作らない。本番運用向き。

何を基にテーブルを作るかというと、下記で説明するエンティティクラスを基に作る。
なので、準備編で作成したschema.sqlは不要なので削除する。

エンティティクラスを用意する

準備編で作成したCustomerクラスをJPA用にカスタマイズする。

package tools.springsample.springsample03.domain;

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.Column;
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

@Entity    // a
@Table(name="customer")    // b
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
    @Id    // c
    @GeneratedValue    // d
    private Integer id;

    @Column(nullable=false)    // e
    private String name;

    @Column(nullable=false)    // e
    private String email;
}

JPA用に変更したのは下記の通り。
a -> エンティティクラスであることを記すアノテーション
b -> どのテーブルとマッピングするのか指定する。
c -> フィールドidがエンティティの主キーであることを表す。
d -> 自動採番する主キーであることを表す。
e -> テーブルに該当するカラムであることを表す。nullでの更新を抑制する。

リポジトリクラスを用意する

今まで作成したCustomerRepositoryクラスをJPA用に変更する。
(変更というかほとんど書き換え)

package tools.springsample.springsample03.repository;

import tools.springsample.springsample03.domain.Customer;

import org.springframework.data.jpa.repository.JpaRepository;

public interface CustomerRepository
    extends JpaRepository<Customer, Integer> {
}

クラスではなくインターフェースになる。
JpaRepositoryを拡張すると、実行時に実装クラスが自動生成される。
CRUDの各メソッドが用意されているので、それを呼ぶとデータベースを操作できる。
JPAを使ってSQLがソースから消えた。

コンパイルして実行

JDBC編で作成したAppクラスはそのまま使えるので、ここまでの状態でコンパイルして実行することができる。

mvn package

続いて起動。

java -jar target\SpringSample03-1.0-SNAPSHOT.jar

実行したあとデータベースを見て登録されることを確認すること。

やったことと言えば、1:設定書いて、2:エンティティ作って、3:リポジトリの宣言、の3ステップだけ。
これだけでデータベース更新できてしまうのが、手軽すぎて逆に怖い。

ただ、不明なことも見えてきた。

  • JpaRepositoryを拡張して出来る検索は全検索かPKでの検索だけだが、他のキーで検索するにはどうする?
  • ほかのテーブルをJOINして検索する場合はどうする?
  • JpaRepositoryに主キーを知らせる(今回はInteger)が、複合主キー(複数カラムを用いた主キー)の場合はどうする?

これらは追々。

■ 参考文献 ■

この記事で参考にしたのは「はじめての Spring Boot」です。