美文网首页
使用entityManager.createNativeQuer

使用entityManager.createNativeQuer

作者: 首席潜水官 | 来源:发表于2020-07-20 18:42 被阅读0次

    一、问题描述

            业务场景是从第三方系统每天同步两次13万数据到我们的数据库。一开始使用jpa的saveAll() 进行保存,并没有内存问题。但是保存的时间在15分钟左右。于是做了部分改进使用entityManager.createNativeQuery(sql)的方式进行批量写库。改造后每次定时任务大概在2分钟跑完。效率改进还挺满意的。大致伪代码如下:

    public int sqlSaveBusinessList(List<TableDTO> list) {

            StringBuilder sb = new StringBuilder();

            sb.append("insert INTO t_supplier_report_summary (id, a, b, c") VALUES ");

            for (int i = 0; i < list.size(); i++) {

                TableDTO entity = list.get(i);

                sb.append("...")

            }

            Query insert = entityManager.createNativeQuery(sb.toString());

            return insert.executeUpdate();

        }

    上线两天后,开始收到线上告警, ERROR o.s.integration.handler.LoggingHandler - java.lang.OutOfMemoryError: Java heap space

    开发环境还原的报错信息

    二、问题分析

    使用JvisualVM工具对进程进行监控。发现堆内存阶梯增加。虽然有gc但是有一部分并未释放。

    堆内存监控

    Dump一下

    第一次dump

    再Dump一下

    第二次dump

    又多了一条insert语句的实例。第二dump之前我有意触发了一次手工的gc。结果实例还在。证明这个是导致内存溢出的主要原因。对象大小4m还不能给回收。

    对象的引用

    看了一下引用,发现被QueryPlanCache持有

    QueryPlanCache

    每次entityManager.createNativeQuery(sql) 会把sql的执行计划缓存起来。但是由于是sql拼接了参数的所以每次sql执行都是不一样。所以执行计划也不一样。导致每次缓存一个

    且只有在 SessionFactory 关闭的时候才会clear缓存。

    三、解决方法

    1、用占位符代替字符串参数拼接。

    2、换jdbc saveAll() 性能较慢

    3、用jdbcTemplate代替entityManager

    相关文章

      网友评论

          本文标题:使用entityManager.createNativeQuer

          本文链接:https://www.haomeiwen.com/subject/kypphktx.html