Spring 启动 JPA/JDBC 批处理 findById 工作但 findOneByX 不工作

Spring Boot JPA/JDBC batching findById works but findOneByX not working

我正在使用 Spring Boot JPA,我通过确保以下几行在我的 application.properties:

中启用了批处理
spring.jpa.properties.hibernate.jdbc.batch_size=1000
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true

我现在有一个循环,我在一个实体上执行 findById,然后像这样保存该实体:

var entity = dao.findById(id)
// Do some stuff
dao.save(entity) //This line is not really required but I am being explicit here

将上面的代码放在一个循环中,我看到保存(更新)语句被批处理到数据库中。 我的问题是,如果我在实体上执行 findOneByX,其中 X 是 属性,则批处理不起作用(批处理大小为 1),一次发送一个请求,即:

var entity = dao.findOneByX(x)
// Do some stuff
dao.save(entity)

为什么会这样? JPA/JDBC是不是只有findById才配batch?

解决方案

参考

  1. 获取要更新的实体列表
  2. 根据需要更新
  3. 呼叫saveAll

PS:当您的列表很大时,请注意此解决方案的内存使用情况。


为什么 findByIdfindOneByX 的行为不同?

根据M. Deinum, hibernate will auto flush的建议,您的更改

prior to executing a JPQL/HQL query that overlaps with the queued entity actions

既然findByIdfindOneByX都会执行查询,那么它们有什么不同呢?

首先,刷新的原因是为了确保会话和数据库处于相同状态,因此您可以从会话缓存(如果可用)和数据库中获得一致的结果。

当调用findById时,hibernate 将尝试从会话缓存中获取它,如果实体不可用,则从数据库中获取它。而对于 findOneByX,我们总是需要从数据库中获取它,因为 X 无法缓存实体。

那么我们可以考虑下面的例子:

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    @Id
    private Long id;
    private String name;
    private int age;
}

假设我们有

id name age
1 Amy 10
@Transactional
public void findByIdAndUpdate() {
    dao.save(new Student(2L, "Dennis", 14));
    // no need to flush as we can get from session
    for (int i = 0; i < 100; i++) {
        Student dennis = dao.findById(2L).orElseThrow();
        dennis.setAge(i);
        dao.save(dennis);
    }
}

将导致

412041 nanoseconds spent executing 2 JDBC batches;

1 个用于插入 1 个用于更新。

  • Hibernate:如果记录不在会话中,我确定可以从会话(不刷新)或数据库中获取结果,所以让我们跳过刷新,因为它很慢!
@Transactional
public void findOneByNameAndUpdate() {
    Student amy = dao.findOneByName("Amy");
    // this affect later query
    amy.setName("Tammy");
    dao.save(amy);
    for (int i = 0; i < 100; i++) {
        // do you expect getting result here?
        Student tammy = dao.findOneByName("Tammy");
        // Hibernate not smart enough to notice this will not affect later result.
        tammy.setAge(i);
        dao.save(tammy);
    }
}

将导致

13964088 nanoseconds spent executing 101 JDBC batches;

1 用于首次更新,100 用于循环更新。

  • Hibernate: 嗯,我不确定存储的更新是否会影响结果,最好刷新更新,否则我会被开发人员指责。