在这个章节中,我们谈论如果将结果集封装成我们自定义的对象,这里只讨论简单的结果集,不考虑复合情况。
结果封装
还是用User举例。
| 字段名 | 类型 | 主键 |
|---|---|---|
| id | int | true |
| name | varchar | false |
| phone | varchar | false |
//省略get set方法和构造器
public class User {
private int id;
private String name;
private String phone;
}
我们在第三章末尾sql语句执行分析到了handleResultSets.handleResultSet方法。该方法处理结果集。就这个方法继续深入,对其进行更详细的分析.
//handleResultSets.handleResultSets
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
//ResultSet rs = stmt.getResultSet();
//将根据ResultSet包装成ResultSetWrapper返回
//ResultSetWrapper里面存放这字段信息,数据库字段类型以及对应的java类型
//这些类型都是在初始化的register的时候预制的
ResultSetWrapper rsw = getFirstResultSet(stmt);
//mappedStatement是根据mapper.xml中的sql语句构建的,ResultMap是里面的结果集映射
//如我们的返回类型resultType,resultMap都会被构建成一个ResultMap
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++;
}
//类似的,ResulSets是查询语句中,多结果集映射,参数为resultSetType
//这里与上文类似
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);
}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
//我们没有使用自自定义的resultHandler,所以会生成一个默认的DefaultResultHandler
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
//处理每行的数据,将结果存储再defaultResultHandler中
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
//这里关闭jdbc中的ResultSet
closeResultSet(rsw.getResultSet());
}
}
DefaultResultSetHandler.handleRowValues。这个类实现的接口应该已经不陌生了,ResultSetHandler,就是用来处理结果集的。
public 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);
}
}
没有嵌套的情况调用,DefaultResultSetHandler.handleRowValuesForSimpleResultMap。
这个方法中有几个参数。 ResultSetWrapper是对jdbc的resultSet的封装,保存数据库的字段名称,字段类型,对应的java类型等等。 ResultMap是在select标签中定义的返回类型的封装,如果使用的是resultType,那就只有类的类型信息。如果使用resultMap,那就是你自定义的字段映射,构造器等。 RowBounds mybatis的默认分页插件,一般不使用。
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
//结果的上下文,用来存储本次处理的行对象信息,对象的编号等
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
//获取jdbc的ResultSet
ResultSet resultSet = rsw.getResultSet();
//跳过分页的判断
skipRows(resultSet, rowBounds);
//shouldProcessMoreRows,分页和一些边界判断
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
//创建一个鉴别器,因为可能返回不同类型的结果集
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
//获取行数据并封装成一个对象
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
//将处理好的对象进行存储
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
在执行完这个方法之后,数据库中这一行的数据就会被转为我们自定义的对象User,这个对象存储在resultContext。再经过最外层while循环,这样一行行数据有都会被存储在List 容器中,最后返回。
DefaultResultSetHandler.getRowValue
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//创建一个具体的实例类型创建一个对象,如空的User类
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
//将该对象封装成一个MetaObject,MetaObject是个工具类,便于填充对象中的字段
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
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;
}
return rowValue;
}
DefaultResultSetHandler.createResultObject 这个方法通过构造器创建对象
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
final List<Class<?>> constructorArgTypes = new ArrayList<>();
final List<Object> constructorArgs = new ArrayList<>();
//这个方法会根据类型来创建一个新的对象。
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
//如果对象不为空,hasTypeHandlerForResultObject用来判断是否有对应的类型处理器
//一般我们如果没有添加类的处理其就是返回false,除非该类只有一个字段,那么会尝试找基本类型的处理器。
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
//这里是类型的结果集映射,可以在mapper.xml中定义resultMap,否则为空。使用resultType也是空的。
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
//判断是否有复合结果集而且有懒加载,懒加载在setting中配置
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
创建对象的四种方式
DefaultResultSetHandler.createResultObject 这里有四种情况。
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
throws SQLException {
//获取需要返回类的类型,这里指代com.entity.User对象。
final Class<?> resultType = resultMap.getType();
//这个mateClass会分析User,获取它的类信息,get和set方法等。
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
//这里获取该类的构造器,这里指的是mapper中resultMap中定义的constructor属性。
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
//这里判断是否是不是基本类型.它首先会判断返回的字段是不是只有一个,如果是,那就找对应的类型处理器。
//当然如果你的类只有一个参数,且自定义了类型处理器,也能满足条件。
if (hasTypeHandlerForResultObject(rsw, resultType)) {
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
//判断resultMap中定义的constructor属中的字段是否为空
} else if (!constructorMappings.isEmpty()) {
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
//是否是接口或者该类有无参的构造器
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
return objectFactory.create(resultType);
//没有无参的构造器,那就是我们已经自定义了构造器了,需要将参数和构造器中的参数进行匹配。
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
}
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
情形一 :基本类型
DefaultResultSetHandler.createPrimitiveResultObject 例如User只定义了一个id参数的情况。
private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final Class<?> resultType = resultMap.getType();
final String columnName;
//这里判断是否有使用了mapper中的sql是否是resultMap作为返回
//如果是,取第一条的字段名称
//这里只能有一条的字段
if (!resultMap.getResultMappings().isEmpty()) {
final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
final ResultMapping mapping = resultMappingList.get(0);
columnName = prependPrefix(mapping.getColumn(), columnPrefix);
} else {
//如果不是,直接取数据库返回的第一条字段名称
columnName = rsw.getColumnNames().get(0);
}
//从typeHandlerRegistry获取对应的类型处理器
final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
//从结果集中取出对应类型的值,并返回。如int ResultSet.getInt(cloumnName)
return typeHandler.getResult(rsw.getResultSet(), columnName);
}
情形二 :在resultMap中定义了构造器
例
<resultMap id="userMap" type="com.entity.User">
<constructor >
<idArg name="id" column="id" javaType="String" jdbcType="VARCHAR"></idArg>
<arg name="name" column="name" javaType="String" jdbcType="VARCHAR"></arg>
</constructor>
<id property="id" column="id" javaType="String" jdbcType="VARCHAR"/>
<result property="name" column="name" javaType="String" jdbcType="VARCHAR"/>
<result property="phone" column="name" javaType="String" jdbcType="VARCHAR"/>
</resultMap>
当然java的User类中也要有对应的构造器(User中的构造方法省略)。
DefaultResultSetHandler.createParameterizedResultObject
Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings,
List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) {
boolean foundValues = false;
//循环构造器中的字段 id,name
for (ResultMapping constructorMapping : constructorMappings) {
//获取到java的类型
final Class<?> parameterType = constructorMapping.getJavaType();
//数据库字段名称
final String column = constructorMapping.getColumn();
final Object value;
try {
//判断是否有复合的查询,也就是里面是否有select,这里指idArg 或者arg 中是否包含select属性
if (constructorMapping.getNestedQueryId() != null) {
value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
//判断是否有复合的结果集,也就是里面是否有resultMap,这里指idArg 或者arg 是否包含resultMap属性
} else if (constructorMapping.getNestedResultMapId() != null) {
final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
value = getRowValue(rsw, resultMap, getColumnPrefix(columnPrefix, constructorMapping));
} else {
//获取类型处理器
final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
//这里获取对应类型和字段名称的参数
value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
}
} catch (ResultMapException | SQLException e) {
throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
}
//添加这个参数的类型到list
constructorArgTypes.add(parameterType);
//添加这个参数的值到list
constructorArgs.add(value);
//如果该参数的值不为空,那么foundValues就为true
foundValues = value != null || foundValues;
}
//在循环结束后判断foundValues的值,如果为false,就是全部的参数都没有值,直接返回null
//如果为true,说明至少有一个参数是有值的,那么创建具体的类将值set进去
//最后调用创建类的方法,在情形三讨论
return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
情形三 :包含无参的构造器
DefaultObjectFactory.create
public <T> T create(Class<T> type) {
return create(type, null, null);
}
情形二和三都调用了该方法来构建类
DefaultObjectFactory.create
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
//判断该类的类型是否是Collection,List,Map等集合类型,如果是返回对应的class,没有匹配到则返回自身
Class<?> classToCreate = resolveInterface(type);
//实例化对象
return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}
DefaultObjectFactory.instantiateClass
private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
try {
Constructor<T> constructor;
//这里首先残判断构造器的参数和类型是否是空的,空的说明直接使用默认的无参构造器创建类就行。
if (constructorArgTypes == null || constructorArgs == null) {
constructor = type.getDeclaredConstructor();
try {
//使用默认构造器创建类
return constructor.newInstance();
} catch (IllegalAccessException e) {
//如果抛出了异常
//例如类的构造器为private,那么constructor.newInstance()就会抛出异常.
//if条件判断是否可以控制成员的访问,如果能就将其设置为可访问,这样即使构造器是private,也能创建类
if (Reflector.canControlMemberAccessible()) {
constructor.setAccessible(true);
return constructor.newInstance();
} else {
throw e;
}
}
}
//这里就是需要使用自定义的有参数创建类了,先获取构造器,然后将参数传入构造器中创建。
constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
try {
return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
} catch (IllegalAccessException e) {
//异常情况同上
if (Reflector.canControlMemberAccessible()) {
constructor.setAccessible(true);
return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
} else {
throw e;
}
}
} catch (Exception e) {
//其他的异常就直接抛出
String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList)
.stream().map(Class::getSimpleName).collect(Collectors.joining(","));
String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList)
.stream().map(String::valueOf).collect(Collectors.joining(","));
throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
}
}
很重要的一点,即使将类的构造器设为私有private属性,mybatis还是能通过反射创建该类
情形四 : 没有无参构造器的情况
DefaultResultSetHandler.createByConstructorSignature
private Object createByConstructorSignature(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
//获取声明的构造器,构造器可能有多个
final Constructor<?>[] constructors = resultType.getDeclaredConstructors();
//这里要找到一个匹配的构造器
final Constructor<?> defaultConstructor = findDefaultConstructor(constructors);
if (defaultConstructor != null) {
//如果找到了构造器,那么就用其创建类
return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, defaultConstructor);
} else {
//如果找不到,那么就遍历所有的构造器
for (Constructor<?> constructor : constructors) {
//这个方法用来判断构造器中的参数是否等于返回的字段个数
if (allowedConstructorUsingTypeHandlers(constructor, rsw.getJdbcTypes())) {
//如果恰好相等,就是用这个构造器
return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, constructor);
}
}
}
throw new ExecutorException("No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames());
}
DefaultResultSetHandler.findDefaultConstructor 这个方法用来找到对应的构造器。
private Constructor<?> findDefaultConstructor(final Constructor<?>[] constructors) {
//如果该类只有一个构造器,那么第一个
if (constructors.length == 1) {
return constructors[0];
}
//如果有多个,那么找到有注解 @AutomapConstructor的那个构造器,如果注解了多个,那么返回第一个
//如果所有的构造器都没有注解,那么返回空
for (final Constructor<?> constructor : constructors) {
if (constructor.isAnnotationPresent(AutomapConstructor.class)) {
return constructor;
}
}
return null;
}
那么如果我们注解构造器中的参数和数据库字段的不匹配呢?会发生什么?我们来看找到构造器后,它是如何处理的. DefaultResultSetHandler.createUsingConstructor
private Object createUsingConstructor(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor) throws SQLException {
boolean foundValues = false;
//遍历构造器中方法中的参数
for (int i = 0; i < constructor.getParameterTypes().length; i++) {
//获取第i个参数类型
Class<?> parameterType = constructor.getParameterTypes()[i];
//获取数据库表字段的第i个
//这里要注意,如果表的字段比构造器方法中的字段少,那么就数组越界了
String columnName = rsw.getColumnNames().get(i);
//找到类型处理器
TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
//根据字段名,获取对应的值
Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
//最后将类型和值都添加到集合中
//值得注意的是,这里是根据构造器中的字段类型和数据库的字段名来进行匹配的
//所以可能会出现构造器中的字段名称和数据库中的字段名称不匹配的情况
//例如我的构造器中第i个字段为String 类型的 a,第i个表字段名称b,即使是这样,也会根据名称b来找到数据库对应的值
constructorArgTypes.add(parameterType);
constructorArgs.add(value);
foundValues = value != null || foundValues;
}
//最后使用构造器创建类
//即使构造器和表中的字段不一样,但是由于构造器中的参数类型是对的,所以并不影响创建.
//但是也是由于这个原因,如果构造器中的参数顺序不同,可能结果不一样
//例如构造器中是String id,String name , 而返回的是 tom ,1,那么就会将tom 赋值给id ,1 赋值给name ,这明显是有错误的
return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
如果我们的构造器都没有注解,那该怎么处理呢?
回到DefaultResultSetHandler.createByConstructorSignature方法,那么会判断构造器中的参数是否等于返回的字段个数,如果找到这样的构造器,就是用其创建对象。找不到匹配的就只能抛出异常。
给对象赋值
创建完对象后就是要将字段的内容set到对象中了,回到DefaultResultSetHandler.getRowValue
//DefaultResultSetHandler.getRowValue
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//创建一个具体的实例类型创建一个对象,如空的User类
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
//对象配成功创建但是不存在类型处理器的情况,除非是基本数据类型会有Mybatis默认的处理器,我们自定义的类都需要手动添加处理器
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
//将该对象封装成一个MetaObject,便于填充其中的字段属性
final MetaObject metaObject = configuration.newMetaObject(rowValue);
//这个参数useConstructorMappings在DefaultResultSetHandler.createResultObject中被赋值
//只要对象被成功创建且构造器中存在参数,就为true
boolean foundValues = this.useConstructorMappings;
//该方法判断是否为自动映射,可以在resultMap中设置autoMapping
if (shouldApplyAutomaticMappings(resultMap, false)) {
//分析该对象的映射关系,并保存到mateObject中
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
//returnInstanceForEmptyRow 可在settings中配置
//当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。默认false
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
DefaultResultSetHandler.shouldApplyAutomaticMappings
private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
//先判断但是否开启了自动映射
if (resultMap.getAutoMapping() != null) {
return resultMap.getAutoMapping();
} else {
//这里直接传入的是false
//AutoMappingBehavior有三个值
//NONE表示不开启映射
//PARTIAL表示在没有复合的情况下映射
//FULL表示全部都映射
//configuration.getAutoMappingBehavior()是获取配置文件settings中的autoMappingBehavior,默认为PARTIAL
//所以这里这个方法返回true
if (isNested) {
return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
} else {
return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
}
}
}
DefaultResultSetHandler.applyAutomaticMappings
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
//找到没有在resultMap中定义的映射,且这个参数字段能和数据库中的字段相匹配
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;
}
//callSettersOnNulls是settings中的配置
//指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。
//class.isPrimitive()是jdk自带方法用来判断是否是基本类型
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
//使用set进行赋值操作
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
DefaultResultSetHandler.createAutomaticMappings
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<>();
//这个获取没有映射的键值,也就是没有在resultMap中定义的映射字段
//如果是使用resultType进行返回,那么这里就是全部的字段
final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
for (String columnName : unmappedColumnNames) {
//数据库中的字段名
String propertyName = columnName;
//columnPrefix是mapper自定义的属性,用来区分resultSet
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;
}
}
//mapUnderscoreToCamelCase在seetings中配置,为是否开启驼峰命名规则
//这里根据字段名称和是否开启驼峰命名来来找到对应的类中参数名称
final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
//如果找到了该参数而且该参数有set方法
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);
//将这些信息封装成一个UnMappedColumnAutoMapping
autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
} else {
//当找不到类型处理器的情况
//它有三种处理模式,可以在settings中配置,默认NONE不做处理,doAction就是要执行的策略
// NONE: 不做任何反应
//WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN)
//FAILING: 映射失败 (抛出 SqlSessionException)
configuration.getAutoMappingUnknownColumnBehavior()
.doAction(mappedStatement, columnName, property, propertyType);
}
} else {
//当数据库中的字段找不到类中的参数时,同上
configuration.getAutoMappingUnknownColumnBehavior()
.doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
}
}
autoMappingsCache.put(mapKey, autoMapping);
}
return autoMapping;
}
回到上文构造器中的参数不能正确匹配的问题。如果使用的是resultType,那么这个类与数据库有映射关系的字段都会被全部赋值,这样就解决了参数不匹配。 如果使用的是resultMap?
回到getRowValue方法,当获取到未映射的对象时,还需要通过mateObject将具体的值set到对象中。还是用User对象举例
<resultMap id="userMap" type="com.entity.User">
<id property="id" column="id" javaType="String" jdbcType="VARCHAR"/>
</resultMap>
<select id="findAll" resultMap="userMap">
select * from user
</select>
public class User {
private String id;
private String name;
private String phone;
@AutomapConstructor
public User(String name , String id){
this.id = id;
this.name = name;
}
}
现在User中有三个字段,id,name,phone,但是在resultMap中只对id进行映射。这里我们指定了构造器,所以在构造对象的时候id,name都会被附上初始值。在方法createAutomaticMappings中,由于在resultMap只定义了id,这里未映射的字段有两个name,phone。然后会调用 metaObject.setValue(mapping.property, value);将具体的值set到对象中。
处理完未定义的映射关系,接着就要处理我们在resultMap中定义的映射关系了。继续回到getRowValue中,之后又调用了applyPropertyMappings。
//DefaultResultSetHandler.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) {
//处理字段的前缀
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
//是否有复合的resultMap,如果有不做处理
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
//参数名称
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
//给对象对象赋值
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
在处理完映射之后,字段名称和数据库字段就能对应的上了。就这样处理完全部的对象之后,返回给用户。当然还是要关闭一系列的数据库链接和资源,处理缓存操作。
小结
Mybatis在处理结果集创建对象的时候。可以简单的分为两个步骤。
创建对象:根据类型的不同会有不同的创建方式。分别是返回基本类型参数,或者自定义类只包含一个参数;有无参数的构造器;没有无参构造器,但是有一个或多个自定义的构造器;在resultMap中指定了构造器;四种情况。Mybatis会尽可能将对象创建出来(即使构造器是私有的)。根据不同的创建方式,这时候对象中的参数值可能是空的,也可能与数据库中字段的值不匹配。
给对象赋值:这一步会根据select中的标签来进行区别。如果使用resultType的方式,那么这个类中与数据库有映射关系参数都会被全部赋值(根据字段名称和参数名称,类型进行匹配)。如果使用resultMap的方式,那么没有进行定义的参数会自动进行赋值。有定义的字段根据我们自定义的规则进行重新赋值。