Spring Boot JPA 批量插入

Posted

技术标签:

【中文标题】Spring Boot JPA 批量插入【英文标题】:Spring Boot JPA Bulk insert 【发布时间】:2018-05-17 20:10:37 【问题描述】:

我有 3 个实体父、子、子子。 Parent 是 Child 的 parent, Child 是 SubChild 的 parent。我需要插入大约 700 个 Parent 对象。父级可以拥有 50 个子级对象。 Child 可以拥有 50 个 SubChild 对象。 我尝试了正常的repository.save(ListOfObjects) 大约需要 4 分钟。

然后我尝试根据批量大小(500)使用实体管理器的persistflushclear。这也花了大约 4 分钟。 性能上没有太大区别。请提出一种有效插入如此大量数据的最佳方法。

父母

@Entity
public class Parent 
@Id @GeneratedValue(strategy= GenerationType.AUTO)
private Long parentId;
private String aaa;
private String bbb;
private String ccc;
@Version
private Long version;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "parent", fetch = FetchType.LAZY)
@JoinColumnsOrFormulas(
@JoinColumnOrFormula(column=@JoinColumn(name="parentId",referencedColumnName="parentId",nullable=false)))
private List<Child> childs = new ArrayList<>();
public Long getParentId() 
    return parentId;

public void setParentId(Long parentId) 
    this.parentId = parentId;

public String getAaa() 
    return aaa;

public void setAaa(String aaa) 
    this.aaa = aaa;

public String getBbb() 
    return bbb;

public void setBbb(String bbb) 
    this.bbb = bbb;

public String getCcc() 
    return ccc;

public void setCcc(String ccc) 
    this.ccc = ccc;

public Long getVersion() 
    return version;

public void setVersion(Long version) 
    this.version = version;

public List<Child> getChilds() 
    return childs;

public void setChilds(List<Child> childs) 
    this.childs = childs;


孩子

@Entity
public class Child 
@Id @GeneratedValue(strategy= GenerationType.AUTO)
private Long childId;
private String ddd;
private String ccc;
private Integer eee;
@OneToMany(cascade = CascadeType.ALL,orphanRemoval = true, mappedBy = "child", fetch = FetchType.LAZY)
@JoinColumnsOrFormulas(
        @JoinColumnOrFormula(column = @JoinColumn(name = "childId", referencedColumnName = "childId", nullable = false)) )
private List<SubChild> subChilds = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumnsOrFormulas(
    @JoinColumnOrFormula(column= @JoinColumn( name="parentId",referencedColumnName="parentId",nullable=false))
)
private Parent parent;

public Long getChildId() 
    return childId;

public void setChildId(Long childId) 
    this.childId = childId;

public String getDdd() 
    return ddd;

public void setDdd(String ddd) 
    this.ddd = ddd;

public String getCcc() 
    return ccc;

public void setCcc(String ccc) 
    this.ccc = ccc;

public Integer getEee() 
    return eee;

public void setEee(Integer eee) 
    this.eee = eee;

public List<SubChild> getSubChilds() 
    return subChilds;

public void setSubChilds(List<SubChild> subChilds) 
    this.subChilds = subChilds;

public Parent getParent() 
    return parent;

public void setParent(Parent parent) 
    this.parent = parent;


子子

@Entity
public class SubChild 
@Id @GeneratedValue(strategy= GenerationType.AUTO)
private Long subChildId;
private String fff;
private String ggg;
private Integer hhh;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumnsOrFormulas(
    @JoinColumnOrFormula(column= @JoinColumn( name="childId",referencedColumnName="childId",nullable=false))
)
private Child child;

public Long getSubChildId() 
    return subChildId;

public void setSubChildId(Long subChildId) 
    this.subChildId = subChildId;

public String getFff() 
    return fff;

public void setFff(String fff) 
    this.fff = fff;

public String getGgg() 
    return ggg;

public void setGgg(String ggg) 
    this.ggg = ggg;

public Integer getHhh() 
    return hhh;

public void setHhh(Integer hhh) 
    this.hhh = hhh;

public Child getChild() 
    return child;

public void setChild(Child child) 
    this.child = child;


用于持久化父实体列表的存储库方法

@Value("$spring.jpa.hibernate.jdbc.batch_size")
private int batchSize;

public <T extends Parent> Collection<T> bulkSave(Collection<T> entities) 
    final List<T> savedEntities = new ArrayList<T>(entities.size());
    int i = 0;
    for (T t : entities) 
        savedEntities.add(persistOrMerge(t));
        i++;
        if (i % batchSize == 0) 
            // Flush a batch of inserts and release memory.
            entityManager.flush();
            entityManager.clear();
        
    
    return savedEntities;

private <T extends Parent> T persistOrMerge(T t) 
    if (t.getTimeSlotId() == null) 
        entityManager.persist(t);
        return t;
     else 
        return entityManager.merge(t);
    

application.yml

spring:
  application:
    name: sample-service
  jpa:
    database: mysql
    show-sql: true
    hibernate:
      ddl-auto: update
      dialect: org.hibernate.dialect.MySQL5Dialect
      naming_strategy: org.hibernate.cfg.ImprovedNamingStrategy
      jdbc:
        batch_size: 100
  jackson:
    date-format: dd/MM/yyyy
  thymeleaf:
    cache: false
spring.datasource.url : jdbc:mysql://$dbhost/sample?createDatabaseIfNotExist=true
spring.datasource.username : root
spring.datasource.password : root
spring.datasource.driver-class-name : com.mysql.cj.jdbc.Driver

【问题讨论】:

刷新后你会得到 50 个插入还是一个? 如果我错了,请纠正我,因为事务是由 @transactional 注释处理的,我认为提交发生在最后阶段。所以我认为我们刷新时它不会插入任何记录。 对不起,以为你在每批之后都提交了。所以在提交时.. 日志显示每个对象插入一个?.. 所以 700? ***.com/questions/45670583/… @MaciejKowalski 它在提交时插入 700 个对象 【参考方案1】:

要启用批量插入,您需要配置中的 batch_size 属性。

此外,由于 jdbc 批处理只能针对一个表,因此您需要 spring.jpa.hibernate.order_inserts=true 属性来对父子之间的插入进行排序,否则语句是无序的,您将看到部分批处理(新批处理随时插入不同的表被调用)

【讨论】:

以上是关于Spring Boot JPA 批量插入的主要内容,如果未能解决你的问题,请参考以下文章

如何为批量插入配置spring boot和data jpa

JPA 批量插入不会提高性能

Spring Boot MySQL 不批量插入

HIbernate 批量插入或更新在 Spring Boot 中不起作用

Spring-data-jpa:批量插入不起作用

Spring数据JPA存储库saveAll不生成批量插入查询