【问题定位】通过看Mybatis源码解决系统问题

开发需求好好的,运维同事突然发现了一个问题,某个任务的详情页面加载不出来。看日志,系统在进行查询操作的时候抛出空指针异常。感觉是Mybatis内部异常,所以就跟踪源码看下Mybatis运行到哪一步报错的。

DefaultSqlSession#selectOne(java.lang.String, java.lang.Object),空指针异常的地方是从DefaultSqlSession报出来的,由于查询结果返回null,导致调用size方法报错。

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

BaseExecutor#query()BaseExecutor的query方法是Mybatis执行查询sql的必经之路,这边有一级缓存和二级缓存的知识点。

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

SimpleExecutor#doQuery,这个方法有两个重要的方法调用,prepareStatement()主要是给预编译的sql设置参数,handler.query(stmt, resultHandler);是获取数据,并进行对象封装。

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

DefaultResultSetHandler#handleResultSets,将查询出来的数据集合挨个解析。

  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

DefaultResultSetHandler#applyPropertyMappings,该方法是将挨个数据库中的列数据映射到属性中。当系统遍历到日期字段,end的时候,该方法报错,跳出。

  private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      if (propertyMapping.getNestedResultMapId() != null) {
        // the user added a column attribute to a nested result map, ignore it
        column = null;
      }
      if (propertyMapping.isCompositeResult()
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          || propertyMapping.getResultSet() != null) {
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        // issue #541 make property optional
        final String property = propertyMapping.getProperty();
        if (property == null) {
          continue;
        } else if (value == DEFERRED) {
          foundValues = true;
          continue;
        }
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

因此发现该行数据的end字段有问题,用Datagrip查询,该字段的数据是2023-11-31 23:59:59,而2023年11月份是没有31号的,所以mybatis赋值会报错。mysql允许非法的日期格式插入,但是mybatis对于这非法的数据处理是处理不了的。

总结:

  1. mybatis查询的核心流程就是解析sql,执行sql,封装对象。空指针异常不是对象为空,可能是mybatis在数据处理的过程中有异常。
  2. 注意数据插入的合法性。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/178051.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode算法题解(动态规划)|LeetCode343. 整数拆分、LeetCode96. 不同的二叉搜索树

一、LeetCode343. 整数拆分 题目链接&#xff1a;343. 整数拆分 题目描述&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入…

【Linux】-进程间通信-共享内存(SystemV),详解接口函数以及原理(使用管道处理同步互斥机制)

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

pyqt5的组合式部件制作(四)

对组合式部件的制作又改进了一版&#xff0c;组合式部件的子部件不再需要单独“提升为”&#xff0c;如果在模板文件的提升部件窗口内选择了“全局包含”&#xff0c;那么只需要在模板文件和应用文件中直接复制粘贴即可&#xff0c;部件的应用更为简便。如下图&#xff1a;按住…

docker、elasticsearch8、springboot3集成备忘

目录 一、背景 二、安装docker 三、下载安装elasticsearch 四、下载安装elasticsearch-head 五、springboot集成elasticsearch 一、背景 前两年研究了一段时间elasticsearch&#xff0c;当时也是网上找了很多资料&#xff0c;最后解决个各种问题可以在springboot上运行了…

>Web 3.0顶级干货教学:浅析区块链与货币关系

Web 3.0顶级干货教学&#x1f525;&#xff1a;浅析区块链与货币关系 尊重原创&#xff0c;编写不易 &#xff0c;帮忙点赞关注一下~转载小伙伴请注明出处&#xff01;谢谢 1.0 数字交易 最早一笔数字化交易 是在www.PizzaHut.com 在 1994 年产生的&#xff0c;但是有趣的事情…

oracle面试相关的,Oracle基本操作的SQL命令

文章目录 数据库-Oracle〇、Oracle用户管理一、Oracle数据库操作二、Oracle表操作1、创建表2、删除表3、重命名表4、增加字段5、修改字段6、重名字段7、删除字段8、添加主键9、删除主键10、创建索引11、删除索引12、创建视图13、删除视图 三、Oracle操作数据1、数据查询2、插入…

linux部署jar 常见问题

1.java -jar xxx.jar no main manifest attribute, in xxx.jar 一.no main manifest attribute, in xxx.jar 在pom.xml文件中加入&#xff1a; <plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifac…

【DevOps】Git 图文详解(八):后悔药 - 撤销变更

Git 图文详解&#xff08;八&#xff09;&#xff1a;后悔药 - 撤销变更 1.后悔指令 &#x1f525;2.回退版本 reset3.撤销提交 revert4.checkout / reset / revert 总结 发现写错了要回退怎么办&#xff1f;看看下面几种后悔指令吧&#xff01; ❓ 还没提交的怎么撤销&#x…

QTableWidget——编辑单元格

文章目录 前言熟悉QTableWiget&#xff0c;通过实现单元格的合并、拆分、通过编辑界面实现表格内容及属性的配置、实现表格的粘贴复制功能熟悉QTableWiget的属性 一、[单元格的合并、拆分](https://blog.csdn.net/qq_15672897/article/details/134476530?spm1001.2014.3001.55…

Linux中的进程程序替换

Linux中的进程程序替换 1. 替换原理2. 替换函数3. 函数解释4. 命名理解程序替换的意义 1. 替换原理 替换原理 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的…

Vue 定义只读数据 readonly

readonly 让一个响应式数据变为 **深层次的只读数据**。 isReadonly 判断一个数据是不是只读数据。 应用场景&#xff1a;不希望数据被修改时使用。 readonly 深层次只读&#xff1a; <template><h1>reactive数据</h1><p>姓名&#xff1a;{{ info…

BUUCTF 梅花香之苦寒来 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 注意&#xff1a;得到的 flag 请包上 flag{} 提交 密文&#xff1a; 下载附件&#xff0c;解压得到一张.jpg图片。 解题思路&#xff1a; 1、用010 Editor看了一下&#xff0c;刚开始以为是修改宽高的题&#xff…

【C++干货铺】list的使用 | 模拟实现

个人主页点击直达&#xff1a;小白不是程序媛 C专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 list的介绍及使用 list的介绍 list的使用 list的构造 list迭代器的使用 list的增删查改 list的模拟实现 结点的封装 迭代器的封装 list成员变量 构造函数 …

22LLMSecEval数据集及其在评估大模型代码安全中的应用:GPT3和Codex根据LLMSecEval的提示生成代码和代码补全,CodeQL进行安全评估

LLMSecEval: A Dataset of Natural Language Prompts for Security Evaluations 写在最前面主要工作 课堂讨论大模型和密码方向&#xff08;没做&#xff0c;只是一个idea&#xff09; 相关研究提示集目标NL提示的建立NL提示的建立流程 数据集数据集分析 存在的问题 写在最前面…

一篇五分生信临床模型预测文章代码复现——Figure 10.机制及肿瘤免疫浸润(六)

之前讲过临床模型预测的专栏,但那只是基础版本,下面我们以自噬相关基因为例子,模仿一篇五分文章,将图和代码复现出来,学会本专栏课程,可以具备发一篇五分左右文章的水平: 本专栏目录如下: Figure 1:差异表达基因及预后基因筛选(图片仅供参考) Figure 2. 生存分析,…

【开源】基于Vue和SpringBoot的高校宿舍调配管理系统

项目编号&#xff1a; S 051 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S051&#xff0c;文末获取源码。} 项目编号&#xff1a;S051&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能需求2.1 学生端2.2 宿管2.3 老师端 三、系统…

C语言每日一题(35)有效的括号

力扣网 20 有效的括号 题目描述 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右…

黑马React18: Redux

黑马React: Redux Date: November 19, 2023 Sum: Redux基础、Redux工具、调试、美团案例 Redux介绍 Redux 是React最常用的集中状态管理工具&#xff0c;类似于Vue中的Pinia&#xff08;Vuex&#xff09;&#xff0c;可以独立于框架运行 作用&#xff1a;通过集中管理的方式管…

Vue生成二维码并进行二维码图片下载

1、安包 npm install vue-qr --save2、引入 // vue2.0 import VueQr from vue-qr // vue3.0 import VueQr from vue-qr/src/packages/vue-qr.vue new Vue({components: {VueQr} })<!-- 设备二维码 对话框 270px--><el-dialog title"点位二维码" :visible.…