Spring DATA JPAで一括インサート
- 使用環境
- pom.xml
- application.properties
- 使用テーブルのCREATE, ALTER
- Modelクラス
- Repositoryクラス
- Controllerクラス
- View(index.html)
- PostgreSQLのログを出力するようにする
- 参考文献
使用環境
- Windows 10
- Java 1.8
- Spring Boot バージョン: 2.4.5
- PostgreSQL 13.1
GitHub - n-yata/batch-insert-sample
pom.xml
spring-boot-starter-data-jpaを入れる
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> </dependencies>
application.properties
reWriteBatchedInserts=true
をURLのパラメータに入れる(PostgreSQLの場合)。spring.jpa.properties.hibernate.order_inserts
とspring.jpa.properties.hibernate.jdbc.batch_size
を設定。batch_sizeは20~100が推奨値みたい。
# DB接続設定 spring.datasource.url=jdbc:postgresql://localhost:5432/postgres?reWriteBatchedInserts=true spring.datasource.username=postgres spring.datasource.password=postgres spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=none spring.jpa.show-sql=true #thymeleafの設定 spring.thymeleaf.cache=false # バッチインサートの設定 spring.jpa.properties.hibernate.order_inserts=true spring.jpa.properties.hibernate.jdbc.batch_size=100
使用テーブルのCREATE, ALTER
テーブルを作成。シーケンステーブルの増分値を100に変更
DROP TABLE IF EXISTS todoitems CASCADE; CREATE TABLE IF NOT EXISTS todoitems( id SERIAL NOT NULL, title VARCHAR(255), done BOOLEAN, tododate TIMESTAMP NOT NULL, user_id VARCHAR(255) NOT NULL, PRIMARY KEY (id) ); ALTER SEQUENCE todoitems_id_seq INCREMENT BY 100;
Modelクラス
@SequenceGenerator
にallocationSize = 100を指定する。
package com.example.model; import java.io.Serializable; import java.time.LocalDateTime; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.SequenceGenerator; import javax.persistence.Table; @Entity @Table(name="todoitems") public class Todoitem implements Serializable { private static final long serialVersionUID = 1L; @Id @SequenceGenerator(name = "todoitems_id_seq", sequenceName = "todoitems_id_seq", allocationSize = 100) @GeneratedValue(strategy=GenerationType.IDENTITY, generator = "todoitems_id_seq") private Integer id; private Boolean done; @Column(length=255) private String title; @Column(nullable=false) private LocalDateTime tododate; @Column(name="user_id", nullable=false, length=255) private String userId; // Getter, Setterは省略 @Override public String toString() { return "Todoitem [id=" + id + ", done=" + done + ", title=" + title + ", tododate=" + tododate + ", userId=" + userId + "]"; } }
Repositoryクラス
package com.example.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.example.model.Todoitem; public interface TodoitemRepository extends JpaRepository<Todoitem, Integer>{ }
Controllerクラス
repository.saveAll(List<Entity>)
で一括登録する
package com.example.controller; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import com.example.model.Todoitem; import com.example.repository.TodoitemRepository; @Controller public class HomeController { @Autowired TodoitemRepository repository; @GetMapping(value = "/") public String get() { return "index"; } /** * サンプルを5件インサート */ @PostMapping(value = "/insert") @Transactional(readOnly = false) public String insert() { List<Todoitem> items = new ArrayList<>(); Todoitem item; for(int i = 0; i < 5; i++) { item = new Todoitem(); item.setTitle("テスト" + i); item.setDone(false); item.setTododate(LocalDateTime.now()); item.setUserId("test_user"); items.add(item); } repository.saveAll(items); return "index"; } /** * アイテム一覧を表示 * @param model * @return */ @GetMapping(value = "/find") public String find(Model model) { List<Todoitem> items = repository.findAll(); List<String> lines = new ArrayList<>(); for(Todoitem item : items) { lines.add(item.toString()); } model.addAttribute("items", lines); return "index"; } }
View(index.html)
thymeleafを使用。5件インサートと全件画面に表示ボタンをつけとく。
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <form method="get" action="/find"> <input type="submit" value="findAll"> </form> <form method="post" action="/insert"> <input type="submit" value="insert to 5 items"> </form> <table> <tr th:each="line:${items}"> <td th:text=${line}></td> </tr> </table> </body> </html>
PostgreSQLのログを出力するようにする
とりあえず一括インサートのサンプルは上記で完成だけど、
eclipse上のログだと1件ずつインサートしているように見える。
アプリからDBに飛んでから変換してる?(reWriteBatchedInserts=true
で)ようなのでPostgreSQL側のログを確認して一括インサートになってるか見てみる。
postgresql.conf
を編集
デフォルトの状態でインストールしてればたぶんここらへんにある。
C:\Program Files\PostgreSQL\13\data\postgresql.conf
コメントアウトとパラメータをall
に変更してSQLが出力されるようにする
# log_statement = 'none' # none, ddl, mod, all ↓ log_statement = 'all' # none, ddl, mod, all
PostgreSQLを再起動して編集内容を反映
$ pg_ctl -D "C:\Program Files\PostgreSQL\13\data" restart
または
アプリ起動してインサートすると、logファイルにSQLが書き込まれる。
ログファイルの場所はデフォルトだと下のようなパス。
C:\Program Files\PostgreSQL\13\data\log
一括インサートできていることが確認できる。
2021-04-29 21:15:28.494 JST [49252] LOG: 実行 <unnamed>: insert into todoitems (done, title, tododate, user_id, id) values ($1, $2, $3, $4, $5),($6, $7, $8, $9, $10),($11, $12, $13, $14, $15),($16, $17, $18, $19, $20)
参考文献
Java - spring-data-jpaでbulk insertするにはentity manager を使うしかないのでしょうか。|teratail