MyBatis源码解析—————序列化实体的原理

前提

在使用数据库ORM框架的时候,通过一些配置,使得ORM能够将查出来的数据转为我们想要的Java对象,虽然大致原理是通过Java本身的反射机制获取相应的方法,生成对象、注入值到对象相应的field,其原理就类似java注解原生模仿@ModelAttribute功能

分析Mybatis如何将数据库查出的数据转为Java对象

在xml中定一个了ResultMap用于映射Java对象与数据库表字段的关系,因此在MyBatis中用ResultMap对象去保存xml中写的resultmap,而Class<?> type这个属性就是代表查出的数据库数据要转为哪个Java对象

1
2
3
4
5
6
public class ResultMap {
...
// 重要的属性,表明要转为查出的数据库数据要转为哪个Java对象
private Class<?> type;
...
}

第一步

这是将数据库数据转为Java对象最终的方法!该方法通过ResultSetWrapperResultMap创建一个成员属性都为默认值的Java对象,在进行映射时,有两次机会,第一次是判断是否可以自动映射将查出来的数据自动注入到实体中,如果可以,则将数据注入到metaObject中;第二次是如果没法通过自动映射解决,则通过xml文件中配置的resultMap进行数据映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

第二步

创建出一个对应的Java对象,同时根据懒加载策略进行相应的对象代理;如果存在懒加载策略,则返回的是一个代理对象,用于拦截方法(一般是属性的get方法),通过方法拦截实现懒加载,具体懒加载的实现可以看这篇:MyBatis延迟加载的简易实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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<Class<?>>();
final List<Object> constructorArgs = new ArrayList<Object>();
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
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;
}

第三步

其核心实现是通过反射实现,而从传参可知,MyBatis支持在没有无参构造函数的情况下能够通过有参的构造函数去反射实例化一个对象出来,因为Java语言里,一切都是对象,因此类的构造函数也是一个对象——Constructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
throws SQLException {
final Class<?> resultType = resultMap.getType();
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
if (hasTypeHandlerForResultObject(rsw, resultType)) {
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
} 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, columnPrefix);
}
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}

第四步

通过前面的几个步骤获得了一个初始化的对象,然后需要把数据库查出来的数据注入到对象中去

applyAutomaticMappings

applyAutomaticMappings函数是将查出来的数据注入到Java对象中的field中的一种方法,它的策略是Java对象的属性名能够与数据库字段名称自动化映射过去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
// 是否存在自动映射,如果存在就根据自动映射将数据注入对应的类Field中
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;
}

applyPropertyMappings

mybatis最重要的特性就是通过xml配置Java对象与数据库表实现字段映射的关系,并且无论自动映射成功与否,applyPropertyMappings都会执行,通过xml中定义的映射关系将查处的数据注入到Java对象中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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);
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;
}