mybatis-plus timestamp返回为null问题排除

本文介绍了一位开发者使用MyBatis Plus框架时遇到的问题:查询结果中Timestamp类型的字段显示为空。通过深入研究源码,最终发现是由于开启了驼峰命名规则导致字段映射失败,并给出了相应的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题是这样的:

在开发时,为了节约时间,我选择了mybatis框架来开发,然后又在网上找了一个许多人都推荐的mybatis-plus来作为持久层框架。

于是乎我按照官方的DEMO下了一个springBoot的mybatis-plus版本的DEMO,地址为:https://gitee.com/baomidou/mybatisplus-spring-boot 

这个DEMO是基于H2数据库的,跑了下没有问题。DEMO是能正常运行的。

然后我将这个工程的代码快速拷贝的新的一个工程里,并把数据库由H2换为了MYSQL。但项目跑起来时,出现了如下问题:

数据库里的数据如下图:


表结构如下图:


查询出来的结果中对于timestamp类型的字段为空


为了解决这个问题,我决定通过debug断点观察下是否查询是把数据数据查出来了

由于用的mybatis-plus框架来开发,而mybaits-plus框架是基于mybatis框架的,为了看是否把数据查询出来,直接在ResultSetHandler上把一个dubug就好。在默认情况下,mybatis框架使用的ResultSetHandler为DefaultResultSetHandler,当查询mybatis查询完毕后,会通过ResultSetHandler的handleResultSets(Statement stmt)方法对查询的数据结果集进行封装

所以将断点打在handlerResultSets方法上最为合适。

再通过statement -> wrapper ->results -> rowData ->rows观察发现如下数据:


查询返回的结果集中rows的记录数为1,第1个字段的ascii为49,而49是ascii中数字1的值。而第二个字段也有值,说明所对应的timestamp字段是有返回值的,数据库查询是没有问题的。

然而通过mybatis代码进一步封装后的数据multipleResults又表示,只查询到了类型为Int的字段的数据

这么也就是说,问题应该是出在了multipleResults的赋值问题上了

 handleResultSets的完整代码为:

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

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

  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);
}
说明问题出在  handleResultSet(rsw, resultMap, multipleResults, null);这句代码上了

通过代码跟踪,发现如下代码:

//
// GET VALUE FROM ROW FOR SIMPLE RESULT MAP
//

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
  if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    boolean foundValues = this.useConstructorMappings;
    if (shouldApplyAutomaticMappings(resultMap, false)) {
      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
    }
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    rowValue = (foundValues || configuration.isReturnInstanceForEmptyRow()) ? rowValue : null;
  }
  return rowValue;
}

继而发现如下的核心代码:

private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
  boolean foundValues = false;
  if (!autoMapping.isEmpty()) {
    for (UnMappedColumnAutoMapping mapping : autoMapping) {
      final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
      if (value != null) {
        foundValues = true;
      }
      if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
        // gcode issue #377, call setter on nulls (value is not 'found')
        metaObject.setValue(mapping.property, value);
      }
    }
  }
  return foundValues;
}
通过断点发现以下数据


在获取这个查询的的返回字段时,只获取出来两个,即int类型和varchar类型的.

再通过跟踪发现了如下代码:

private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  final String mapKey = resultMap.getId() + ":" + columnPrefix;
  List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
  if (autoMapping == null) {
    autoMapping = new ArrayList<UnMappedColumnAutoMapping>();
    final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
    for (String columnName : unmappedColumnNames) {
      String propertyName = columnName;
      if (columnPrefix != null && !columnPrefix.isEmpty()) {
        // When columnPrefix is specified,
        // ignore columns without the prefix.
        if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
          propertyName = columnName.substring(columnPrefix.length());
        } else {
          continue;
        }
      }
      final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
      if (property != null && metaObject.hasSetter(property)) {
        if (resultMap.getMappedProperties().contains(property)) {
          continue;
        }
        final Class<?> propertyType = metaObject.getSetterType(property);
        if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
          final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
          autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
        } else {
          configuration.getAutoMappingUnknownColumnBehavior()
              .doAction(mappedStatement, columnName, property, propertyType);
        }
      } else {
        configuration.getAutoMappingUnknownColumnBehavior()
            .doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
      }
    }
    autoMappingsCache.put(mapKey, autoMapping);
  }
  return autoMapping;
}
直到看到这里  
final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
才知道问题所在了,  configuration.isMapUnderscoreToCamelCase()的值为true,即开启了驼峰命令。所以查找字段时就找不到。
把之前的类似crt_time改为crtTime后,就可以了!
没想到这么一个小错误,让我纠结了这么久!还好能跟踪源码!
通过这次的问题排查,让我明白了一个道理: 如果不知道某个框架原理的情况下,不要随便填写它的配置信息。在享受到框架的便捷性的同时,最好也得要明白它的原理,这样当出现问题时,才好快速定位。

### 解决 MyBatis-Plus 中日期格式转换的方法 在处理数据库中的日期字段时,可能会遇到 Java 实体类属性类型与数据库字段类型的不一致情况。为了确保应用程序能够正确读取和写入这些日期值,可以利用 MyBatis-Plus 的自定义类型处理器功能来完成特定的数据转换逻辑。 #### 创建自定义类型处理器 对于日期格式化的需求,可以通过继承 `org.apache.ibatis.type.BaseTypeHandler` 类并重写相应方法来自定义处理器: ```java import com.baomidou.mybatisplus.core.handlers.AbstractSqlParserHandler; import org.apache.ibatis.type.JdbcType; public class DateTypeHandler extends BaseTypeHandler<LocalDateTime> { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @Override public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType) throws SQLException { ps.setTimestamp(i, Timestamp.valueOf(parameter)); } @Override public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException { java.sql.Timestamp timestamp = rs.getTimestamp(columnName); return null == timestamp ? null : timestamp.toLocalDateTime(); } @Override public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException { java.sql.Timestamp timestamp = rs.getTimestamp(columnIndex); return null == timestamp ? null : timestamp.toLocalDateTime(); } @Override public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { java.sql.Timestamp timestamp = cs.getTimestamp(columnIndex); return null == timestamp ? null : timestamp.toLocalDateTime().format(FORMATTER); } } ``` 此代码片段展示了如何创建一个名为 `DateTypeHandler` 的自定义类型处理器[^1]。该处理器负责将 JDBC 时间戳转换为 Java8 的 `LocalDateTime` 对象,并按照指定模式进行字符串表示。 #### 注册全局类型处理器 为了让 MyBatis-Plus 使用上述自定义的类型处理器,需将其注册到全局配置文件中。可以在 application.yml 或者其他 Spring Boot 配置文件里添加如下设置: ```yaml mybatis-plus: type-handlers-package: com.example.handler # 替换成实际包路径 ``` 这一步骤告知框架去哪里查找已定义好的类型处理器类。 #### 应用场景下的注意事项 当涉及到不同版本之间的差异时,需要注意所使用的 MyBatis-Plus 是否支持某些特性或接口。例如,在较新版本中推荐使用 `PaginationInnerInterceptor` 来替代旧版的分页拦截器[^3]。不过这部分内容主要针对分页插件优化而非直接关联于日期格式转换问题上。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水中加点糖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值