亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Mybatis返回類型為Map時遇到的類型轉(zhuǎn)化的異常問題

 更新時間:2023年12月07日 08:45:02   作者:猩猩丶燈  
這篇文章主要介紹了Mybatis返回類型為Map時遇到的類型轉(zhuǎn)化的異常問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

一、問題背景

為了滿足通用化的設(shè)計,項目中通過入?yún)碇付ú樵兊臄?shù)據(jù)表并以Map的形式獲取所有數(shù)據(jù),同時希望所有取得的數(shù)據(jù)都是String的格式,因此使用List<Map<String, String>>來存儲取得的數(shù)據(jù)。

Mapper.java

List<Map<String, String>> selectAllByStatement(...)

Mapper.xml

<select id="selectAllByStatement" parameterType="java.lang.String" resultType="java.util.Map">
    ...
</select>

二、遇到的問題

從數(shù)據(jù)庫中取得的數(shù)據(jù)無法被正常使用↓

List<Map<String, String>> maps = mapper.selectAllByStatement(tableName, request, statement);
Double val1 = Double.valueOf(maps.get(0).get("gmv"));  // java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
Double val2 = Double.valueOf(maps.get(0).get("gmv").toString());  // java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
Double val3 = maps.get(0).get("gmv");  // 編譯報錯

由于gmv字段在數(shù)據(jù)庫中的數(shù)據(jù)類型是double

結(jié)果導(dǎo)致maps.get(0)中的數(shù)據(jù),編譯時看上去是Map定義的類型String,運行時是數(shù)據(jù)庫中對應(yīng)的類型Double,然后出現(xiàn)以上錯誤。具體錯誤原因沒有細究

三、解決思路

方法一

Mapper中返回類型定義為 Object,即:

List<Map<String, Object>> selectAllByStatement(...)

每次使用的時候,可以toString()轉(zhuǎn)化再使用

方法二

本文主要討論這種方式

mybatis從數(shù)據(jù)庫中讀取數(shù)據(jù),并以反射的方式生成Object的對象

然后使用對應(yīng)類型的TypeHandler對其進行數(shù)據(jù)類型的轉(zhuǎn)化

因此,我們需要在TypeHandler上做手腳,自定義TypeHandler并將其注冊到TypeHandlerMap中,讓特定的數(shù)據(jù)類型轉(zhuǎn)化走我們的TypeHandler

在此次問題中,我們以gmv字段為例,

首先邏輯走到 6.1的24行 handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);

propertyTypeObject,jdbcTypeDouble.class

但是mybatis沒有定義ObjectDouble的handler

所以會走到 6.1的32行 handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);

javaType是從Double,jdbcTypeDouble.class

然后會匹配到org.apache.ibatis.type.DoubleTypeHandler,把數(shù)據(jù)轉(zhuǎn)換成Double

所以,我們該怎么做呢,我們可以定義一個ObjectDouble的Handler,讓他在6.1的24行的時候匹配到,里面轉(zhuǎn)化的邏輯我們定義成Object.toString(),具體操作看《五、具體實現(xiàn)》

四、mybatis處理查詢結(jié)果的源碼

本節(jié)是對mybatis查詢數(shù)據(jù)時,對查詢結(jié)果的處理過程的個人理解

1、查詢

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 執(zhí)行查詢
    ps.execute();
    // 處理查詢結(jié)果  --> 2.1
    return resultSetHandler.handleResultSets(ps);
  }

2、準備處理查詢結(jié)果

2.0.1

public class ResultSetWrapper {
  private final ResultSet resultSet;  // 保存著原始數(shù)據(jù)
  private final TypeHandlerRegistry typeHandlerRegistry;
  private final List<String> columnNames = new ArrayList<>(); // 數(shù)據(jù)庫列名
  private final List<String> classNames = new ArrayList<>(); // 對應(yīng)的Java類型
  private final List<JdbcType> jdbcTypes = new ArrayList<>(); // 對應(yīng)的數(shù)據(jù)庫類型
  private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
  private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
  private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();
}

2.1 org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForSimpleResultMap

逐行處理并存儲數(shù)據(jù)

// rsw 保存著查詢結(jié)果的各種相關(guān)數(shù)據(jù)  --> 2.0.1
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
  DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  ResultSet resultSet = rsw.getResultSet();
  skipRows(resultSet, rowBounds);
  // 一行行處理
  while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
    ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
    // 1. 處理:數(shù)據(jù)對象的生成、類型轉(zhuǎn)化等等  
    Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
    // 2. 存儲處理的結(jié)果
    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  }
}

3、每一行的處理

解析數(shù)據(jù)庫中取得的數(shù)據(jù)rsw,反射生成Java的類型

3.0.1

public class MetaObject {
  private final Object originalObject;  // 指向數(shù)據(jù)對象
  private final ObjectWrapper objectWrapper;
  private final ObjectFactory objectFactory;
  private final ObjectWrapperFactory objectWrapperFactory;
  private final ReflectorFactory reflectorFactory;
}

3.1 將數(shù)據(jù)庫「一行」數(shù)據(jù)轉(zhuǎn)化為對象,并逐字段解析成對應(yīng)的數(shù)據(jù)類型

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  // 1. 創(chuàng)建結(jié)果對象map - 數(shù)據(jù)實例化BaseTypeHandler#getNullableResult
  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); 
  if (rowValue != nmonomialull && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    // 2. 創(chuàng)建MetaObject對象,「并將成員變量originalObject指向rowValue」,之后在操作直接對metaObject進行操作;  MetaObject --> 3.0.2
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    boolean foundValues = this.useConstructorMappings;
    // 3. 是否應(yīng)用自動映射,也就是通過resultType進行映射
    if (shouldApplyAutomaticMappings(resultMap, false)) {
      // 3.1 主要處理流程 --> 4.1
      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
    }
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
  }
  // 解析完后返回metaObject.originalObject
  return rowValue;
}

4、數(shù)據(jù)類型自動映射

4.0.1

private static class UnMappedColumnAutoMapping {
  private final String column;  // 列名
  private final String property;  // 屬性名
  private final TypeHandler<?> typeHandler;  // 該列數(shù)據(jù)的數(shù)據(jù)類型轉(zhuǎn)化Handler
  private final boolean primitive;
}

4.1 為每一列字段找到他的轉(zhuǎn)換Handler

  • 通過Handler反射生成他們在Java中的類型
  • 將反射生成的對象放入metaObject.originalObject
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  // 包含每一列數(shù)據(jù)類型對應(yīng)的轉(zhuǎn)化Handler  --> 5.1
  List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
  boolean foundValues = false;
  if (!autoMapping.isEmpty()) {
    for (UnMappedColumnAutoMapping mapping : autoMapping) {
      // 數(shù)據(jù)轉(zhuǎn)換;表面為Object實際為MySQL對應(yīng)的類型
      final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);  
      if (value != null) {
        foundValues = true;
      }
      if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
        // 將反射生成的對象放入metaObject.originalObject中
        metaObject.setValue(mapping.property, value);
      }
    }
  }
  return foundValues;
}

5、為每個字段匹配轉(zhuǎn)換Handler

5.1 得到每個字段的對應(yīng)的Handler 的List(Handler 包在UnMappedColumnAutoMapping里)

private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    autoMapping = new ArrayList<>();
    final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
    // 1. 對于每一列 分別去找他們的 Handler
    for (String columnName : unmappedColumnNames) {
        //...
        if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
          // 得到該類型 的 TypeHandler。 // 優(yōu)先用屬性類型property type匹配`TypeHandler`,如果沒有再用column JDBC Type去匹配
          final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
          autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
        } 
        //...
    }
    return autoMapping;
}

6、字段匹配Handler

6.1 優(yōu)先用屬性類型property type匹配TypeHandler,如果沒有再用column JDBC Type去匹配

public class ResultSetWrapper {
    private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();  // 緩存

    /**
       * Gets the type handler to use when reading the result set.
       * Tries to get from the TypeHandlerRegistry by searching for the property type.
       * If not found it gets the column JDBC type and tries to get a handler for it.
       */
    public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
      TypeHandler<?> handler = null;
      // 先找緩存  // typeHandlerMap 作為緩存
      Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);  // Map<String, Map<Class<?>, TypeHandler<?>>>
      if (columnHandlers == null) {
        columnHandlers = new HashMap<>();
        typeHandlerMap.put(columnName, columnHandlers);
      } else {
        handler = columnHandlers.get(propertyType);
      }
      // 沒有緩存的話↓
      if (handler == null) {
        // 獲得列名對應(yīng)的數(shù)據(jù)庫中的類型JdbcType
        JdbcType jdbcType = getJdbcType(columnName);  // 數(shù)據(jù)庫中的類型
		// ** 用屬性類型propertyType去找 **  --> 7.1
        handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
        // ↑ 用屬性類型propertyType沒找到的話 就 ↓
        if (handler == null || handler instanceof UnknownTypeHandler) {
          // 找該列在數(shù)據(jù)庫的實際類型在Java中對應(yīng)的javaType
          final int index = columnNames.indexOf(columnName);
          final Class<?> javaType = resolveClass(classNames.get(index));
          if (javaType != null && jdbcType != null) {
            // ** 用數(shù)據(jù)庫類型去找 **  --> 7.1
            handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
          } 
          // ...
        }
        if (handler == null || handler instanceof UnknownTypeHandler) {
          handler = new ObjectTypeHandler();
        }
        // 緩存記錄
        columnHandlers.put(propertyType, handler);
      }
      return handler;
    }
}

7、給定Type和jdbcType匹配Handler

7.1 org.apache.ibatis.type.TypeHandlerRegistry#getTypeHandler

根據(jù)type和jdbcType一起查符合的Handler

一個propertyType會對應(yīng)多個jdbcType的Handler

所以存儲Handler的變量類型是 Map<Type, Map<JdbcType, TypeHandler<?>>>

private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
  if (ParamMap.class.equals(type)) {
    return null;
  }
  /** 
   *  一個propertyType會對應(yīng)多個jdbcType的Handler
   *  所以存儲Handler的變量類型是 Map<Type, Map<JdbcType, TypeHandler<?>>>  
   **/
  // 先根據(jù)propertyType去找  --> 7.2
  Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
  TypeHandler<?> handler = null;
  if (jdbcHandlerMap != null) {
    // 找到之后,第二層Map再用jdbcType去找
    handler = jdbcHandlerMap.get(jdbcType);
    if (handler == null) {
      // 如果沒找到走默認的,UnknownTypeHandler
      handler = jdbcHandlerMap.get(null);
    }
    if (handler == null) {
      // #591
      handler = pickSoleHandler(jdbcHandlerMap);
    }
  }
  // type drives generics here
  return (TypeHandler<T>) handler;
}

7.2 給定Java中的類型,查找對應(yīng)的Handler

// 給定Java中的類型,查找對應(yīng)的Handler
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
  Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);  // typeHandlerMap在初始化時,JDBC Handler被手動注冊上的  --> 8.1
  //...
  return jdbcHandlerMap;
}

8、Handler的初始化

8.1 org.apache.ibatis.type.TypeHandlerRegistry

TypeHandlerRegistry 的構(gòu)造函數(shù)會把所有的類型和他對應(yīng)的Handler都注冊到Map上

public final class TypeHandlerRegistry {
  // 儲存所有的Handler
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
    
  public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());
    //... 把所有常規(guī)的映射情況都注冊上了
    register(Reader.class, new ClobReaderTypeHandler());
    register(String.class, new StringTypeHandler());
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
  }
    
  private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
      if (javaType != null) {
        Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
        if (map == null || map == NULL_TYPE_HANDLER_MAP) {
          map = new HashMap<>();
          typeHandlerMap.put(javaType, map);  // typeHandlerMap: Map<Type, Map<JdbcType, TypeHandler<?>>>
        }
        map.put(jdbcType, handler);
      }
      allTypeHandlersMap.put(handler.getClass(), handler);
  }
}

五、具體實現(xiàn)

實現(xiàn)Handler

自定義TypeHanlder需要繼承BaseTypeHandler

@MappedJdbcTypes@MappedTypes來定義此Handler應(yīng)用于哪些類型的轉(zhuǎn)換

@MappedTypes指定字段在Java的數(shù)據(jù)類型

@MappedJdbcTypes指定字段在數(shù)據(jù)庫中的類型

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

@MappedTypes({Object.class})
@MappedJdbcTypes({JdbcType.DOUBLE, JdbcType.DATE, JdbcType.BIGINT, JdbcType.TIMESTAMP, JdbcType.INTEGER})
public class MapToStringTypeHandler extends BaseTypeHandler<Object> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        if (parameter instanceof Date){
            ps.setDate(i, new java.sql.Date(((Date)parameter).getTime()));
        } else {
            ps.setString(i, parameter.toString());
        }
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        return rs.getString(columnName);
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        return rs.getString(columnIndex);
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        return cs.getString(columnIndex);
    }
}

配置Handler

在Mybatis 的配置類中指定自定義Handler的包

com.wujie.pandora.repository.config.DataAnalysisMybatisConfig#buildSqlSessionFactory

public SqlSessionFactory buildSqlSessionFactory() {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
	//...
    // 指定自定義Handler的包
    bean.setTypeHandlersPackage("com.wujie.pandora.repository.handler");
    return bean.getObject();
}

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 在Springboot中Mybatis與Mybatis-plus的區(qū)別詳解

    在Springboot中Mybatis與Mybatis-plus的區(qū)別詳解

    MyBatis是一個優(yōu)秀的持久層框架,它對JDBC的操作數(shù)據(jù)庫的過程進行封裝,MyBatisPlus (簡稱 MP)是一個 MyBatis的增強工具,在 MyBatis 的基礎(chǔ)上只做增強不做改變,為簡化開發(fā)、提高效率而生,本文將給大家介紹了在Springboot中Mybatis與Mybatis-plus的區(qū)別
    2023-12-12
  • Java Calendar類的詳解及使用實例

    Java Calendar類的詳解及使用實例

    這篇文章主要介紹了Java Calendar類的詳解及使用實例的相關(guān)資料,需要的朋友可以參考下
    2017-04-04
  • Java調(diào)用Shell命令的方法

    Java調(diào)用Shell命令的方法

    這篇文章主要介紹了Java調(diào)用Shell命令的方法,實例分析了java調(diào)用shell命令的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07
  • java實現(xiàn)菜單滑動效果

    java實現(xiàn)菜單滑動效果

    這篇文章主要介紹了java實現(xiàn)菜單滑動效果,效果非常棒,這里推薦給大家,有需要的小伙伴可以參考下。
    2015-03-03
  • HashMap之keyset()方法底層原理解讀

    HashMap之keyset()方法底層原理解讀

    這篇文章主要介紹了HashMap之keyset()方法底層原理解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • Java Web十條開發(fā)實用小知識

    Java Web十條開發(fā)實用小知識

    這篇文章主要介紹了Java Web十條開發(fā)實用小知識的相關(guān)資料,需要的朋友可以參考下
    2016-05-05
  • 詳解Springboot之接收json字符串的兩種方式

    詳解Springboot之接收json字符串的兩種方式

    這篇文章主要介紹了Springboot之接收json字符串的兩種方式,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • SpringBoot整合MQTT小結(jié)匯總

    SpringBoot整合MQTT小結(jié)匯總

    MQTT 客戶端是運行 MQTT 庫并通過網(wǎng)絡(luò)連接到 MQTT 代理的任何設(shè)備,是一種基于發(fā)布/訂閱(publish/subscribe)模式的“輕量級”通訊協(xié)議,該協(xié)議構(gòu)建于 TCP/IP 協(xié)議上,由 IBM 于 1999 年發(fā)明,對SpringBoot整合MQTT相關(guān)知識感興趣的朋友一起看看吧
    2022-01-01
  • springmvc中進行數(shù)據(jù)保存以及日期參數(shù)的保存過程解析

    springmvc中進行數(shù)據(jù)保存以及日期參數(shù)的保存過程解析

    這篇文章主要介紹了springmvc中進行數(shù)據(jù)保存以及日期參數(shù)的保存過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-09-09
  • 23種設(shè)計模式(12)java模版方法模式

    23種設(shè)計模式(12)java模版方法模式

    這篇文章主要為大家詳細介紹了23種設(shè)計模式之java模版方法模式,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11

最新評論