spring-data-jpa自定义查询导致jdbc连接池占满

摘要:
`接口@OverridepublicPage<selectSql.and(“selectxx.aa;param3”).append(“…”):查询selectQuery=em.createNativeQuery(selectSql.toString());list=selectQuery.unwrap(SQLQuery.class).addScalar(“param1”;

最近在测试环境遇到一次jdbc连接池占满的问题。背景如下:
有一个批量操作,分页去查表数据然后进行后续处理,该查询跨表并且需要返回自定义的字段。
spring-data-jpa提供了方便使用的JpaRepository接口,依次继承PagingAndSortingRepositoryCrudRepositoryRepository
自定义查询一般步骤如下:

  1. 定义包含自定义查询的接口
public interface XxxRepositoryCustom {
    Page<XxxRespVo> findXxx(XxxReqVo, Pageable pageable);
}
其中`findXxx`为自定义的查询接口
  1. 定义表对应的接口,同时继承JpaRepositoryXxxRepositoryCustom
@Repository
public interface XxxRepository extends JpaRepository<Xxx, Long>, XxxRepositoryCustom, JpaSpecificationExecutor<Xxx> {
    ...
}

注:这里同时继承了`JpaSpecificationExecutor<Xxx>`接口,便于同时能使用`Specification`查询功能。
  1. 实现XxxDao接口,实现自定义的查询方法
@Repository
public class XxxRepositoryImpl implements XxxRepositoryCustom {

    @PersistenceContext
    private EntityManager em;
    
    @Override
    public Page<XxxRespVo> findSimpleDepotProductList(XxxReqVo reqVo, Pageable pageable) {
         ...
        StringBuilder selectSql = new StringBuilder();
        selectSql.append("select xx.aa,yy.bb,zz.cc")
                        .append(" from xx join yy join zz ...")
                        .append(" where param1=:param1")
                        .append(" where param2=:param2")
                        .append(" where param3=:param3")
                        .append("...");

        Query selectQuery = em.createNativeQuery(selectSql.toString());
        selectQuery.setParameter("xx", xx);

        List<XxxRespVo> list = selectQuery.unwrap(SQLQuery.class)
                .addScalar("param1", StandardBasicTypes.STRING)
                .addScalar("param2", StandardBasicTypes.STRING)
                .addScalar("param3", StandardBasicTypes.LONG)
                .setResultTransformer(Transformers.aliasToBean(XxxRespVo.class))
                .setFirstResult(pageable.getPageNumber() * pageable.getPageSize())
                .setMaxResults(pageable.getPageSize())
                .list();
        return new PageImpl<>(list, pageable, total);
    }
}

用单元测试调该查询方法成功,在测试环境跑,结果一会儿前端界面调该服务的其它都变慢了,查看日志发现了大量异常:

|ERROR|2020-10-27 16:30:21.811|DubboServerHandler-192.168.x.x:20001-thread-461|c.a.d.r.f.ExceptionFilter:85
      [DUBBO] Got unchecked and undeclared exception which called by 192.168.x.x. service: com.xxx.XxxService, method: findXxx, exception: org.springframework.orm.jpa.JpaSystemException: Unable to acquire JDBC Connection; nested exception is org.hibernate.exception.GenericJDBCException: Unable to acquire JDBC Connection, dubbo version: 2.6.2, current host: 192.168.x.x

org.springframework.orm.jpa.JpaSystemException: Unable to acquire JDBC Connection; nested exception is org.hibernate.exception.GenericJDBCException: Unable to acquire JDBC Connection
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:314)
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:225)
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:417)
        at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
        at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
       ...

Caused by: org.hibernate.exception.GenericJDBCException: Unable to acquire JDBC Connection
        at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47)
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
       ...

Caused by: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60000, active 50, maxActive 50
        at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1139)
        at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:960)
        at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:940)
        at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:930)
        at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:102)
        at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122)
        at org.hibernate.internal.AbstractSessionImpl$NonContextualJdbcConnectionAccess.obtainConnection(AbstractSessionImpl.java:386)
        at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.java:84)
        ... 85 common frames omitted  

注意到Unable to acquire JDBC ConnectionGetConnectionTimeoutException,获取数据库连接池超时了。
测试环境的jdbc连接池配置如下:

<jdbc.initialSize>2</jdbc.initialSize>
<jdbc.minIdle>2</jdbc.minIdle>
<jdbc.maxActive>50</jdbc.maxActive>
<jdbc.maxWait>60000</jdbc.maxWait>

最大活跃链接数50,等待时间60秒,跟日志中的异常信息对应。

推测可能是连接没有关闭,改小maxActive值,用单元测试循环去调,很快复现出了测试环境的报错异常。

检查发现,这里使用了自定义查询,但缺少事务的注解,连接没有自动关闭。

解决方法:加上spring的事务注解,因为是查询,设置为只读能一定程度提高性能。

@Transactional(readOnly = true)
@Override
public Page<XxxRespVo> findSimpleDepotProductList(XxxReqVo reqVo, Pageable pageable) {
...
}   

参考:
spring-data-jpa官方文档:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations

免责声明:文章转载自《spring-data-jpa自定义查询导致jdbc连接池占满》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇OTA配置服务器11正则表达式RE下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

org.xml.sax.SAXParseException: 元素类型 "input" 必须由匹配的结束标记 "&amp;lt;/input&amp;gt;" 终止。

错误记录 Spring Boot推荐使用thymeleaf作为视图,按照SpringBoot实战一书的案例写Demo。 发生错误: org.xml.sax.SAXParseException: 元素类型 "input" 必须由匹配的结束标记 "</input>" 终止。 at com.sun.org.apache.xerces.interna...

RPi 树莓派 DSI 接口研究 MIPI raspberry pi

之前一直在玩树莓派,发现有个DSI显示接口一直没有被用上,经过一番研究发现有点意思,记录一下相关资料以后再说。 (update1: 目前全网已经有非常多的方案研究hdmi和mipi的互转方案: a. ) mipi屏幕+hdmi接口:研究最多因为mipi屏幕很多且参数美好。详情google,感谢包括稚晖在内的各路大神的研发,例如pocketLCD方案。 其中...

API文档管理平台

一、应用场景 在公司中,有很多开发,每个人维护的api接口是不一样的。如果有一个统一的api文档管理平台,每个开发,把自己维护的接口录入进去。 之后再开发别的功能时,不需要重复造轮子,直接调用就可以了。有新员工入职时,也可以快速上手! 关于api文档管理,网上有很多。有在线收费的,也有开源的。基于节省成本考虑,这里主要介绍2个开源工具:ShowDoc和YA...

JDBC 基础入门

由于我也是初学参考的是网上的或者是培训机构的资料所以可能会有错误的信息,仅供参考 一、什么是JDBC(Java Data Base Connectivity)? java程序连接数据库,JDBC是由SUN公司提出的一组规范,这组规范主要由一组接口构成,主要作用就是访问数据库。 二、JDBC核心思想【思想重要】     三、核心API【重点】      ...

UML类图基本画法

类 简要画法 类有三个单元格的矩形(看上图中的动物类) 第一格:类名称(如果是抽象类,名称标注为斜体字) 第二格:类属性名称 第三格:类操作名称 类属性或者操作的访问修改符的标注: public用加号标注 private用减号标注 protected用#号标注 接口 简要画法 接口有两个单元格的矩形(看上图中的飞翔接口) 第一格:接口名称(名称...

freemarke之TemplateDirectiveModel详解

http://hougbin.iteye.com/blog/1457924 TemplateDirectiveModel接口是freemarker自定标签或者自定义指令的核心处理接口。通过实现该接口,用户可以自定义标签(指令)进行任意操作,、 任意文本写入模板的输出。 该接口中只定义了如下方法,当模板页面遇到用户自定义的标签指令时,该方法会被执行。 pub...