前言
- 在此之前需要掌握以下要点:
- 反射常见方法
- 常用设计模式
- 多线程相关知识
- JDBC,毕竟Mybatis底层是对JDBC的封装,若能知道JDBC原理最好
- 会配置并且使用mybatisPlus
- 会整合mybatisPlus与springBoot,本系列使用的是单数据源,即指定
spring.datasource属性
- 使用技术
- mybatis-plus 3.2.0,内含mybatis 3.5.1
- spring-boot 2.1.1.RELEASE
- mysql数据库
- 数据库连接池使用druid,版本为1.1.9
ORM映射结果集的实现
DemoController->test():
Tree tree = service.getById(2);
PreparedStatementHandler->query():
// ps内部封装了PreparedStatement,最终执行的是ps的execute()
PreparedStatement ps = (PreparedStatement) statement;
// 执行完execute()之后,结果集原始对象封装到了ps的stmt的statment的delegate的results的rowData中
ps.execute();
// 结果集处理器处理ps
return resultSetHandler.handleResultSets(ps);
DefaultResultSetHandler->handleResultSets():
// 最终结果集
final List<Object> multipleResults = new ArrayList<>();
// rsw对象封装了返回的字段名、字段类型(Java与MySQL类型)、rs/ps对象(位于resultSet变量的rs与wrapper中)
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 获取Tree的相关信息
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
......
// 最终multipleResults存储了一个List类型的对象,这个对象存储了我们想要tree对象
// handleResultSet方法最后还很好心地帮我们执行了rs.close()
handleResultSet(rsw, resultMap, multipleResults, null); // --1
......
// multipleResults只有一个list的话,那就只返回一个list
// 至此,解析完成,情景处返回的是ArrayList,其值为list[0]=new Tree(2,5)
// 返回过程中还会很好心地执行ps.close()
return collapseSingleResultList(multipleResults);
DefaultResultSetHandler->handleResultSet(): // --1
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
DefaultResultSetHandler->handleRowValues():
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
DefaultResultSetHandler->handleRowValuesForSimpleResultMap():
// resultSet.next()最终调用的是JDBC的rs的next()方法
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()){
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 这一步是将一条结果实例化成我们想要的对象,然后添加到resultHandler变量中(resultHandler是一个继承了List接口的ArrayList类型的对象,代码见DefaultObjectFactory->resolveInterface())
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
......
DefaultResultSetHandler->getRowValue():
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); // --2
// 如果该值不为空,并且返回实体类不对应JDBC类型
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
......
// 如果配置了应用自动映射的话,这边最终会调用反射方法method.invoke(target, args)给对象赋值(类似于tree.setId(2))
if (shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
}
DefaultResultSetHandler->applyAutomaticMappings():
// 是否自动映射,首先获取返回的字段名称,遍历,看字段是否有对应实体类的set方法
// 然后看该字段类型是否对应JDBC类型,有的话,说明是自动映射,然后进行赋值的操作
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); // --3
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
// 获得结果
// 根据typeHandler决定用哪一种handler,比如是Long类型,那么最终调用的就是rs.getLong("columnName")
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
// 如果值为空,那么不会给对象赋值
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// 最终会调用method.invoke(target, args)给对象赋值
metaObject.setValue(mapping.property, value);
}
}
return foundValues;
DefaultResultSetHandler->createAutomaticMappings(): // --3
// 先从缓存获取,如果缓存没有的话,利用columnDefinition获取返回的字段名称等属性
List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
if (autoMapping == null) {
autoMapping = new ArrayList<>();
// select几个字段,unmappedColumnNames的长度就为多少
final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
DefaultResultSetHandler->createResultObject(): // --2
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
DefaultResultSetHandler->createResultObject():
if (hasTypeHandlerForResultObject(rsw, resultType)) {
// 如果只返回一个字段,并且该字段对应JDBC的类型,直接返回该值
return createPrimitiveResultObject(rsw, resultMap, columnPrefix); // 1
} else if (!constructorMappings.isEmpty()) {
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
// 如果类是接口或者有默认无参构造器,最终调用无参构造器方法
return objectFactory.create(resultType); // 2
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
// 会抽取类的构造器,获取返回的字段名称columnName,根据构造器参数类型调用相应的get方法(如第一个参数是String类型,第一个返回字段为column1,则底层调用rs.getString("column1"))
// 最后使用反射方法Tree.class.getConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()])).newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]))实例化类
return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
}
总结与反思:
-
映射关系
- 如果数据库只有a字段,却选择b字段,是在接收字节流解析的时候发现的(同JDBC)
- 如果数据库只有a字段,却返回a+1字段,在自动映射的时候会发现没有set(a+1)这个方法,因此不会自动映射
- 如果数据库是varchar,实体类却是int,获取值的时候会调用rs.getInt(columnName)方法
- 如果实体类属性有a,b,c,select的是a,b,那么在做字段映射的时候只会set(a),set(b)
- 如果mysql返回的字段值为null,那么不会做set操作
-
DefaultResultSetHandler->createResultObject()方法中,如果使用了有参构造(String a,Integer b),数据库返回的是c、d字段,那么最终会调用rs.getString(c),rs.getInteger(d),然后将这两个值赋给a和b- 如果rsw只返回一个字段,并且该字段对应JDBC的类型,走--2中的1
- 如果有无参构造器,使用无参构造器
- 如果没有无参构造器,只有一种构造器,使用该构造器
- 如果有多种构造器,使用全构造器
- 如果多种构造器都没有全构造器的话,会抛异常
不管这边怎么实例化的,只要不是null,最终都会判断是否是对应JDBC类型以及是否自动映射
-
MetaObject讲解:
假如对象a下有个对象b,对象b下有个int类型的字段id
MetaObject类:
// 原对象
private final Object originalObject;
// 对象包装 如果是BeanWrapper类型,object为原类型,metaClass封装了原类型的get/set/construct等信息
private final ObjectWrapper objectWrapper;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
PropertyTokenizer类:
// name与indexName相同,children为字符串第一个.之后的字符串
private String name;
private final String indexedName;
private final String children;
----------------------------------------------------------------------------------------
MetaObject->getValue(String name):
// 解析字符串
PropertyTokenizer prop = new PropertyTokenizer(name);
// 如果字符串.后面有内容
if (prop.hasNext()) {
// b.id,那么metaValue就是对象b的相关信息
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
} else {
// 继续调用getValue方法,这里的metaValue是b,以此类推,直到最后一个.前面
return metaValue.getValue(prop.getChildren());
}
} else {
// 利用对象工厂来获取值
// 如果是map类型,返回map.get(name)
// 如果是bean类型,先获取getName反射方法,再利用反射方法返回值,此处最终调用的是b.getId()
return objectWrapper.get(prop);
}
MetaObject->metaObjectForProperty():
// 这一步是获取值
Object value = getValue(name);
return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
-
getById/selectList与xml/mapper中的ORM映射实现方式一模一样
-
参数类型对应JDBC类型(
typeHandlerRegistry.hasTypeHandler(parameterObject.getClass()))解释:
- 即参数对象类型有相应的类型处理器
- 如int型对应IntegerTypeHandler,String对应StringTypeHandler,bean类一般来说都是没有的
- 连接池使用责任链模式的用途和意义
if (this.pos < filterSize) {
return nextFilter().xxxxxxA();
}
return xxxxxxB();
虽然最终都会走到xxxxxxB()方法,但是在执行xxxxxxB()方法前可以做一些检验以及监控工作