質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.20%

spring-data-jpaでbulk insertするにはentity manager を使うしかないのでしょうか。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 3
  • VIEW 5,413

motisen

score 78

お世話になります。

spring-boot 1.4
spring-data-jpa 1.10
を使用しています。

バッチ処理の速度改善に取り組んでいますが、100件ほど入れるinsertがbulk insertではないことに気づきました。
(この100件ほどのinsertはバッチ内で何度も実行されます。)
bulk insertに変更したいと思い、以下のページを読みましたが、
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-data-access
https://stackoverflow.com/questions/34228044/how-to-enable-batch-inserts-with-hibernate-and-spring-boot
https://www.baeldung.com/spring-data-jpa-batch-inserts
どれもentity managerを使うような仕組みでした。

そのように実装してもよいのですが、どちらかといえば、
saveメソッド(JpaRepository)

    <S extends T> List<S> save(Iterable<S> entities);


の動きをbulk insertに変更できないかと考えています。

お知恵を貸していただきたいです。
よろしくお願いします。

---情報を追加しました。---
DB : PostgreSQL 9.5
エンティティクラスの主キー部分のソースコード

    @Id
    @SequenceGenerator( name            = "hoge",
                        sequenceName    = "piyo",
                        allocationSize  = 1 )
    @GeneratedValue(strategy    = GenerationType.SEQUENCE,
                    generator   = "hoge" )
    @Column
    private Long fooBarId;
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • rubytomato

    2019/10/25 13:28

    利用されているデータベースの種類とバージョン、バルクインサートしたいエンティティクラスの主キーの部分のソースコード(@Idを付けているフィールドのこと)を質問内容に追記してください。

    キャンセル

  • motisen

    2019/10/25 14:37

    情報追記致しました。ご確認よろしくお願い致します。

    キャンセル

回答 1

checkベストアンサー

+3

質問内容の追記ありがとうございました。

下記の環境で、リポジトリのメソッドを使ってコレクションの永続化にバルクインサートが行われるか確認しました。
この調査で参考にしたサイトは『Batch Insert/Update with Hibernate/JPA』です。

また、ご質問内容のSpring Bootのバージョンと、今回検証に使用したSpring Bootのバージョンが大きく異なりますので、この回答があてにならない可能性がありますが、そのときはご了承ください。

環境

  • Windows 10 Professional 1903
  • OpenJDK 11.0.2
  • Spring Boot 2.2.0
  • Spring Data JPA 2.2.0
  • Hibernate Core 5.4.6
  • postgresql 42.2.8 (JDBC Driver)

データベース

データベースはPostgreSQL 9.6.1です。

memoテーブル
CREATE TABLE memo (
  id bigserial NOT NULL,
  title varchar(255) NOT NULL,
  description text NOT NULL,
  done boolean DEFAULT false NOT NULL,
  updated timestamp without time zone DEFAULT current_timestamp NOT NULL,
  CONSTRAINT memo_pkey PRIMARY KEY(id)
);

memoテーブルの主キー用シーケンスの増分数を100へ変更

ALTER SEQUENCE memo_id_seq INCREMENT BY 100;

Spring Boot

アプリケーション設定ファイル(application.yml)
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/test_db?reWriteBatchedInserts=true
    username: test_user
    password: test_user
    hikari:
      connection-test-query: select 1
      connection-timeout: 10000
      maximum-pool-size: 2
      minimum-idle: 2
  jpa:
    open-in-view: true
    show-sql: true
    properties:
      hibernate:
        generate_statistics: true
        order_inserts: true
        format_sql: true
        dialect: org.hibernate.dialect.PostgreSQL95Dialect
        jdbc:
          batch_size: 100
エンティティクラス
@Entity
@Table(name = "memo")
public class Memo implements Serializable {

    private static final long serialVersionUID = 7104170344304358209L;

    @Id
    @SequenceGenerator(name = "memo_id_seq", sequenceName = "memo_id_seq", allocationSize = 100)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "memo_id_seq")
    private Long id;
    @Column(name = "title", nullable = false)
    private String title;
    @Column(name = "description", nullable = false)
    private String description;
    @Column(name = "done", nullable = false)
    private Boolean done;
    @Column(name = "updated", nullable = false)
    private LocalDateTime updated;

    // アクセサは省略

}
リポジトリ
public interface MemoRepository extends JpaRepository<Memo, Long> {
}
サービスクラス

MemoクラスのコレクションをリポジトリのsaveAllメソッドで永続化する部分の抜粋です。
Spring Data JPA 2.xからコレクションを永続化するメソッドはsaveAllに変わっています。(Spring Data JPA 1.xではsave

Interface JpaRepository<T,ID> - 1.5.0.RELEASE
Interface JpaRepository<T,ID> - 2.2.0.RELEASE

@Transactional(readOnly = false)
@Override
public void save(List<Memo> entities) {
    log.info("save all start");
    memoRepository.saveAll(entities);
    log.info("save all end");
}

また、比較用にエンティティマネージャを使った永続化のコードです。

@Transactional(readOnly = false)
@Override
public void save2(List<Memo> entities) {
    log.info("save2 all start");
    for (int i=0; i<entities.size(); i++) {
        entityManager.persist(entities.get(i));
        if (i> 0 && i % 100 == 0) {
            entityManager.flush();
            entityManager.clear();
        }
    }
    entityManager.flush();
    entityManager.clear();
    log.info("save2 all end");
}

動作確認

バルクインサートについて

リポジトリのsaveAllメソッドでもバルクインサートが行われるようにするには、下記2点の設定が必要でした。

  • 1つ目は、バッチサイズ(spring.jpa.properties.hibernate.jdbc.batch_size)を設定すること。
  • 2つ目は、JDBC URLにreWriteBatchedInserts=trueを付けること。

2つ目についてはJDBCドライバレベルで影響が出る設定なので、こちら『Connecting to the Database』などでご確認ください。

比較用にエンティティマネージャを使った場合の動作確認も行いましたが、エンティティマネージャを使う方は、100件単位でflush/clearを行っています。(無駄なキャッシュをクリアするため)

リポジトリの方では行っていないのですが、メモリの使用量が気になる場合は同じような処理を入れた方がいいかもしれません。

バルクインサートが行われたかどうかの確認

PostgreSQLのログからバルクインサートが行われていることを確認しました。

LOG:  実行 <unnamed>: insert into memo (description, done, title, updated, id) values ($1, $2, $3, $4, $5),($6, $7, $8, $9, $10),($11, $12, $13, $14, $15),($16, $17, $18, $19, $20),($21, $22, $23, $24, $25),($26, $27, $28, $29, $30),($31, $32, $33, $34, $35),($36, $37, $38, $39, $40),($41, $42, $43, $44, $45),($46, $47, $48, $49, $50),($51, $52, $53, $54, $55),($56, $57, $58, $59, $60),($61, $62, $63, $64, $65),($66, $67, $68, $69, $70),($71, $72, $73, $74, $75),($76, $77, $78, $79, $80),($81, $82, $83, $84, $85),($86, $87, $88, $89, $90),($91, $92, $93, $94, $95),($96, $97, $98, $99, $100),($101, $102, $103, $104, $105),($106, $107, $108, $109, $110),($111, $112, $113, $114, $115),($116, $117, $118, $119, $120),($121, $122, $123, $124, $125),($126, $127, $128, $129, $130),($131, $132, $133, $134, $135),($136, $137, $138, $139, $140),($141, $142, $143, $144, $145),($146, $147, $148, $149, $150),($151, $152, $153, $154, $155),($156, $157, $158, $159, $160),($161, $162, $163, $164, $165),($166, $167, $168, $169, $170),($171, $172, $173, $174, $175),($176, $177, $178, $179, $180),($181, $182, $183, $184, $185),($186, $187, $188, $189, $190),($191, $192, $193, $194, $195),($196, $197, $198, $199, $200),($201, $202, $203, $204, $205),($206, $207, $208, $209, $210),($211, $212, $213, $214, $215),($216, $217, $218, $219, $220),($221, $222, $223, $224, $225),($226, $227, $228, $229, $230),($231, $232, $233, $234, $235),($236, $237, $238, $239, $240),($241, $242, $243, $244, $245),($246, $247, $248, $249, $250),($251, $252, $253, $254, $255),($256, $257, $258, $259, $260),($261, $262, $263, $264, $265),($266, $267, $268, $269, $270),($271, $272, $273, $274, $275),($276, $277, $278, $279, $280),($281, $282, $283, $284, $285),($286, $287, $288, $289, $290),($291, $292, $293, $294, $295),($296, $297, $298, $299, $300),($301, $302, $303, $304, $305),($306, $307, $308, $309, $310),($311, $312, $313, $314, $315),($316, $317, $318, $319, $320)

性能

以下はバッチサイズを100と設定した状態と設定しなかった状態で、Memoクラスのコレクションが1万,5万,10万件を永続化したときにかかった時間です。

件数    リポジトリ (batch_size=100) リポジトリ (batch_size未指定)  エンティティマネージャ (batch_size=100) エンティティマネージャ (batch_size未指定) 
1万件    1.075秒                     3.044秒                      0.925秒                                 2.918秒                   
5万件    3.74秒                      14.081秒                     3.269秒                                 13.483秒                  
10万件   6.862秒                     27.644秒                     6.282秒                                 26.519秒                  

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/10/28 14:29

    とても詳しいご回答ありがとうございます!

    私の環境でも
    reWriteBatchedInserts=true
    spring.jpa.properties.hibernate.jdbc.batch_size
    を設定することで、bulk insertとして動作することが確認できました。

    性能まで載せて頂き、とても助かりました。ありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.20%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る