NamedParameterJdbcTemplate 性能下降

问题描述

我有一个使用 NamedParameterJdbcTemplate 从 OracleDB 获取数据的 Spring5 应用程序。

当我从 intellij DB 控制台执行相同的查询时,我会在 1.5 秒内得到结果,但是当我使用 Java 应用程序执行相同的查询jdbcTemplate.queryForList(query,params); 需要 90 秒

这是 DBconfigration 类:

@Configuration
@EnableJpaRepositories("com.xxxx.xxx.relational.repositories")
@PropertySource(value = "classpath:jdbc.properties")
@ComponentScan
@EnableTransactionManagement
@Import(RestTemplateConfig.class)
public class RelationalConfig {

    @Bean(destroyMethod = "close")
    public BasicDataSource dataSource(@Value("${jdbc.driverClassName}") String driverClassName,@Value("${jdbc.url}") String url,@Value("${jdbc.username}") String username,@Value("${jdbc.password}") String password) {
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName(driverClassName);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }

    @Bean
    HibernateJpavendorAdapter hibernateJpavendorAdapter() {
        return new HibernateJpavendorAdapter();
    }

    @Bean
    @Autowired
    LocalContainerEntityManagerfactorybean entityManagerFactory(BasicDataSource dataSource,HibernateJpavendorAdapter vendor,@Value("${hibernate.dialect}") String dialect,@Value("${jdbc.schema}") String schema) {
        LocalContainerEntityManagerfactorybean entityManagerfactorybean = new LocalContainerEntityManagerfactorybean();
        entityManagerfactorybean.setDataSource(dataSource);
        entityManagerfactorybean.setJpavendorAdapter(vendor);
        Properties jpaProperties = new Properties();

        jpaProperties.put("hibernate.dialect",dialect);
        jpaProperties.put("hibernate.default_schema",schema);
//        jpaProperties.put("hibernate.jdbc.fetch_size",200);
//        jpaProperties.put("hibernate.connection.pool_size",10);
        entityManagerfactorybean.setJpaProperties(jpaProperties);
        entityManagerfactorybean.setPackagesToScan("com.xxx.xxx.relational.entities");
        return entityManagerfactorybean;
    }


    @Bean
    @Autowired
    JpaTransactionManager transactionManager(EntityManagerFactory factory) {
        return new JpaTransactionManager(factory);
    }


    @Bean
    @Autowired
    NamedParameterJdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setFetchSize(200);
        return new NamedParameterJdbcTemplate(jdbcTemplate);
    }


    @Bean
    @Autowired
    SiblingsRepo<LegDTO> legSiblingsRepo(LegRepository legRepo) {
        return new SiblingsRepo<LegDTO>() {
            @Override
            public Optional<LegDTO> byNext(LegDTO next) {
                return legRepo.findByNextLegId(next.getId());
            }

            @Override
            public Optional<LegDTO> byId(Integer id) {
                return legRepo.findById(id);
            }

            @Override
            public LegDTO save(LegDTO sibling) {
                return legRepo.save(sibling);
            }
        };
    }


    @Bean
    @Autowired
    SiblingsRepo<journeyDTO> journeySiblingsRepo(journeyRepository journeyRepo) {
        return new SiblingsRepo<journeyDTO>() {
            @Override
            public Optional<journeyDTO> byNext(journeyDTO next) {
                return journeyRepo.findByNextjourneyId(next.getId());
            }

            @Override
            public Optional<journeyDTO> byId(Integer id) {
                return journeyRepo.findById(id);
            }

            @Override
            public journeyDTO save(journeyDTO sibling) {
                return journeyRepo.save(sibling);
            }
        };
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyConfig() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

这是查询

String q = "select journeyid,extid,extccuid
        from ACTIVE_journey_VIEW
        WHERE coalesce(ata,nextjourneyEtd,sysdate + 1) > sysdate
          AND ((organizationid = :orgId AND deleted = 0)
            OR organizationid = :orgId
            OR enclosingHire_organizationId = :orgId
            OR gln_orig_organizationid = :orgId
            OR originFacilityId in (
                select facilityid
                from v_FacilityAndBbrorg
                where bbrorgid = :orgId
                  and (nvl(atd,etd),activeEndDate) overlaps (fromDate,toDate))
            OR gln_dest_organizationid = :orgId
            OR destinationFacilityId in (
                select facilityid
                from v_FacilityAndBbrorg
                where bbrorgid = :orgId
                  and (nvl(atd,toDate))
            )";

protected <T> List<T> query(String key,RowMapper<T> mapper,Object... kvs) {
    Map<String,Object> params = map(kvs);
    return  jdbcTemplate.queryForList(q,params);
}

解决方法

请记住,在运行查询时,IntelliJ 可能在幕后执行某种优化 - 预取数据切片、使用缓存等。由于这些和其他原因,当应用程序作为纯 Java 代码运行时,这种工具通常会提供更好的性能。

话虽如此,为了改进您的查询,您可以做几件事。

一方面,回顾一下你的sql,我认为可以通过不同的方式改进:

  • 去掉 IN 子句,它们的性能通常很差。使用 JOINS 或者在您的示例中使用 EXISTS 子句。
  • 可能建议对以下列进行索引(无论如何要小心,这些列很多,并且索引维护可能很昂贵并且在运行插入、更新和删除操作时会影响性能,请尝试在查询和 DML 操作之间找到平衡):
    • 组织 ID
    • 封闭Hire_organizationId
    • gln_orig_organizationid
    • gln_dest_organizationid
    • bbrorgid
    • 设施标识
  • 您正在对日期应用 nvlcoalesce 之类的函数:恐怕它们似乎是必要的,但请注意,它可能会阻止使用在该列上创建的任何索引。使用多个 OR 有时也不是最佳选择。
  • 我认为您可以简化与 v_FacilityAndBbrOrg 表相关的查询。

总而言之,请尝试使用与此类似的查询:

String q = "select journeyid,extid,extccuid
        from ACTIVE_JOURNEY_VIEW
        WHERE coalesce(ata,nextJourneyEtd,sysdate + 1) > sysdate
          AND ((organizationid = :orgId AND deleted = 0)
            OR organizationid = :orgId
            OR enclosingHire_organizationId = :orgId
            OR gln_orig_organizationid = :orgId
            OR gln_dest_organizationid = :orgId
            OR EXISTS (
                select 1 
                from v_FacilityAndBbrOrg
                where bbrorgid = :orgId
                  and (facilityid = originFacilityId OR facilityid = destinationFacilityId)
                  and (nvl(atd,etd),activeEndDate) overlaps (fromDate,toDate)
            )
          )";

此外,尝试使用不同的提取大小对您的应用程序进行基准测试。您指出 200 记录似乎很合理。当然,无论如何,根据您的应用程序的可用内存以及行大小等其他方面,尝试增加该数字:最多 1000 条记录的数量就足够了。

最后,尝试衡量您的应用程序何时消耗您的时间,无论是查询数据库、获取结果并转换记录、序列化为 JSON 还是呈现服务器端页面。当然,为此您有多种选择,但乍一看,使用 StopWatch 之类的简单工具可用于粗略地检测粗略可能的瓶颈。 >