使用Spring Data JPA自定义update执行慢的问题

1.问题

使用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.解决

经验证,用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方法.


3.问题原因

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(); ///< 这里耗时
        }
    } 
    

 

    

你可能感兴趣的:(问题处理)