ibatis源码学习(三)参数和结果的映射原理

2024-05-13 23:32

本文主要是介绍ibatis源码学习(三)参数和结果的映射原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

   在 ibatis整体设计和核心流程 一文中,我们提到了ibatis框架中sql的执行过程,sql执行前后有两个重要步骤: 参数对象映射到sql执行参数,sql执行结果映射到结果对象。本文将深入分析ibatis框架中参数和结果的映射原理。

问题  
在详细介绍ibatis参数和结果映射原理之前,让我们先来思考几个问题。 
1. 为什么需要参数和结果的映射?  
相对于全自动的orm,ibatis一个重要目标是,通过维护POJO与SQL之间的映射关系,让我们执行 SQL时对输入输出的数据管理更加方便。也就是说,ibatis并不会为程序员在运行期自动生成SQL 执行,具体的 SQL 需要程序员编写,然后通过映射配置文件,将SQL所需的参数,以及返回的结果字段映射到指定POJO。 

2. 如何维护参数和结果的映射关系?  
对于变化的数据,当然是通过配置文件的方式,在sqlMap映射文件中配置参数和结果对象与SQL的映射关系。 

3. 如何执行参数和结果与SQL的映射?  
初始化时读取配置文件,将参数和结果与SQL的映射关系维护在SqlMapClientImpl中;处理请求时,通过配置的映射关系,构建sql参数和结果对象,可以采用反射等方式读取和写入对象的属性值。 

SQL执行环境  
        在 ibatis整体设计和核心流程 一文的SQL执行过程中,SqlMapExecutorDelegate首先获取初始化时构建好的MappedStatement (初始化过程见 初始化和配置文件解析 ),再通过MappedStatement执行SQL,参数和结果与SQL的映射正是在MappedStatement的SQL执行过程中进行处理。在介绍参数和结果与SQL的映射原理之前,我们先看一下MappedStatement的相关类图: 

上面的这张类图需说明两点: 
1. 从纵向上体现了Statement类的整体继承关系,MappedStatement接口提供了SQL执行 上下文信息和执行操作 ,如ParameterMap、ResultMap、SQL、Timeout等上下文信息和executeQueryForList等操作信息;BaseStatement抽象类提供了MappedStatement的初步实现,它组合了MappedStatement需要的 上下文信息 ;GeneralStatement实现类提供了MappedStatement的 执行操作 的基本实现;InsertStatement、SelectStatement等实现类提供了针对不同类型SQL操作的特定实现。 
2. 从横向上体现了Statement类的初始化数据和请求处理数据的分离。 类图中的第一行对象(ParameterMap/ResultMap/SQL等)在初始化过程中会构造完毕,请求处理时直接获取即可; 类图中的第三行对象(RequestScope)在请求处理时才会创建,通过执行方法的参数传入,再进行后续处理。 

映射主要类图  
参数和结果映射的主要类图如下: 

1. ParameterMap  
该接口提供了和参数处理相关的方法,如根据参数对象生成sql参数数组,为PrepareStatement设置参数等。它的默认实现类是BasicParameterMap,其内部组合了多个ParameterMapping对象。 

2. ResultMap  
该接口提供了和结果处理相关的方法,如根据ResultSet生成结果对象等。它的默认实现类是BasicResultMap,其内部组合了多个ResultMapping对象。 

3. ParameterMapping/ResultMapping  
该接口用于维护参数对象和结果对象中每个属性的详细映射信息,如propertyName、jdbcType、javaType、nullValue等,以及针对该属性类型的TypeHandler。 

4. DataExchange  
该接口是映射的核心接口,执行具体的映射操作,默认实现是BaseDataExchange,针对不同的参数和结果类型有不同的实现类,如JavaBeanDataExchange、MapDataExchange、ListDataExchange、PrimitiveDataExchange等,这些对象统一由DataExchangeFactory创建和管理。 

下面以查询操作为例,分析ibatis执行过程中参数和结果的映射原理。 
参数映射过程  
1. SqlMapExecutorDelegate调用GeneralStatement.executeQueryWithCallback()方法,执行sql语句。 
Java代码   收藏代码
  1. protected void executeQueryWithCallback(RequestScope request, Connection conn, Object parameterObject, Object resultObject, RowHandler rowHandler, int skipResults, int maxResults)  
  2.     throws SQLException {  
  3.  ...  
  4.     parameterObject = validateParameter(parameterObject);  //校验输入参数  
  5.   
  6.     Sql sql = getSql();   //获取sql对象  
  7.   
  8.     errorContext.setMoreInfo("Check the parameter map.");  
  9.     ParameterMap parameterMap = sql.getParameterMap(request, parameterObject);     //从sql对象中获取ParameterMap   
  10.   
  11.     errorContext.setMoreInfo("Check the result map.");  
  12.     ResultMap resultMap = sql.getResultMap(request, parameterObject); //从sql对象中获取ResultMap  
  13.   
  14.     request.setResultMap(resultMap);  
  15.     request.setParameterMap(parameterMap);  
  16.   
  17.     Object[] parameters = parameterMap.getParameterObjectValues(request, parameterObject); //重要,目的是通过参数映射获取参数值  
  18.   
  19.     errorContext.setMoreInfo("Check the SQL statement.");  
  20.     String sqlString = sql.getSql(request, parameterObject); //获取sql语句  
  21.   
  22.     errorContext.setActivity("executing mapped statement");  
  23.     errorContext.setMoreInfo("Check the SQL statement or the result map.");  
  24.     RowHandlerCallback callback = new RowHandlerCallback(resultMap, resultObject, rowHandler);  
  25.     sqlExecuteQuery(request, conn, sqlString, parameters, skipResults, maxResults, callback);  // 重要,执行sql操作  
  26.     ...  

该方法执行过程中,有非常重要的两步:  根据参数对象生成sql执行参数数组 ,该步在parameterMap.getParameterObjectValues()中完成; 通过参数数组为PrepareStatement设置参数 ,该步在sqlExecuteQuery()方法中完成。下面重点看一下这两个的方法的实现。 

2. BasicParameterMap.getParameterObjectValues()的实现如下: 
Java代码   收藏代码
  1. public Object[] getParameterObjectValues(RequestScope request, Object parameterObject) {  
  2.   return dataExchange.getData(request, this, parameterObject);  
  3. }  

可以看出,参数对象映射主要交给dataExchange对象完成,针对不同的parameterClass,BasicParameterMap初始化时会创建不同的dataExchange对象。这里以MapDataExchange对象为例,说明参数的映射过程。 

3. MapDataExchange.getData()的实现如下: 
Java代码   收藏代码
  1. public Object[] getData(RequestScope request, ParameterMap parameterMap, Object parameterObject) {  
  2.     if (!(parameterObject instanceof Map)) {  
  3.       throw new RuntimeException("Error.  Object passed into MapDataExchange was not an instance of Map.");  
  4.     }  
  5.   
  6.     Object[] data = new Object[parameterMap.getParameterMappings().length];  
  7.     Map map = (Map) parameterObject;  
  8.     ParameterMapping[] mappings = parameterMap.getParameterMappings();  
  9.     for (int i = 0; i < mappings.length; i++) {  
  10.       data[i] = map.get(mappings[i].getPropertyName());  
  11.     }  
  12.     return data;  
  13.   }  

可以看出,map参数对象的映射方式是,通过循环处理每一个ParameterMapping对象获得属性名称,再从map参数中获取对应的属性值,最终放入参数数组中。有兴趣的同学可以看一下JavaBeanDataExchange的实现,其内部通过组合AccessPlan对象,使用反射方式生成参数数组。 

4. 通过上面两步已经获得了sql执行参数数组,sqlExecuteQuery()方法将通过参数数组为PrepareStatement设置参数。sqlExecuteQuery()方法内部调用SqlExecutor.executeQuery()进行查询,部分源码如下: 
Java代码   收藏代码
  1. public void executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {  
  2.   ...  
  3.   PreparedStatement ps = null;  
  4.   ResultSet rs = null;  
  5.   setupResultObjectFactory(request);  
  6.   try {  
  7.     errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");  
  8.     Integer rsType = request.getStatement().getResultSetType();  
  9.     //准备PrepareStatement  
  10.     if (rsType != null) {  
  11.       ps = prepareStatement(request.getSession(), conn, sql, rsType);  
  12.     } else {  
  13.       ps = prepareStatement(request.getSession(), conn, sql);  
  14.     }  
  15.     setStatementTimeout(request.getStatement(), ps);  
  16.     Integer fetchSize = request.getStatement().getFetchSize();  
  17.     if (fetchSize != null) {  
  18.       ps.setFetchSize(fetchSize.intValue());  
  19.     }  
  20.     errorContext.setMoreInfo("Check the parameters (set parameters failed).");  
  21.     request.getParameterMap().setParameters(request, ps, parameters); //为PrepareStatement设置参数  
  22.     errorContext.setMoreInfo("Check the statement (query failed).");  
  23.     ps.execute(); //执行  
  24.     errorContext.setMoreInfo("Check the results (failed to retrieve results).");  
  25.   
  26.     // Begin ResultSet Handling  
  27.     rs = handleMultipleResults(ps, request, skipResults, maxResults, callback);  //处理结果  
  28.     ...  
  29. }  

上面方法中通过调用getParameterMap().setParameters(request, ps, parameters)为PrepareStatement设置参数,其中BasicParameterMap().setParameters()源码如下: 
Java代码   收藏代码
  1. public void setParameters(RequestScope request, PreparedStatement ps, Object[] parameters)  
  2.     throws SQLException {  
  3.   ....  
  4.   if (parameterMappings != null) {  
  5.     for (int i = 0; i < parameterMappings.length; i++) {  
  6.       BasicParameterMapping mapping = (BasicParameterMapping) parameterMappings[i];  
  7.       errorContext.setMoreInfo(mapping.getErrorString());  
  8.       if (mapping.isInputAllowed()) {  
  9.         setParameter(ps, mapping, parameters, i); //循环处理每一个参数  
  10.       }  
  11.     }  
  12.   }  
  13. }  

在前面参数数组生成时我们看到,parameterMappings和参数数组是一一对应关系,并且保证前后顺序,这里再循环处理每一个parameterMappings和相应参数值,为PrepareStatement设置参数。 其中setParameter()方法如下: 
Java代码   收藏代码
  1. protected void setParameter(PreparedStatement ps, BasicParameterMapping mapping, Object[] parameters, int i) throws SQLException {  
  2.   Object value = parameters[i];  
  3.   // 设置空值  
  4.   String nullValueString = mapping.getNullValue();  
  5.   if (nullValueString != null) {  
  6.     TypeHandler handler = mapping.getTypeHandler();  
  7.     if (handler.equals(value, nullValueString)) {  
  8.       value = null;  
  9.     }  
  10.   }  
  11.   
  12.   // 设置Parameter  
  13.   TypeHandler typeHandler = mapping.getTypeHandler();  
  14.   if (value != null) {  
  15.     typeHandler.setParameter(ps, i + 1, value, mapping.getJdbcTypeName());  
  16.   } else if (typeHandler instanceof CustomTypeHandler) {  
  17.     typeHandler.setParameter(ps, i + 1, value, mapping.getJdbcTypeName());  
  18.   } else {  
  19.     int jdbcType = mapping.getJdbcType();  
  20.     if (jdbcType != JdbcTypeRegistry.UNKNOWN_TYPE) {  
  21.       ps.setNull(i + 1, jdbcType);  
  22.     } else {  
  23.       ps.setNull(i + 1, Types.OTHER);  
  24.     }  
  25.   }  
  26. }  

上面的方法最终调用TypeHandler进行参数设置,下面以BigDecimalTypeHandler为例,看一下setParameter()方法实现: 
Java代码   收藏代码
  1. public void setParameter(PreparedStatement ps, int i, Object parameter, String jdbcType)  
  2.     throws SQLException {  
  3.   ps.setBigDecimal(i, ((BigDecimal) parameter));  
  4. }  

到这里,谜底已经揭开了。 

总结一下上面整个过程: 初始化时通过配置文件构建ParameterMap;请求处理时再通过ParameterMap构建出对应的sql参数数组,这个构建过程通过调用dataExchange对象完成;最后通过sql参数数组为PrepareStatement设置参数。 

结果映射过程  
1. 在参数映射过程的第4步中,最后一行代码是handleMultipleResults(),正是这里进行结果映射,部分源码如下: 
Java代码   收藏代码
  1. private ResultSet handleMultipleResults(PreparedStatement ps, RequestScope request, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {  
  2.   ResultSet rs;  
  3.   rs = getFirstResultSet(ps);  
  4.   if (rs != null) {  
  5.     handleResults(request, rs, skipResults, maxResults, callback);  
  6.   }  
  7.   ...  


2. handleResults()方法的实现如下: 
Java代码   收藏代码
  1. private void handleResults(RequestScope request, ResultSet rs, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {  
  2.    try {  
  3.        request.setResultSet(rs);  
  4.        ResultMap resultMap = request.getResultMap();        
  5.   
  6.        ...  
  7.        int resultsFetched = 0;  
  8.        while ((maxResults == SqlExecutor.NO_MAXIMUM_RESULTS || resultsFetched < maxResults) && rs.next()) {  
  9.          Object[] columnValues = resultMap.resolveSubMap(request, rs).getResults(request, rs);  //获取每条记录的各个属性值  
  10.          callback.handleResultObject(request, columnValues, rs); //通过属性值组装结果对象  
  11.          resultsFetched++;  
  12.        }  
  13.      }  
  14.    } finally {  
  15.      request.setResultSet(null);  
  16.    }  
  17.  }  

上面方法中依次处理ResultSet的每条记录,首先 获取该记录的属性值数组 ,该步通过BasicResultMap.getResults()方法实现;再将 属性值数组装换成结果对象 ,该步通过RowHandlerCallback.handleResultObject()方法实现;最后存储在RowHandlerCallback对象中,最终结果在GeneralStatement中通过rowHandler.getList()返回。下面分别看一下这两个方法的具体实现。 

3. BasicResultMap.getResults()的部分源码如下: 
Java代码   收藏代码
  1. public Object[] getResults(RequestScope request, ResultSet rs)  
  2.     throws SQLException {  
  3.   ...  
  4.   boolean foundData = false;  
  5.   Object[] columnValues = new Object[getResultMappings().length];  
  6.   
  7.   // 依次处理ResultMappings,设置每个属性值  
  8.   for (int i = 0; i < getResultMappings().length; i++) {  
  9.     BasicResultMapping mapping = (BasicResultMapping) getResultMappings()[i];  
  10.     if (mapping.getStatementName() != null) {  
  11.       if (resultClass == null) {  
  12.         throw new SqlMapException("The result class was null when trying to get results for ResultMap named " + getId() + ".");  
  13.       } else if (Map.class.isAssignableFrom(resultClass)) {  
  14.         Class javaType = mapping.getJavaType();  
  15.         if (javaType == null) {  
  16.           javaType = Object.class;  
  17.         }  
  18.         columnValues[i] = getNestedSelectMappingValue(request, rs, mapping, javaType);  
  19.       } else if (DomTypeMarker.class.isAssignableFrom(resultClass)) {  
  20.         Class javaType = mapping.getJavaType();  
  21.         if (javaType == null) {  
  22.           javaType = DomTypeMarker.class;  
  23.         }  
  24.         columnValues[i] = getNestedSelectMappingValue(request, rs, mapping, javaType);  
  25.       } else {  
  26.         Probe p = ProbeFactory.getProbe(resultClass);  
  27.         Class type = p.getPropertyTypeForSetter(resultClass, mapping.getPropertyName());  
  28.         columnValues[i] = getNestedSelectMappingValue(request, rs, mapping, type);  
  29.       }  
  30.       foundData = foundData || columnValues[i] != null;  
  31.     } else if (mapping.getNestedResultMapName() == null) {  
  32.       columnValues[i] = getPrimitiveResultMappingValue(rs, mapping);  
  33.       if (columnValues[i] == null) {  
  34.         columnValues[i] = doNullMapping(columnValues[i], mapping);  
  35.       }  
  36.       else  {  
  37.         foundData = true;  
  38.       }  
  39.     }  
  40.   }  
  41.   request.setRowDataFound(foundData);  
  42.   
  43.   return columnValues;  
  44. }  

上面的代码依次处理ResultMappings,设置每个属性值,最后统一放入columnValues数组中。 

4. RowHandlerCallback.handleResultObject()方法的实现如下: 
Java代码   收藏代码
  1. public void handleResultObject(RequestScope request, Object[] results, ResultSet rs) throws SQLException {  
  2.    Object object;  
  3.   
  4.    request.setCurrentNestedKey(null);  
  5.    object = resultMap.resolveSubMap(request, rs).setResultObjectValues(request, resultObject, results);  //生成结果对象  
  6.    ...  
  7.    rowHandler.handleRow(object); // 将object加入rowHandler内部的list中  
  8.    }  
  9.  }  

该方法内部实现调用BasicResultMap.setResultObjectValues()生成结果对象,再将结果对象保存在RowHandler中。setResultObjectValues()方法目标是将属性值设置到对象中,部分场景是借助上文提及的dataExchange对象实现,这里就不再详述。 

小结  
最后,以一张图总结参数和结果的映射整体流程: 
 
每个映射过程都主要拆分为两步,为什么需要这样的设计?一个核心的原因是实现职责分离,不同阶段借助不同的处理方式(dataExchange,typeHandler等),使整个处理逻辑更清晰。 

这篇关于ibatis源码学习(三)参数和结果的映射原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/987109

相关文章

51单片机学习记录———定时器

文章目录 前言一、定时器介绍二、STC89C52定时器资源三、定时器框图四、定时器模式五、定时器相关寄存器六、定时器练习 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出~ 提示:以下是本篇文章正文内容,下面案例可供参考 一、定时器介绍 定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。 定时器作用: 1.用于计数系统,可

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中,少不了使用word,这个是大家必备的软件,今天给大家分享word设置上标快捷键,希望在办公中能帮到您! 1、添加上标 在录入一些公式,或者是化学产品时,需要添加上标内容,按下快捷键Ctrl+shift++就能将需要的内容设置为上标符号。 word设置上标快捷键的方法就是以上内容了,需要的小伙伴都可以试一试呢!

AssetBundle学习笔记

AssetBundle是unity自定义的资源格式,通过调用引擎的资源打包接口对资源进行打包成.assetbundle格式的资源包。本文介绍了AssetBundle的生成,使用,加载,卸载以及Unity资源更新的一个基本步骤。 目录 1.定义: 2.AssetBundle的生成: 1)设置AssetBundle包的属性——通过编辑器界面 补充:分组策略 2)调用引擎接口API

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

大学湖北中医药大学法医学试题及答案,分享几个实用搜题和学习工具 #微信#学习方法#职场发展

今天分享拥有拍照搜题、文字搜题、语音搜题、多重搜题等搜题模式,可以快速查找问题解析,加深对题目答案的理解。 1.快练题 这是一个网站 找题的网站海量题库,在线搜题,快速刷题~为您提供百万优质题库,直接搜索题库名称,支持多种刷题模式:顺序练习、语音听题、本地搜题、顺序阅读、模拟考试、组卷考试、赶快下载吧! 2.彩虹搜题 这是个老公众号了 支持手写输入,截图搜题,详细步骤,解题必备

持久层 技术选型如何决策?JPA,Hibernate,ibatis(mybatis)

转自:http://t.51jdy.cn/thread-259-1-1.html 持久层 是一个项目 后台 最重要的部分。他直接 决定了 数据读写的性能,业务编写的复杂度,数据结构(对象结构)等问题。 因此 架构师在考虑 使用那个持久层框架的时候 要考虑清楚。 选择的 标准: 1,项目的场景。 2,团队的技能掌握情况。 3,开发周期(开发效率)。 传统的 业务系统,通常业

《offer来了》第二章学习笔记

1.集合 Java四种集合:List、Queue、Set和Map 1.1.List:可重复 有序的Collection ArrayList: 基于数组实现,增删慢,查询快,线程不安全 Vector: 基于数组实现,增删慢,查询快,线程安全 LinkedList: 基于双向链实现,增删快,查询慢,线程不安全 1.2.Queue:队列 ArrayBlockingQueue:

ABAP怎么把传入的参数刷新到内表里面呢?

1.在执行相关的功能操作之前,优先执行这一段代码,把输入的数据更新入内表里面 DATA: lo_guid TYPE REF TO cl_gui_alv_grid.CALL FUNCTION 'GET_GLOBALS_FROM_SLVC_FULLSCR'IMPORTINGe_grid = lo_guid.CALL METHOD lo_guid->check_changed_data.CALL M

硬件基础知识——自学习梳理

计算机存储分为闪存和永久性存储。 硬盘(永久存储)主要分为机械磁盘和固态硬盘。 机械磁盘主要靠磁颗粒的正负极方向来存储0或1,且机械磁盘没有使用寿命。 固态硬盘就有使用寿命了,大概支持30w次的读写操作。 闪存使用的是电容进行存储,断电数据就没了。 器件之间传输bit数据在总线上是一个一个传输的,因为通过电压传输(电流不稳定),但是电压属于电势能,所以可以叠加互相干扰,这也就是硬盘,U盘