我有以下的 Spring Data JPA Repository
public interface FooRepository extends JpaRepository<Foo, String> {
@QueryHints(
value = {
@QueryHint(name = HINT_FETCH_SIZE, value = "1000"),
@QueryHint(name = HINT_CACHEABLE, value = "false"),
@QueryHint(name = HINT_FLUSH_MODE, value = "ALWAYS"),
@QueryHint(name = HINT_CACHE_MODE, value = "IGNORE"),
@QueryHint(name = HINT_READONLY, value = "true")
})
Stream<Foo> findAll();
}
以下方法中被称为如下所示
@Transactional
public void doSomething() {
AtomicInteger counter = new AtomicInteger();
try(Stream<Foo> stream = fooRepository.findAll()) {
stream.forEach(foo -> {
int i = counter.incrementAndGet();
logger.info(() -> "" + i);
});
}
}
当运行这段代码时,有数百万个Foo实体,此代码会抛出OutOfMemoryError。在崩溃后查看堆转储时,我发现有大量的MutableEntityEntry、Foo和EntityEntryContext$ManagedEntityImpl。所有三者都具有完全相同的计数。除此之外,还有正好是该计数两倍的EntityKey。例如,在堆转储中,我有40k个前3个实体和80k个EntityKey。
为了使其工作,我尝试手动刷新、清除和垃圾回收,但没有成功。
@Transactional // org.springframework.transaction.annotation.Transactional
public void doSomething() {
entityManager.joinTransaction(); // properly injected through Spring DI
AtomicInteger counter = new AtomicInteger();
try(Stream<Foo> stream = fooRepository.findAll()) {
stream.forEach(foo -> {
int i = counter.incrementAndGet();
if (i % 100 == 0) {
fooRepository.flush();
entityManager.clear();
System.gc();
logger.info(() -> "flush, clear, gc");
}
logger.info(() -> "" + i);
});
}
由于在我的代码中没有保留任何流式传输到堆转储的
foo
实体的引用,一旦出现错误,我怀疑问题出在Hibernate的L1 Session缓存中,即使有一个QueryHint
停用了缓存(据我理解)。感觉只有给定方法中的HINT_FETCH_SIZE
在QueryHints
中起作用,但我不知道为什么。顺便说一下,我的项目中根本没有使用Spring Boot。因此,在我的
SpringConfiguration
中有以下bean来配置Spring Data JPA:@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory()
throws MalformedURLException {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.POSTGRESQL);
vendorAdapter.setGenerateDdl(false);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan(getClass().getPackage().getName());
factory.setDataSource(dataSource());
Properties jpaProperties = new Properties();
jpaProperties.setProperty(
"hibernate.physical_naming_strategy",
"my.domain.hibernate.SnakeCasePhysicalNamingStrategy");
jpaProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQL10Dialect");
factory.setJpaProperties(jpaProperties);
return factory;
}
@Bean
public EntityManager entityManager() throws MalformedURLException {
return entityManagerFactory().getObject().createEntityManager();
}
@Bean
public PlatformTransactionManager transactionManager() throws MalformedURLException {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory().getObject());
return txManager;
}
以下是每个版本的信息:
- Spring 5.2.13.RELEASE
- Spring Data JPA 2.3.7.RELEASE
- Hibernate 5.4.28.Final
- PostgreSQL 13.1(在Docker上使用alpine)