基於mybatis-plus timestamp返回為null問題的排除
問題是這樣的
在開發時,為瞭節約時間,我選擇瞭mybatis框架來開發,然後又在網上找瞭一個許多人都推薦的mybatis-plus來作為持久層框架。
於是乎我按照官方的DEMO下瞭一個springBoot的mybatis-plus版本的DEMO
這個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後,就可以瞭! 沒想到這麼一個小錯誤,讓我糾結瞭這麼久!還好能跟蹤源碼!
通過這次的問題排查,讓我明白瞭一個道理: 如果不知道某個框架原理的情況下,不要隨便填寫它的配置信息。在享受到框架的便捷性的同時,最好也得要明白它的原理,這樣當出現問題時,才好快速定位。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- mybatis-plus查詢源碼詳解
- 淺談Mybatis SqlSession執行流程
- mybatis plus框架@TableField註解不生效問題及解決方案
- 分析mybatis運行原理
- autoMapping和autoMappingBehavior的區別及說明