JPA + PostgreSQL でエンティティからテーブル生成 + ID 発番

PostgreSQL に対し、 JPA のエンティティからテーブルを自動生成して ID 列を自動採番する方法を試してみました。

今回は Spring Boot 1.3.0 を使っています。 JPA 実装は Hibernate 4.3.11.Final のようです*1

JPA のエンティティがこんな感じ。

@Entity
@Table(name = "account")
public class Account {
	
	@Id
	@SequenceGenerator(name = "account_id_gen", sequenceName = "account_id_seq")
	@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "account_id_gen")
	private Long id;
	
	@Column(nullable = false)
	private String loginId;

        // getter/setter 略
}

GeneratedValue に加え、SequenceGenerator を指定するのがポイントっぽいです。オプションなしの GeneratedValue のみの場合、 hibernate_sequence というシーケンスが作られるようですが、さすがに明示的に指定したほうが良さそうです。

Spring Boot を使っているので、 application.yml にデータベース接続定義を書きます。ユーザーやデータベース自体は予め作成しておくものとします。

spring:
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: create-drop
  datasource:
    url: jdbc:postgresql://localhost:5432/sampledb
    driverClassName: org.postgresql.Driver
    username: testuser
    password: password

これでアプリケーションを起動し、 psql でデータベースの状態を確認します。以下のような感じでテーブルとシーケンスが作成されることが確認できます。

sampledb=> \d
                リレーションの一覧
 スキーマ |      名前      |     型     | 所有者
----------+----------------+------------+---------
 public   | account        | テーブル   | testuser
 public   | account_id_seq | シーケンス | testuser
(2 行)


sampledb=> \d account
                                テーブル "public.account"
    列    |           型           |                        修飾語
----------+------------------------+------------------------------------------------------
 id       | bigint                 | not null default nextval('account_id_seq'::regclass)
 login_id | character varying(255) | not null
インデックス:
    "account_pkey" PRIMARY KEY, btree (id)

シーケンスが作成され、 id 列のデフォルト値として設定されていることがわかります。この状態で JPA でレコード登録を行うと、以下のような SQL が出力されます。デフォルト値が使われていますね。

Hibernate: insert into account (login_id) values (?)

なお、 GenerationType.SEQUENCE を指定した場合、 id 列のデフォルト値が設定されませんでした。

sampledb=> \d account
          テーブル "public.account"
    列    |           型           |  修飾語
----------+------------------------+----------
 id       | bigint                 | not null
 login_id | character varying(255) | not null
インデックス:
    "account_pkey" PRIMARY KEY, btree (id)

しかしこの状態で JPA で登録を行ってもちゃんと登録されます。 SQL のログを見ると以下のようになっていました。一度シーケンスから値を取得してから insert するようです。検索すると SEQUENCE を使ってる例が結構出てきますが、どっちがいいんだろう?

Hibernate: select nextval ('account_id_seq')
Hibernate: insert into account (login_id, id) values (?, ?)

自動採番される id は RDBMS によって JPA のエンティティの定義方法が微妙に変わってくるところがちょっとめんどうですね。

*1:Spring Boot の場合とそうでない場合で挙動が違うかどうかは調べていません