質問内容の追記ありがとうございました。
下記の環境で、リポジトリのメソッドを使ってコレクションの永続化にバルクインサートが行われるか確認しました。
この調査で参考にしたサイトは『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秒