Mybatis的parameterType造成线程阻塞问题分析

 更新时间:2023年06月08日 10:58:40   作者:京东云开发者  
这篇文章主要详细分析了Mybatis的parameterType造成线程阻塞问题,文中有详细的解决方法,及相关的代码示例,具有一定的参考价值,感兴趣的朋友可以借鉴阅读

一、前言

最近在新发布某个项目上线时,每次重启都会收到机器的 CPU 使用率告警,查看对应监控,持续时长达 5 分钟,对于服务重启有很大风险。而该项目有非常多 Consumer 消费,服务启动后会有大量线程去拉取消息处理逻辑,通过多次 Jstack 输出线程快照发现有很多 BLOCKED 状态线程,此文主要记录分析 BLOCKED 原因。

二、分析过程

2.1、初步分析

"consumer_order_status_jmq1714_1684822992337" #3125 daemon prio=5 os_prio=0 tid=0x00007fd9eca34000 nid=0x1ca4f waiting for monitor entry [0x00007fd1f33b5000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1027)
    - waiting to lock <0x000000056e822bc8> (a java.util.concurrent.ConcurrentHashMap$Node)
    at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006)
    at org.apache.ibatis.type.TypeHandlerRegistry.getJdbcHandlerMap(TypeHandlerRegistry.java:234)
    at org.apache.ibatis.type.TypeHandlerRegistry.getTypeHandler(TypeHandlerRegistry.java:200)
    at org.apache.ibatis.type.TypeHandlerRegistry.getTypeHandler(TypeHandlerRegistry.java:191)
    at org.apache.ibatis.mapping.ParameterMapping$Builder.resolveTypeHandler(ParameterMapping.java:128)
    at org.apache.ibatis.mapping.ParameterMapping$Builder.build(ParameterMapping.java:103)
    at org.apache.ibatis.builder.SqlSourceBuilder$ParameterMappingTokenHandler.buildParameterMapping(SqlSourceBuilder.java:123)
    at org.apache.ibatis.builder.SqlSourceBuilder$ParameterMappingTokenHandler.handleToken(SqlSourceBuilder.java:67)
    at org.apache.ibatis.parsing.GenericTokenParser.parse(GenericTokenParser.java:78)
    at org.apache.ibatis.builder.SqlSourceBuilder.parse(SqlSourceBuilder.java:45)
    at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:44)
    at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:292)
    at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:83)
	at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
	at com.sun.proxy.$Proxy232.query(Unknown Source)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:77)
	at sun.reflect.GeneratedMethodAccessor160.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
	at com.sun.proxy.$Proxy124.selectOne(Unknown Source)
	at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:166)
	at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:82)
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
        ......

通过对服务连续间隔 1 分钟使用 Jstack 抓取线程快照,发现存在部分线程是 BLOCKED 状态,通过堆栈可以看出,当前线程阻塞在 ConcurrentHashMap.putVal,而 putVal 方法内部使用了 synchronized 导致当前线程被 BLOCKED,而上一级是 Mybaits 的TypeHandlerRegistry,TypeHandlerRegistry 的作用是记录 Java 类型与 JDBC 类型的相互映射关系,例如 java.lang.String 可以映射 JdbcType.CHAR、JdbcType.VARCHAR 等,更上一级是 Mybaits 的 ParameterMapping,而 ParameterMapping 的作用是记录请求参数的信息,包括 Java 类型、JDBC 类型,以及两种类型转换的操作类 TypeHandler。通过以上信息可以初步定位为在并发情况下 Mybaits 解析某些参数导致大量线程被阻塞,还需继续往下分析。

我们可以先回想下 Mybatis 启动加载时的大致流程,查看下流程中哪些地方会操作 TypeHandler,会使用 ConcurrentHashMap.putVal 进行缓存操作?

在 Mybatis 启动流程中,大致分为以下几步:

1、XMLConfigBuilder#parseConfiguration() 读取本地XML文件

2、XMLMapperBuilder#configurationElement() 解析XML文件中的 select|insert|update|delete 标签

3、XMLMapperBuilder#parseStatementNode() 开始解析单条 SQL,包括请求参数、返回参数、替换占位符等

4、SqlSourceBuilder 组合单条 SQL 的基本信息

5、SqlSourceBuilder#buildParameterMapping() 解析请求参数

6、ParameterMapping#getJdbcHandlerMap() 解析 Java 与 JDBC 类型,并把映射结果放入缓存

而在第 6 步时候(图中标色),会去获取 Java 对象类型与 JDBC 类型的映射关系,并把已经处理过的映射关系 TypeHandler 存入本地缓存中。但是堆栈信息显示,还是触发了 TypeHandler 入缓存的操作,也就是某个 paramType 并没有命中缓存,而是在 SQL 查询的时候实时解析 paramType,在高并发情况下造成了线程阻塞情况。下面继续分析下 sql xml 的配置:

<select id="listxxxByMap" parameterType="java.util.Map" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from xxxxx
        where business_id = #{businessId,jdbcType=VARCHAR}
        and template_id = #{templateId,jdbcType=INTEGER}
    </select>

代码请求:

Map<String, Object> params = new HashMap<>();
params.put("businessId", "11111");
params.put("templateId", "11111");
List<TrackingInfo> result = trackingInfoMapper.listxxxByMap(params);

初步看没发现问题,但是我们在入 TypeHandler 缓存时 debug 下,分析下哪种类型在缓存中缺失?

从 debug 信息中可以看出,TypeHandler 缓存中存在的是 interface java.util.Map,而 SQL 执行时传入的是 class java.util.HashMap,导致并没有命中缓存。那我们修改下 xml 文件为 parameterType="java.util.HashMap" 是不是就解决了?

很遗憾,部署后仍然存在问题。

2.2、进一步分析

为了进一步分析,引入了对照组,而对照组的 paramType 为具体 JavaBean。

<select id="listResultMap" parameterType="com.jdwl.xxx.domain.TrackingInfo" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from xxxx
        where business_id = #{businessId,jdbcType=VARCHAR}
        and template_id = #{templateId,jdbcType=INTEGER}
    </select>

对照组代码请求

TrackingInfo record = new TrackingInfo();
record.setBusinessId("11111");
record.setTemplateId(11111);
List<TrackingInfo> result = trackingInfoMapper.listResultMap(record);

在装载参数的 Handler 类 org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters 处进行 debug 分析。

2.2.1、对照组为 listResultMap(paramType=JavaBean)

两个参数的解析类型分别为 StringTypeHandler(红框中灰色的字)与 IntegerTypeHandler(红框中灰色的字),已经是 Mybatis 提供的 TypeHandler,并没有再进行类型的二次解析。说明 JavaBean 中的 businessId、templateId 字段已经在启动时候被预解析了。

2.2.2、实验组为listxxxByMap(paramType=Map)

两个参数的解析都是 UnknownTypeHandler(红框中灰色的字),而在 UnknownTypeHandler 中会再次调用 resolveTypeHandler() 方法,对参数进行类型的二次解析。可以理解为 Map 里的属性不是固定类型,只能在执行 SQL 时候再解析一次。

最后修改为 paramType=JavaBean 部署测试环境再抓包,并未发现 TypeHandlerRegistry 相关的线程阻塞。

三、引申思考

既然 paramType 传值会出现阻塞问题,那 resultType 与 resultMap 是不是有相同问题呢?继续分为两个实验组:

1、对照组(resultMap=BaseResultMap)

<resultMap id="BaseResultMap" type="com.jdwl.tracking.domain.TrackingInfo">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="template_id" property="templateId" jdbcType="INTEGER"/>
        <result column="business_id" property="businessId" jdbcType="VARCHAR"/>
        <result column="is_delete" property="isDelete" jdbcType="TINYINT"/>
        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
        <result column="ts" property="ts" jdbcType="TIMESTAMP"/>
    </resultMap>
<select id="listResultMap" parameterType="com.jdwl.tracking.domain.TrackingInfo" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from tracking_info
        where business_id = #{businessId,jdbcType=VARCHAR}
        and template_id = #{templateId,jdbcType=INTEGER}
    </select>

对照组代码请求:

TrackingInfo record = new TrackingInfo();
record.setBusinessId("11111");
record.setTemplateId(11111);
List<TrackingInfo> result1 = trackingInfoMapper.listResultMap(record);

2、实验组(resultType=JavaBean)

<select id="listResultType" parameterType="com.jdwl.tracking.domain.TrackingInfo" resultType="com.jdwl.tracking.domain.TrackingInfo">
        select
        <include refid="Base_Column_List"/>
        from tracking_info
        where business_id = #{businessId,jdbcType=VARCHAR}
        and template_id = #{templateId,jdbcType=INTEGER}
    </select>

实验组代码请求:

TrackingInfo record = new TrackingInfo();
record.setBusinessId("11111");
record.setTemplateId(11111);
List<TrackingInfo> result2 = trackingInfoMapper.listResultType(record);

在对返回结果 Handler 处理类 org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createAutomaticMappings() 进行 debug 分析。

1、对照组(resultMap=BaseResultMap)

List unmappedColumnNames 长度为 0,表示所有字段都命中了 标签配置,符合预期。

2、实验组(resultType=JavaBean)

List unmappedColumnNames 长度为 11,表示所有字段都在 标签配置中未找到。这是因为 SQL 执行后的 resultMap 对应的 id 并不等于标签的 id,所以这些字段被标识为未解析,又会执行 TypeHandlerRegistry 的类型映射逻辑,引发并发时线程阻塞问题。

四、总结

1、在使用 paramType 时,xml 配置的类型需要与 Java 代码中传入的一致,使用 Mybatis 预加载时的类型缓存。

2、在使用 paramType 时,避免使用 java.util.HashMap 类型,避免 SQL 执行时解析 TypeHandler。

3、在接受返回值时,使用 resultMap,提前映射返回值,减少 TypeHandler 解析。

五、后续

在 Mybatis 社区已经优化了 TypeHandler 入缓存的逻辑,可以解决重复计算 TypeHandler 问题,一定程度上缓解以上问题。但是 Mybatis 修复最低版本为 3.5.8,依赖 spring5.x,而我们项目使用的 Mybatis3.4.4,spring4.x,直接升级会存在一定风险,所以在不升级情况下,按照总结规范使用也可以降低阻塞风险。

以上就是Mybatis的parameterType造成线程阻塞问题分析的详细内容,更多关于Mybatis parameterType 线程阻塞的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot越权和数据权限控制的实现方案(最新整理)

    SpringBoot越权和数据权限控制的实现方案(最新整理)

    文章介绍通过自定义注解@PermissionCheck、切面编程和用户权限服务实现Java权限控制,限制只读用户操作,优化数据库查询并统一异常处理,确保系统安全与扩展性,本文给大家介绍SpringBoot越权和数据权限控制的实现方案,感兴趣的朋友跟随小编一起看看吧
    2025-06-06
  • SpringCloud OpenFeign超时控制示例详解

    SpringCloud OpenFeign超时控制示例详解

    在Spring Cloud中使用OpenFeign时,可以通过配置来控制请求的超时时间,这篇文章主要介绍了SpringCloud OpenFeign超时控制,需要的朋友可以参考下
    2024-05-05
  • java SelectableChannel的使实例用法讲解

    java SelectableChannel的使实例用法讲解

    在本篇文章里小编给大家整理的是一篇关于java SelectableChannel的使实例用法讲解内容,有兴趣的朋友们可以学习下。
    2021-03-03
  • Java多线程锁机制相关原理实例解析

    Java多线程锁机制相关原理实例解析

    这篇文章主要介绍了Java多线程锁机制相关原理实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • java操作文件之文件重命名实现方式

    java操作文件之文件重命名实现方式

    这篇文章主要介绍了java操作文件之文件重命名实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2026-03-03
  • Maven2 plugin开发教程详解

    Maven2 plugin开发教程详解

    这篇文章主要为大家详细介绍了Maven2 plugin的开发教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • Java实现聊天机器人

    Java实现聊天机器人

    这篇文章主要为大家详细介绍了Java简单实现聊天机器人,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • Java中的synchronized 优化方法之锁膨胀机制

    Java中的synchronized 优化方法之锁膨胀机制

    这篇文章主要介绍了Java中的synchronized 优化方法之锁膨胀机制,锁膨胀机制是提升 synchronized 性能最有利的方法之一,下面我们就来看看什么事锁膨胀及锁膨胀的各种细节
    2022-05-05
  • Java后端用EL表达式改进JSP

    Java后端用EL表达式改进JSP

    EL 全名为Expression Language,EL的语法很简单,它最大的特点就是使用上很方便,本文带你用EL表达式改进JSP,感兴趣的朋友来看看吧
    2022-02-02
  • SpringBoot 集成 MyBatis/MyBatis-Plus 超详细教程

    SpringBoot 集成 MyBatis/MyBatis-Plus 超详细教程

    本文详细介绍了SpringBoot集成MyBatis和MyBatis-Plus的完整流程,包括数据源配置、Mapper扫描、XML映射等,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-06-06

最新评论