ResultSetHandler
来源:互联网 发布:三菱数控车床编程实例 编辑:程序博客网 时间:2024/06/06 02:05
title: ResultSetHandler
date:2017年11月26日16:58:04
categories: Mybatis
ResultSetHandler介绍
ResultSetHandler是处理结果集的接口,在我看来他的实现类DefaultResultSetHandler是最难理解的一个组件,之所以复杂是因为Mybatis中ResultMap的设计非常强大,他可以满足用户的很多需求。
MyBatis是基于“数据库结构不可控”的思想建立的,也就是我们希望数据库遵循第三范式或BCNF,但实际事与愿违,那么结果集映射就是MyBatis为我们提供这种理想与现实间转换的手段了,为了实现这一灵活的转化,给用户带来便利,Mybatis做了很多努力,这些努力的具体实现大多都体现在DefaultResultSetHandler这个类中。所以在后面我们会通过查询过程的Debug来好好分析这个类。
我们先看看ResultSetHandler接口的实现
public interface ResultSetHandler { <E> List<E> handleResultSets(Statement stmt) throws SQLException; void handleOutputParameters(CallableStatement cs) throws SQLException;}
这个接口只定义了两个方法
handleResultSets就是用来处理结果集的方法,如何将数据库执行后返回的结果集转化为我们在Mapper文件中定义的ResultMap对应的返回,就是通过这个方法实现的。
handleOutputParameters这个方法目前还没有用到,因为目前还没有使用到存储过程。
handleResultSets
我们接着分析查询的过程
现在代码已经执行到了这个阶段
PreparedStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; //执行数据库操作 ps.execute(); //调用handleResultSets处理结果集 return resultSetHandler.<E> handleResultSets(ps); }
执行ps.execute();后
控制台如下
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2ed2d9cb]==> Preparing: select * from t_user where t_id in ( ? , ? , ? ) ==> Parameters: 1(Integer), 3(Integer), 25(Integer)
进入handleResultSets方法
public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); //先新建一个存放最终的结果的ArrayList final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0; //得到ResultSet结果集, 然后对ResultSetMetaData元数据封装一下 ResultSetWrapper rsw = getFirstResultSet(stmt); //得到该SqlMapper配置文件指定的ResultMap List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); //校验ResultMap的数量,不能为0或者不存在,如果是,则会抛出异常 validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { //依次从List集合中按index取ResultMap ResultMap resultMap = resultMaps.get(resultSetCount); //然后加入针对ResultMap处理结果集的方法 handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResulSets(); 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); }
getFirstResultSet
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException { ResultSet rs = stmt.getResultSet(); while (rs == null) { // move forward to get the first resultset in case the driver // doesn't return the resultset as the first result (HSQLDB 2.1) if (stmt.getMoreResults()) { rs = stmt.getResultSet(); } else { if (stmt.getUpdateCount() == -1) { // no more results. Must be no resultset break; } } } return rs != null ? new ResultSetWrapper(rs, configuration) : null; }
这个方法就是为了得到ResultSet结果集,如果成功得到结果集,就将结果集封装一下,返回封装好的ResultSetWrapper对象
这个方法里面用到了一些原生JDBC的API方法
getResultSetResultSet getResultSet() throws SQLException以 ResultSet 对象的形式获取当前结果。每个结果只应调用一次此方法。 返回:以 ResultSet 对象的形式返回当前结果;如果结果是更新计数或没有更多的结果,则返回 null 抛出: SQLException - 如果发生数据库访问错误,或者在已关闭的 Statement 上调用此方法另请参见:execute(java.lang.String)getMoreResultsboolean getMoreResults(int current) throws SQLException将此 Statement 对象移动到下一个结果,根据给定标志指定的指令处理所有当前 ResultSet 对象;如果下一个结果为 ResultSet 对象,则返回 true。 当以下表达式为 true 时没有更多结果: // stmt is a Statement object ((stmt.getMoreResults(current) == false) && (stmt.getUpdateCount() == -1))参数:current - 下列 Statement 常量之一,这些常量指示将对使用 getResultSet 方法获取的当前 ResultSet 对象发生的操作:Statement.CLOSE_CURRENT_RESULT、Statement.KEEP_CURRENT_RESULT 或 Statement.CLOSE_ALL_RESULTS 返回:如果下一个结果为 ResultSet 对象,则返回 true;如果其为更新计数或者不存在更多的结果,则返回 false 抛出: SQLException - 如果发生数据库访问错误,在已关闭的 Statement 上调用此方法,或者提供的参数不是以下参数之一:Statement.CLOSE_CURRENT_RESULT、Statement.KEEP_CURRENT_RESULT 或 Statement.CLOSE_ALL_RESULTS SQLFeatureNotSupportedException - 如果 DatabaseMetaData.supportsMultipleOpenResults 返回 false,并且 Statement.KEEP_CURRENT_RESULT 或 Statement.CLOSE_ALL_RESULTS 作为参数提供。从以下版本开始: 1.4 --------------------------------------------------------------------------------getUpdateCountint getUpdateCount() throws SQLException以更新计数的形式获取当前结果;如果结果为 ResultSet 对象或没有更多结果,则返回 -1。每个结果只应调用一次此方法。 返回:以更新计数的形式返回当前结果;如果当前结果为 ResultSet 对象或没有更多结果,则返回 -1 抛出: SQLException - 如果发生数据库访问错误,或者在已关闭的 Statement 上调用此方法另请参见:execute(java.lang.String)
我们也有必要了解这个封装的对象ResultSetWrapper
class ResultSetWrapper { private final ResultSet resultSet; private final TypeHandlerRegistry typeHandlerRegistry; private final List<String> columnNames = new ArrayList<String>(); private final List<String> classNames = new ArrayList<String>(); private final List<JdbcType> jdbcTypes = new ArrayList<JdbcType>(); private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<String, Map<Class<?>, TypeHandler<?>>>(); private Map<String, List<String>> mappedColumnNamesMap = new HashMap<String, List<String>>(); private Map<String, List<String>> unMappedColumnNamesMap = new HashMap<String, List<String>>(); public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException { super(); //注入了TypeHandlerRegistry,TypeHandler的注册中心 this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); //注入了ResultSet this.resultSet = rs; //得到ResultSet的元数据 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象。 final ResultSetMetaData metaData = rs.getMetaData(); final int columnCount = metaData.getColumnCount(); //对元数据的相关有用信息进行封装 for (int i = 1; i <= columnCount; i++) { //将返回的元数据的ColumnName添加到一个String类型的ArrayList中,如果SQL中使用了AS ColumnLable就取ColumnLable,否则就取ColumnName. /* getColumnLabelString getColumnLabel(int column) throws SQLException获取用于打印输出和显示的指定列的建议标题。建议标题通常由 SQL AS 子句来指定。如果未指定 SQL AS,则从 getColumnLabel 返回的值将和 getColumnName 方法返回的值相同。 getColumnNameString getColumnName(int column) throws SQLException获取指定列的名称。 */ columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i)); /* getColumnTypeint getColumnType(int column) throws SQLException获取指定列的 SQL 类型。 得到元数据指定Column的SQL 类型类型,将类型也存储到一个List集合中 */ jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i))); /* getColumnClassNameString getColumnClassName(int column) throws SQLException如果调用方法 ResultSet.getObject 从列中获取值,则返回构造其实例的 Java 类的完全限定名称。ResultSet.getObject 可能返回此方法所返回的类的子类。 参数:column - 第一列是 1,第二个列是 2,…… 返回:Java 编程语言中类的完全限定名称,方法 ResultSet.getObject 将使用该名称获取指定列中的值。此名称为用于自定义映射关系的类名称。 */ //将列对应的类完全限定名也加入一个List中 classNames.add(metaData.getColumnClassName(i)); } }}
handleResultSet
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try { //是否有父Mapper if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } //如果没有 else { //且ResultHandler为空,即Method参数列表中没有ResultHandler if (resultHandler == null) { //新建一个DefaultResultHandler DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); //然后将新的resultHandler注入到handleRowValues中,对结果集进行处理 handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { closeResultSet(rsw.getResultSet()); // issue #228 (close resultsets) } }
handleRowValues
private void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { if (resultMap.hasNestedResultMaps()) { ensureNoRowBounds(); checkResultHandler(); handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } else { handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } }
handleRowValuesForSimpleResultMap
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { //临时存储结果的上下文 DefaultResultContext resultContext = new DefaultResultContext(); //这个方法与RowBounds有关,目前没有理解这个方法,但是这个方法目前不是特别重要。 skipRows(rsw.getResultSet(), rowBounds); //判断是否需要继续执行 //shouldProcessMoreRows会去校验上下文的标志位以及行数是否超过RowBounds的限制 // rsw.getResultSet().next()就很好理解了,看下API的解释就明了了 /* nextboolean next() throws SQLException将光标从当前位置向前移一行。ResultSet 光标最初位于第一行之前;第一次调用 next 方法使第一行成为当前行;第二次调用使第二行成为当前行,依此类推。 当调用 next 方法返回 false 时,光标位于最后一行的后面。任何要求当前行的 ResultSet 方法调用将导致抛出 SQLException。如果结果集的类型是 TYPE_FORWARD_ONLY,则其 JDBC 驱动程序实现对后续 next 调用是返回 false 还是抛出 SQLException 将由供应商指定。 如果对当前行开启了输入流,则调用 next 方法将隐式关闭它。读取新行时,将清除 ResultSet 对象的警告链。 返回:如果新的当前行有效,则返回 true;如果不存在下一行,则返回 false */ //当执行了rsw.getResultSet().next()后,第一个结果集就出来了 //控制台新增输出: /* <== Columns: t_id, t_name, address<== Row: 1, kobe, los */ while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { //这个方法目前没有深究,但是看到Discriminated我想应该是校验是否在SqlMapper中用到了discriminate把,之后测试了这一情况再来细说 ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); //这里终于到了正式处理结果集的时候了, Object rowValue = getRowValue(rsw, discriminatedResultMap); storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } }
getRowValue(rsw, discriminatedResultMap)
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); //创建我们要返回的对象类型,这里是User Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null); // 创建的实例不为空而且也不是基本类型(泛指) if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) { //构造出一个Mybatis自己定义的MetaObject final MetaObject metaObject = configuration.newMetaObject(resultObject); //判断是否有指定构造方法 boolean foundValues = resultMap.getConstructorResultMappings().size() > 0; //判断是否有autoMapping,如果没有执行if下的语句,目前还没有弄清楚autoMapping是什么属性 if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) { //处理没有显式用result节点声明的ColumnName foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues; } //处理显式使用result节点声明的ColumnName foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; resultObject = foundValues ? resultObject : null; return resultObject; }
createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>(); final List<Object> constructorArgs = new ArrayList<Object>(); final Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); //创建的实例不为空而且也不是基本类型(泛指) if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) { final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); //就看下propertyMappings里面是不是带有查询,即select属性 //propertyMappings 很显然就对应ResultMap节点中的Result节点 for (ResultMapping propertyMapping : propertyMappings) { //如果带有select属性而且指定了是懒加载 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { // issue gcode #109 && issue #149 //具体这种方式是如何创建的,目前还没有测试过,后续再细说 return configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } } } return resultObject; }
createResultObject
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) throws SQLException { //先得到ResultMap要返回的类型,也就是我们在Mapper文件中对ResultMap配置时的type属性 final Class<?> resultType = resultMap.getType(); //这个目前测试的都是为null的,看这个命名应该是ResultMap中有指定对应的返回类型的构造方法吧 final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings(); //如果对应的返回类型不是JavaBean或者集合类型等,而是基本类型或者是他的一些包装类和时间日期等类型 if (typeHandlerRegistry.hasTypeHandler(resultType)) { //创建基本类型的返回类型的实例 return createPrimitiveResultObject(rsw, resultMap, columnPrefix); } //如果指定了构造方法,使用构造方法来创建实例 else if (constructorMappings.size() > 0) { return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix); } else { //要不然使用objectFactory来创建实例 return objectFactory.create(resultType); } }
objectFactory.create(resultType)
DefaultObjectFactory
public <T> T create(Class<T> type) { return create(type, null, null); } public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { Class<?> classToCreate = resolveInterface(type); @SuppressWarnings("unchecked") // we know types are assignable T created = (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs); return created; } private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { try { Constructor<T> constructor; //如果没有在ResultMap中指定构造方法,Mybatis默认该类只有默认的无参构造方法 if (constructorArgTypes == null || constructorArgs == null) { /* getDeclaredConstructorpublic Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。parameterTypes 参数是 Class 对象的一个数组,它按声明顺序标识构造方法的形参类型。 如果此 Class 对象表示非静态上下文中声明的内部类,则形参类型作为第一个参数包括显示封闭的实例。 参数:parameterTypes - 参数数组 返回:带有指定参数列表的构造方法的 Constructor 对象 */ //这里调用该方法时没有指定参数,意思即试图返回该类的无参构造方法,如果没有无参构造方法,就会抛出异常,然后再后面的catch住,后抛出给用户 constructor = type.getDeclaredConstructor(); if (!constructor.isAccessible()) { constructor.setAccessible(true); } //如果存在无参构造方法,就直接创建一个实例且返回 return constructor.newInstance(); } //如果有指定构造方法,试图返回相应的有参构造方法,如果失败,抛出异常后catch住重新抛出给用户 constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()])); if (!constructor.isAccessible()) { constructor.setAccessible(true); } return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()])); } catch (Exception e) { StringBuilder argTypes = new StringBuilder(); if (constructorArgTypes != null) { for (Class<?> argType : constructorArgTypes) { argTypes.append(argType.getSimpleName()); argTypes.append(","); } } StringBuilder argValues = new StringBuilder(); if (constructorArgs != null) { for (Object argValue : constructorArgs) { argValues.append(String.valueOf(argValue)); argValues.append(","); } } throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e); } }
当我们分析完这段代码的时候,我们就可以很清楚的知道为什么当我们没有在ResultMap中显式的指定构造方法时,如果我们对我们的JavaBean创建了一个有参构造方法而此时没有显式的给出无参构造方法,这时运行程序,就会抛出Error instantiating异常。
有些同学可能会觉得看源码的用处不大,甚至是浪费时间,但是我不这样觉得,看源码还是有很多好处的。
1、看源码我们可以学习框架的设计思想,学习设计模式。
2、看源码可以加深对JDK API的认识,再复杂的代码都是基于JDK API写出来的,对JDK API的熟悉度会一定程度决定你的编码质量。
3、看源码虽然确实很花时间,但是看了源码之后我们才真正的做到了知其然,也知其所以然,这是一个热爱的代码,热爱编程了必须要有的专研精神,看了源码,我们就知道了,这个框架到底为什么要这样用,为什么那样配置就不对,为什么会报这个错误,这样我们才能利用用框架工具,而不是被框架牵着走。
啰嗦完了,继续往下看。
configuration.newMetaObject(resultObject)
public MetaObject newMetaObject(Object object) { return MetaObject.forObject(object, objectFactory, objectWrapperFactory); } public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory) { if (object == null) { return SystemMetaObject.NULL_META_OBJECT; } else { return new MetaObject(object, objectFactory, objectWrapperFactory); } } private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory) { this.originalObject = object; this.objectFactory = objectFactory; this.objectWrapperFactory = objectWrapperFactory; //如果object已经是封装过的ObjectWarpper,就不需要再封装了,直接转化为ObjectWarpper类型就好了 if (object instanceof ObjectWrapper) { this.objectWrapper = (ObjectWrapper) object; } //如果是已经封装了,去Factory里取 else if (objectWrapperFactory.hasWrapperFor(object)) { this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object); } //如果是Map类型,创建一个MapWrapper else if (object instanceof Map) { this.objectWrapper = new MapWrapper(this, (Map) object); } //如果是Collection类型,一定要注意哦,Collection和Map是平级的哦,Map可不属于Collection //Collection下面有List Set等 else if (object instanceof Collection) { this.objectWrapper = new CollectionWrapper(this, (Collection) object); } else { //否则创建一个JavaBean 普通Java对象的包装类 this.objectWrapper = new BeanWrapper(this, object); } }
BeanWarpper的创建
public BeanWrapper(MetaObject metaObject, Object object) { super(metaObject); this.object = object; this.metaClass = MetaClass.forClass(object.getClass()); }public static MetaClass forClass(Class<?> type) { return new MetaClass(type); } private MetaClass(Class<?> type) { this.reflector = Reflector.forClass(type); }
this.reflector = Reflector.forClass(type);
这里的reflector是MetaClass的一个属性对应的类是Reflector,是Mybatis处理反射最重要的类。
Reflector
public static Reflector forClass(Class<?> clazz) { if (classCacheEnabled) { // synchronized (clazz) removed see issue #461 Reflector cached = REFLECTOR_MAP.get(clazz); if (cached == null) { cached = new Reflector(clazz); REFLECTOR_MAP.put(clazz, cached); } return cached; } else { return new Reflector(clazz); } } private Reflector(Class<?> clazz) { type = clazz; //添加默认的无参构造方法 addDefaultConstructor(clazz); //添加Get方法 addGetMethods(clazz); //添加Set方法 addSetMethods(clazz); //添加Field属性 addFields(clazz); readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]); writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]); //下面的两个put元素的操作是很重要的,我们看到他们把对应的PropertyName的大写作为Key, //把PropertyName作为Value存入这个HashMap中 for (String propName : readablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } for (String propName : writeablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } }
applyAutomaticMappings
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { //得到没有显式声明的ColumnName 的 List final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix); boolean foundValues = false; //后续的赋值操作比较简单,就不详细分析了 for (String columnName : unmappedColumnNames) { String propertyName = columnName; if (columnPrefix != null && columnPrefix.length() > 0) { // When columnPrefix is specified, // ignore columns without the prefix. if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) { propertyName = columnName.substring(columnPrefix.length()); } else { continue; } } //对这句代码还是有必要分析一下的,因为这里就是驼峰命名可以不显式声明ColumnName的原因所在 final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase()); if (property != null && metaObject.hasSetter(property)) { final Class<?> propertyType = metaObject.getSetterType(property); if (typeHandlerRegistry.hasTypeHandler(propertyType)) { final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName); final Object value = typeHandler.getResult(rsw.getResultSet(), columnName); if (value != null || configuration.isCallSettersOnNulls()) { // issue #377, call setter on nulls if (value != null || !propertyType.isPrimitive()) { metaObject.setValue(property, value); } foundValues = true; } } } } return foundValues; }
getUnmappedColumnNames
public List<String> getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException { List<String> unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix)); if (unMappedColumnNames == null) { loadMappedAndUnmappedColumnNames(resultMap, columnPrefix); unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix)); } return unMappedColumnNames; } private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException { List<String> mappedColumnNames = new ArrayList<String>(); List<String> unmappedColumnNames = new ArrayList<String>(); final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH); final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix); for (String columnName : columnNames) { final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH); if (mappedColumns.contains(upperColumnName)) { mappedColumnNames.add(upperColumnName); } else { unmappedColumnNames.add(columnName); } } mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames); unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames); }
loadMappedAndUnmappedColumnNames这个方法将没有显式的在ResultMap节点中的Result节点声明的property和显式声明的property分别存到unmappedColumnNames和unmappedColumnNames中。
findProperty(propertyName, configuration.isMapUnderscoreToCamelCase())
final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase()); //如果设置了数据库列名使用驼峰命名可以不用显式声明result,会把列名的下划线去掉,使其与JavaBean 属性名相同 public String findProperty(String name, boolean useCamelCaseMapping) { if (useCamelCaseMapping) { name = name.replace("_", ""); } return findProperty(name); }
如果设置了
<setting name="mapUnderscoreToCamelCase" value="true"/>
上述的useCamelCaseMapping就是true了
applyPropertyMappings
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) { final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); if (propertyMapping.isCompositeResult() || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) || propertyMapping.getResultSet() != null) { //我们主要需要看的是这个方法的具体实现,其他的都比较好理解, Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); final String property = propertyMapping.getProperty(); // issue #541 make property optional if (value != NO_VALUE && property != null && (value != null || configuration.isCallSettersOnNulls())) { // issue #377, call setter on nulls if (value != null || !metaObject.getSetterType(property).isPrimitive()) { metaObject.setValue(property, value); } foundValues = true; } } } return foundValues; }
getPropertyMappingValue
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { //如果result节点中select属性存在 if (propertyMapping.getNestedQueryId() != null) { //在本次查询中是不会用到这个方法的,因为result节点中并没有 return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix); } //这一分支应该是这个select属性对应的查询结果ResultMap中的result节点依然有select属性,当然目前还没有验证 else if (propertyMapping.getResultSet() != null) { addPendingChildRelation(rs, metaResultObject, propertyMapping); return NO_VALUE; } else if (propertyMapping.getNestedResultMapId() != null) { // the user added a column attribute to a nested result map, ignore it return NO_VALUE; } else { final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler(); final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); return typeHandler.getResult(rs, column); } }
如果我们的ResultMap是这样的
。。。待补充
那么就会用到这个方法
getNestedQueryMappingValue
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { final String nestedQueryId = propertyMapping.getNestedQueryId(); final String property = propertyMapping.getProperty(); final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId); final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType(); //得到该result节点对应column的值,这个值将作为select对应的select Mapper的参数传入 final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix); Object value = NO_VALUE; if (nestedQueryParameterObject != null) { final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject); final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql); //ofType 如果没有指定ofType,应该会取selct Mapper中对应的ResultMap Type把,这个做实验就知道会不会这样取了 final Class<?> targetType = propertyMapping.getJavaType(); if (executor.isCached(nestedQuery, key)) { executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType); } else { final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql); //如果是懒加载,不要立即查出结果,先缓存 if (propertyMapping.isLazy()) { lazyLoader.addLoader(property, metaResultObject, resultLoader); } else { //执行查询,得到结果 value = resultLoader.loadResult(); } } } return value; }
loadResult
public Object loadResult() throws SQLException { //selectList并不难理解 List<Object> list = selectList(); //extractObjectFromList就是转化下返回的类型 resultObject = resultExtractor.extractObjectFromList(list, targetType); return resultObject; } private <E> List<E> selectList() throws SQLException { Executor localExecutor = executor; if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) { localExecutor = newExecutor(); } try { return localExecutor.<E> query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql); } finally { if (localExecutor != executor) { localExecutor.close(false); } } }
extractObjectFromList
public Object extractObjectFromList(List<Object> list, Class<?> targetType) { Object value = null; if (targetType != null && targetType.isAssignableFrom(list.getClass())) { value = list; } else if (targetType != null && objectFactory.isCollection(targetType)) { value = objectFactory.create(targetType); MetaObject metaObject = configuration.newMetaObject(value); metaObject.addAll(list); } else if (targetType != null && targetType.isArray()) { Class<?> arrayComponentType = targetType.getComponentType(); Object array = Array.newInstance(arrayComponentType, list.size()); if (arrayComponentType.isPrimitive()) { for (int i = 0; i < list.size(); i++) { Array.set(array, i, list.get(i)); } value = array; } else { value = list.toArray((Object[])array); } } else { if (list != null && list.size() > 1) { throw new ExecutorException("Statement returned more than one row, where no more than one was expected."); } else if (list != null && list.size() == 1) { value = list.get(0); } } return value; }
总结
到这里对ResultSetHandler的讲解就稍微告一段落了
目前还有MapKey(差实验),鉴别器(),缓存,懒加载还没有讲到。
ResultSetHandler是Mybatis最复杂的一部分,它对应着ResultMap的处理,而ResultMap的配置也是配置文件中最难的。
在这里再说下我之前的一个疑问,也是自己对泛型没有很好的理解到位。
当我第一次看到这段代码时
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); }
我对这行
return resultSetHandler.<E> handleResultSets(ps);
是蛮疑惑的,resultSetHandler.\ handleResultSets(ps);
这种在调用方法前使用泛型的,代表这个方法肯定是泛型方法,否则肯定是不能这样用的。
但是我们发现在DefaultResultSetHandler中这个方法的返回类型是List\,并不是一个泛型方法呀?
public List<Object> handleResultSets(Statement stmt) throws SQLException {...}
但是依然在返回给我们结果的时候进行了强制的类型转化,也就是实现了泛型方法的效果。
这是为什么呢?在一开始我是十分想不通的。
当然在ResultSetHandler接口中,这是一个泛型方法
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
这说明实现了这个接口的方法,即使在实现类指定了某个具体的类型,还是具有泛型方法的特性的,还是会被当做泛型方法的。
至于为什么会这样,我的理解是,只要实现的方法是泛型方法,即使实现类里明确指定了特定的返回类型,依然会在编译时期,完成类型的强制转化,依然是具有泛型方法特性的。
最后,我们举个例子来验证一下:
package com.wangcc.MyJavaSE.Generic;import java.util.List;public interface ResultSetHandler { public <E> List<E> handleResultSets();}public class DefaultResultSetHandler implements ResultSetHandler { public List<Object> handleResultSets() { // TODO Auto-generated method stub List<Object> list = new ArrayList<Object>(); list.add("Kobe"); list.add("james"); return list; }}public class GenericTest { public static void main(String[] args) { // TODO Auto-generated method stub ResultSetHandler resultSetHandler = new DefaultResultSetHandler(); List<String> list = resultSetHandler.<String>handleResultSets(); for (String str : list) { System.out.println(str); } }}
这个测试类是可以正常运行的。
- ResultSetHandler
- dbutils中ResultSetHandler用法
- ResultSetHandler的总结
- Mybatis 之 ResultSetHandler
- DBUtil用法之ResultSetHandler 和 QueryRunner总结
- jsp---DBUtil用法之ResultSetHandler 和 QueryRunner
- DBUtil用法之ResultSetHandler 和 QueryRunner总结
- DbUtils之ResultSetHandler的实现类
- DBUtils学习----ResultSetHandler接口与实现
- DBUtiles中的简单使用(QueryRunner和ResultSetHandler的手动实现)
- 分析DbUtils: JDBC Utility Component Examples(QueryRunner and ResultSetHandler)
- jsp---DBUtil用法之ResultSetHandler 和 QueryRunner总结
- DbUtils 中ResultSetHandler接口的九大实现对象
- DbUtils——ResultSetHandler接口的实现类
- jsp---DBUtil用法之ResultSetHandler 和 QueryRunner总结
- MyBatis插件原理第五篇——ParameterHandler 和 ResultSetHandler
- Mybatis源码分析之结果封装ResultSetHandler和DefaultResultSetHandler
- DBUtils工具类库的QueryRunner类 、ResultSetHandler接口
- leetcode 习题解答:84. Largest Rectangle in Histogram
- 原生JS实现网页版红包游戏1
- HDU2008
- 洛谷P2617 Dynamic Ranking
- 120. Triangle
- ResultSetHandler
- 输入密码显示星号
- Chrome浏览器在linux root权限下无法启动的问题搜集
- 剑指_二维数组中的查找
- 网络优化之Xception
- Python数据结构——树的实现
- CentOS安装shadowsocks客户端
- CS231n-Assignment1(作业1)-softmax
- python 开始