Spring Data JPA附加EntityManagerFactory仅针对缓存和批量操作进行了优化



我有一个遗留的Spring Data JPA应用程序,该应用程序具有大量实体和CrudRepository。JPA是使用下面的XML配置的。我们有一个新的要求,要求我们通过FileUpload一次将10000-50000个实体插入数据库。使用现有配置,数据库CPU会出现峰值。启用hibernate统计信息后,很明显,由于InvoiceService中的单个插入所需的所有验证逻辑,这10000个插入操作生成了超过200000个DB查询。

原始配置

<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="${db.driver}"/>
<property name="url" value="${db.jdbcurl}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
<property name="maxTotal" value="${db.maxTotal}"/>
<property name="maxIdle" value="${db.maxIdle}"/>
<property name="minIdle" value="${db.minIdle}"/>
<property name="initialSize" value="${db.initialSize}"/>
<property name="maxWaitMillis" value="${db.maxWaitMillis}"/>
<property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}"/>
<property name="timeBetweenEvictionRunsMillis" value="${db.timeBetweenEvictionRunsMillis}"/>
<property name="testOnBorrow" value="${db.testOnBorrow}"/>
<property name="testOnReturn" value="${db.testOnReturn}"/>
<property name="testWhileIdle" value="${db.testWhileIdle}"/>
<property name="removeAbandonedOnBorrow" value="${db.removeAbandonedOnBorrow}"/>
<property name="removeAbandonedOnMaintenance" value="${db.removeAbandonedOnMaintenance}"/>
<property name="removeAbandonedTimeout" value="${db.removeAbandonedTimeout}"/>
<property name="logAbandoned" value="${db.logAbandoned}"/>
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" depends-on="flyway">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="my.package.domain" />
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto:validate}</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql:false}</prop>
</props>
</property>
<property name="persistenceUnitName" value="entityManagerFactory" />
</bean>
<bean id="persistenceAnnotationBeanPostProcessor" class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor">
<property name="defaultPersistenceUnitName" value="entityManagerFactory"/>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven proxy-target-class="true" />
<bean id="persistenceExceptionTranslationPostProcessor"
class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<jpa:repositories base-package="my.package.repository" entity-manager-factory-ref="entityManagerFactory"/>

FileUploadService片段如下所示。。。

EntityManager batchEntityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = batchEntityManager.getTransaction();
transaction.begin();
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(file.getInputStream()))) {
buffer.lines()
.filter(StringUtils::isNotBlank)
.forEach(csvLine -> {
invoiceService.createInvoice(csvLine);
if (counter.incrementAndGet() % (updateFrequency.equals(0) ? 1 : updateFrequency) == 0) {
FileUpload fileUpload1 = fileUploadRepository.findOne(fileUpload.getId());
fileUpload1.setNumberOfSentRecords(sentCount.get());
fileUploadRepository.save(fileUpload1);
transaction.commit();
transaction.begin();
batchEntityManager.clear();
}
});
transaction.commit();
} catch (IOException ex) {
systemException.incrementAndGet();
log.error("Unexpected error while performing send task.", ex);
transaction.rollback();
}
// Update FileUpload status.
FileUpload fileUpload1 = fileUploadRepository.findOne(fileUpload.getId());
fileUpload1.setNumberOfSentRecords(sentCount.get());
if (systemException.get() != 0) {
fileUpload1.setStatus(FileUploadStatus.SYSTEM_ERROR);
} else {
fileUpload1.setStatus(FileUploadStatus.SENT);
}
fileUploadRepository.save(fileUpload1);
batchEntityManager.close();

大多数DB查询都是select语句,它们为插入的每个记录返回相同的结果。很明显,启用EhCache作为二级缓存将显著提高性能。然而,在没有启用ehcache的情况下,该应用程序已经在生产中完美运行了好几年。我很犹豫是否在全球范围内启用此功能,因为我不知道这将如何影响大量其他存储库/查询。

问题1

有没有一种方法可以配置一个";交替的";EntityManagerFactory仅对此批处理进程使用二级缓存?我如何选择使用此工厂而不是仅用于此批处理过程的主工厂?

我尝试在我的spring配置中添加如下内容。我可以很容易地将这个额外的EntityManager注入到我的类中并使用它。然而,现有的存储库(如FileUploadRepository(似乎没有使用它——它们只是返回null。我不确定这种方法是否可行。JpaTransactionManager的文档显示

此事务管理器适用于使用单个JPA EntityManagerFactory进行事务数据访问的应用程序

这正是我而不是所做的。那么还有什么其他选择呢?

<bean id="batchEntityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" depends-on="flyway">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="my.package.domain" />
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto:validate}</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql:false}</prop>
<prop key="hibernate.generate_statistics">${hibernate.generate_statistics:false}</prop>
<prop key="hibernate.ejb.interceptor">my.package.HibernateStatistics</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<prop key="hibernate.jdbc.batch_size">100</prop>
<prop key="hibernate.order_inserts">true</prop>
<prop key="hibernate.order_updates">true</prop>
</props>
</property>
<property name="persistenceUnitName" value="entityManagerFactory" />
</bean>

问题2

假设没有其他选择;选择性地";使用EhCache时,我尝试仅在主EntityManagerFactory上启用它。我们当然可以进行回归测试,以确保不会引入新问题。我认为这样做是相当安全的?然而,另一个问题出现了。我正在尝试批量提交插入,如本文所述,并在上面的代码中显示。由于Connection org.postgresql.jdbc.PgConnection@1e7eb804 is closed.,我在尝试提交批处理时遇到回滚异常。我认为这是由于dataSource上的maxWaitMillis属性造成的。

我不想为应用程序中其他所有现有的springService/Restore/query更改此属性。如果我可以使用";自定义";EntityManagerFactory我可以很容易地为不同的DataSourceBean提供我想要的属性。再说一遍,这可能吗?

也许我看错了这个问题。还有其他建议吗?

您可以使用另一个具有不同限定符的EntityManagerFactorybean,因此这是一种选择。我仍然建议您查看这些精选查询。我敢打赌,问题只是缺少索引,导致数据库进行全表扫描。如果添加了正确的索引,则可能不需要更改应用程序中的任何内容。

最新更新