使用JPA修改一条按primary key定位的记录耗时超过30秒.
执行方法代码.
@Transactional
public void update() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
this.lastClearingDate = this.clearDate;
paraRepository.update(ParaItemType.LAST_CLEARING_DATE.name().toLowerCase(),sdf.format(this.lastClearingDate));
}
该函数只是修改一个配置项记录. 但是,耗时30多秒.
ParaRepository定义的update方法如下:
public interface ParaRepository extends JpaRepository{
@Modifying
@Transactional
@Query(nativeQuery = true, value = "update clr_para set item_value = ?2 where item_name = ?1")
int update(String itemName, String itemValue);
}
经验证,用2种方式可以解决这个问题:
(1)采用JdbcTemplate执行同样的命令,耗时42毫秒.测试代码如下:
@Transactional
public void update() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
this.lastClearingDate = this.clearDate;
JdbcTemplate jdbcTemplate = (JdbcTemplate)Application.getBean(JdbcTemplate.class);
String sql = String.format("update clr_para set item_value='%s' where item_name='last_clearing_date'",sdf.format(this.lastClearingDate));
jdbcTemplate.execute(sql);
}
(2)不使用Native Query
public interface ParaRepository extends JpaRepository{
@Modifying
@Transactional
@Query(nativeQuery = false, value = "update Para set itemValue = ?2 where itemName = ?1")
int update(String itemName, String itemValue);
}
差别:nativeQuery = false. value采用JPQL方法.
Native Query方式为什么如此慢?目前没有找到解释.
执行Native Query之前执行了flush.
见下文
How does AUTO flush strategy work in JPA and Hibernate
https://vladmihalcea.com/how-does-the-auto-flush-work-in-jpa-and-hibernate/
以下是分析记录:
跟踪paraRepository.update的部分堆栈如下:
NativeQueryImpl.beforeQuery() line: 245
NativeQueryImpl(AbstractProducedQuery).executeUpdate() line: 1502
JpaQueryExecution$ModifyingExecution.doExecute(AbstractJpaQuery, Object[]) line: 256
JpaQueryExecution$ModifyingExecution(JpaQueryExecution).execute(AbstractJpaQuery, Object[]) line: 91
NativeJpaQuery(AbstractJpaQuery).doExecute(JpaQueryExecution, Object[]) line: 136
NativeJpaQuery(AbstractJpaQuery).execute(Object[]) line: 125
RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(MethodInvocation) line: 590
RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(MethodInvocation) line: 578
ReflectiveMethodInvocation.proceed() line: 185
耗时发生在NativeQueryImpl.java的beforeQuery方法.
具体位置见代码中的标记。
?query spaces是什么
?该update语句包含查询(需要定位记录),查询前需要提交事务,如何理解
?代码中的相关注释什么含义
beforeQuery代码如下:
@Override
protected void beforeQuery() {
prepareQueryReturnsIfNecessary();
boolean noReturns = queryReturns == null || queryReturns.isEmpty();
if ( noReturns ) {
this.autoDiscoverTypes = true;
}
else {
for ( NativeSQLQueryReturn queryReturn : queryReturns ) {
if ( queryReturn instanceof NativeSQLQueryScalarReturn ) {
NativeSQLQueryScalarReturn scalar = (NativeSQLQueryScalarReturn) queryReturn;
if ( scalar.getType() == null ) {
autoDiscoverTypes = true;
break;
}
}
else if ( NativeSQLQueryConstructorReturn.class.isInstance( queryReturn ) ) {
autoDiscoverTypes = true;
break;
}
}
}
super.beforeQuery();
if ( getSynchronizedQuerySpaces() != null && !getSynchronizedQuerySpaces().isEmpty() ) {
// The application defined query spaces on the Hibernate native SQLQuery which means the query will already
// perform a partial flush according to the defined query spaces, no need to do a full flush.
return;
}
// otherwise we need to flush. the query itself is not required to execute in a transaction; if there is
// no transaction, the flush would throw a TransactionRequiredException which would potentially break existing
// apps, so we only do the flush if a transaction is in progress.
//
// NOTE : this was added for JPA initially. Perhaps we want to only do this from JPA usage?
if ( shouldFlush() ) {
getProducer().flush(); ///< 这里耗时
}
}