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?
解决方案
参考
- 获取要更新的实体列表
- 根据需要更新
- 呼叫
saveAll
PS:当您的列表很大时,请注意此解决方案的内存使用情况。
为什么 findById
和 findOneByX
的行为不同?
根据M. Deinum, hibernate will auto flush的建议,您的更改
prior to executing a JPQL/HQL query that overlaps with the queued entity actions
既然findById
和findOneByX
都会执行查询,那么它们有什么不同呢?
首先,刷新的原因是为了确保会话和数据库处于相同状态,因此您可以从会话缓存(如果可用)和数据库中获得一致的结果。
当调用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: 嗯,我不确定存储的更新是否会影响结果,最好刷新更新,否则我会被开发人员指责。
我正在使用 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?
解决方案
参考
- 获取要更新的实体列表
- 根据需要更新
- 呼叫
saveAll
PS:当您的列表很大时,请注意此解决方案的内存使用情况。
为什么 findById
和 findOneByX
的行为不同?
根据M. Deinum, hibernate will auto flush的建议,您的更改
prior to executing a JPQL/HQL query that overlaps with the queued entity actions
既然findById
和findOneByX
都会执行查询,那么它们有什么不同呢?
首先,刷新的原因是为了确保会话和数据库处于相同状态,因此您可以从会话缓存(如果可用)和数据库中获得一致的结果。
当调用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: 嗯,我不确定存储的更新是否会影响结果,最好刷新更新,否则我会被开发人员指责。