接着上文太长的那个文章开始分析http://blog.csdn.net/ccityzh/article/details/71517490
其实初始化的部分没有什么可以分析的,就是解析Xml文件,不会解析的可以查一下,现在常用的都是JDOM,DOM4J,不过这里不是用的这两种。分析的过程中有初始化某些关键的部分会单独拿出来分析一下。
注:本文都是根据上一篇中实例为入口的,看到的非mybatis框架代码都是上节例子中的代码
.首先根据配置文件获取SqlSessionFactory,这个过程也是mybatis初始化的过程,读取配置文件,封装配置文件的过程。
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
根据类及方法的名字就可以看出使用构建模式构建sqlSessionFactory这个对象,一般复杂的对象都是用构建模式来创建对象。
(1)SqlSessionFactoryBuilder.java
//首先将配置文件以输入流的方式载入,接着使用一个重载的方法跳转到公共的build方法
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
//使用XMLConfigBulder来封装输入流,最终会将输入流转化成一个解析XML大家所熟悉的Document对象
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); (1)
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
没什么难以理解的地方,关键代码也就那try中的两句,其他都是安全措施,下面进入到XMLConfigBuilder类中。
(2)XMLConfigBuilder.java
//没有什么直接利用另一个构造函数,转移到了XPathParser中了,可以了解一下JDK的XPath,反正我是不会
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
//这个类中看到了我们解析XML过程中亲切的document对象了
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
(3) XMLConfigBuilder.java
接着会看到更有令人欣喜的东西,慢慢的就会走到咱们mybatis_config文件中各个节点的解析,让你看到熟悉的内容,不要被这些没听过的东西吓到,只是一个层层封
装与转化的过程。下面转化为了document之后,也就这个转化的流程就结束了,接着就返回到了上面(1)出所标记的地方。目前的情况是:XMLConfigBuilder 中包含着XPathParser,XPathParser中包含着document对象。包含着document对象的parser对象在(1)处调用parse方法 。
//惊不惊喜,这个类里面包含着配置文件中的根节点/configuration,同时也看到了我们最终将配置文件封装在的Configuration类中
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//这个就是解析配置文件的核心方法了,每个节点都拿出来单独解析,其中有很多框架运行起来的初始化操作,例如sql语句的封装了,拦截器的初始化了等
//理解这个方法还是比较关键的了
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
(4) SqlSessionFactoryBuilder.java
先把整个流程看完,然后再单独取几个上面关键的节点解析分析一下。接着将各个配置节点解析到了Configuration类,然后(1)处调用 build(parser.parse())方法,也就是构建模式的第二步,利用Configuration构建出复杂的SqlSessionFactory对象,第一步是封装为Configuration的过程。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
这样就取到了打开Mybatis大门的钥匙SqlSessionFactory,DefaultSqlSessionFactory是SqlSessionFactory的默认实现类用它就可以随便创建出Mybatis的session,session也就相当于jdbc的connection,拿到session以后就是执行sql的过程了。
(5)下面大体上说下配置文件各个节点的作用,主要分析一下mapper这个节点,这个也是文中最关键的;plugin这个节点,以后会单独写一篇插件(拦截器)的执行流程,毕竟大多数时候分页都是直接使用Mybatis的插件的,这里就不说了;然后typeAlias节点可以看下,别名有时候会用到,也可以看到mybatis中已经有的jdk某些类的默认别名;其他节点感兴趣的可以自己看下。下面按源代码中的解析顺序大体分析一下。
1.properties 这个到处都在用,配置各种参数,没什么可说的,例如mybatis_config中的
2.typeAliases,别名,这个就是有时候对象分布在各个包中,全路径太长,所以利用全路径注册一个别名,以后mybatis就认识这个别名了,在配置文件中使用起来就方便多了。我的实例中只有一个包,没什么可注册的,直到这个意思就好了,下面看下mybatis中已经注册的别名。
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
没什么可分析的,以后在mapper配置文件中可以方便的用,比如
3.setting,设置一些全局的功能,像缓存了,日志控制了等比较关键,具有全局行,但好像一般也不太用
private void settingsElement(XNode context) throws Exception {
if (context != null) {
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
}
4. enviroments,配置文件中的功能,一目了然,
5. typeHandler,这个用的比较多,一般也不会自己扩展,有兴趣的可以研究下自己扩展,自定义typeHandler。大多数时候使用比较简单,只是mapper中javaType和jdbcType的一个类型映射关系的支持。
6.mapper 接下来分析这个最关键的节点
-----------------------------------------------------------------------感觉排版比阅读代码都麻烦,真是好懒啊---------------------------------------------------------------------------------
mapperElement(root.evalNode("mappers"));
这句可以说是整个初始化最关键的一句,过程也是最复杂的,包括mapper文件中单个节点(select|delete|update|insert)的抽象过程,dao层接口的注册等,复杂又复杂,下面一层一层揭开面纱,尽量详细点讲述。
/**
*
*
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//这个分支我一般走不到,还没用过mapper下的package属性
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//我们的例子中直接走这个分支
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
//显然和mybatis_config的初始化解析一样,也是这么个过程,输入流,抽象document
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//关键又是这一句parse
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
看起来也没什么,又是一个xml文件的解析。
接着看mapperParser.parse()方法:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
看起来流程也比较清晰,如果这个xml资源没有被载入过,解析这个xml的mapper节点。
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
这个方法里全是熟悉的面孔,说一下常用的,不常用的就不说了,有兴趣的可以继续跟一下。
namespace,必须有,这里也可以看得到,不然报异常,也可以想的到,没有namespace怎么知道sql对应的dao方法呢。
cache相关还没有研究,等后面单独研究下mybatis的一级二级缓存。
resultMap,对应实体类和数据库字段的一个关系准则
sql好像是个sql的缓存一样的,此处定义了,后面可以用include直接使用,
select|insert|update|delete又是最关键的过程。
1.首先看resultMap的处理过程,这里面使用到了经典的构建者模式构建对象
//一个xml文件可以定义多个resultMap
private void resultMapElements(List list) throws Exception {
for (XNode resultMapNode : list) {
try {
//处理单个resultMap
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
//什么也没干
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections. emptyList());
}
//部分熟悉的内容,一般只用的着id,唯一标记这个resultMap;type 实体类,resutlMap的内容就是这个实体类和数据库表字段的一个映射关系
private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class typeClass = resolveClass(type);
Discriminator discriminator = null;
List resultMappings = new ArrayList();
resultMappings.addAll(additionalResultMappings);
//这句拿到映射关系的具体内容,接着对每条映射内容处理
List resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
ArrayList flags = new ArrayList();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
//单条映射的最终处理方法,每条映射为一个单独的resultMapping
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
看下resultMappings add方法里的buildResultMappingFromContext(resultChild, typeClass, flags)方法:
//这个方法就是把每条的映射属性一个个拿出来,最终注册成为一个resultMapping
private ResultMapping buildResultMappingFromContext(XNode context, Class resultType, ArrayList flags) throws Exception {
String property = context.getStringAttribute("property");
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections. emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resulSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class> typeHandlerClass = (Class>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
//这里是一个非常典型的构造者模式,不太清楚的可以研究下构造者模式应用在对象构建的优势(effective java说的挺清晰)
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resulSet, foreignColumn, lazy);
}
//构建者模式的全貌, 值得借鉴学习,当一个对象比较复杂的时候,可以使用这种内部类构建对象。
public ResultMapping buildResultMapping(
Class resultType,
String property,
String column,
Class javaType,
JdbcType jdbcType,
String nestedSelect,
String nestedResultMap,
String notNullColumn,
String columnPrefix,
Class> typeHandler,
List flags,
String resultSet,
String foreignColumn,
boolean lazy) {
Class javaTypeClass = resolveResultJavaType(resultType, property, javaType);
TypeHandler typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
List composites = parseCompositeColumnName(column);
if (composites.size() > 0) column = null;
ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);
builder.jdbcType(jdbcType);
builder.nestedQueryId(applyCurrentNamespace(nestedSelect, true));
builder.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true));
builder.resultSet(resultSet);
builder.typeHandler(typeHandlerInstance);
builder.flags(flags == null ? new ArrayList() : flags);
builder.composites(composites);
builder.notNullColumns(parseMultipleColumnNames(notNullColumn));
builder.columnPrefix(columnPrefix);
builder.foreignColumn(foreignColumn);
builder.lazy(lazy);
return builder.build();
}
当每一条映射关系构建成一个resultMapping,并且都add到resultMappings之后,做了统一处理,最终肯定是要把这些内容都封装在configuration类的。resultMapElement方法的下面这一步就是做这件事的:
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
//没什么说的
public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class type, String extend, Discriminator discriminator, List resultMappings, Boolean autoMapping) {
this.assistant = assistant;
this.id = id;
this.type = type;
this.extend = extend;
this.discriminator = discriminator;
this.resultMappings = resultMappings;
this.autoMapping = autoMapping;
}
public ResultMap resolve() {
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
//看到没有又是一个构建者模式,细节就不解释了,看到最终放到了configuration类中了
public ResultMap addResultMap(
String id,
Class type,
String extend,
Discriminator discriminator,
List resultMappings,
Boolean autoMapping) {
id = applyCurrentNamespace(id, false);
extend = applyCurrentNamespace(extend, true);
ResultMap.Builder resultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping);
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
List extendedResultMappings = new ArrayList(resultMap.getResultMappings());
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
Iterator extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
extendedResultMappingsIter.remove();
}
}
}
resultMappings.addAll(extendedResultMappings);
}
resultMapBuilder.discriminator(discriminator);
ResultMap resultMap = resultMapBuilder.build();
configuration.addResultMap(resultMap);
return resultMap;
}
到此为止,resultMap放到了configuraion类中,resultMap的初始化也就结束了。sql标签就不讲了,相对比较简单,下面看更加关键也更加复杂的select|insert|update|delete。
2.其次,select|insert|update|delete标签的解析
---------------------------------------------------------------------------排版第二次好麻烦啊,都过了凌晨0点了,明天继续----------------------------------------------------------------------------
这句开始解析:buildStatementFromContext(context.evalNodes("select|insert|update|delete"));其中mapper.xml文件中的每个sql节点都会解析一次,其实都会解析成为一个MappedStatement,当让最终又会将这个MappedStatement存储在Configuration中。
//简单判断,什么也没做
private void buildStatementFromContext(List list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
//没做什么,用了一个构建器辅助类解析增删改查节点
private void buildStatementFromContext(List list, String requiredDatabaseId) {
//这里看到,对一个mapper文件的每个增删改查都执行一次
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
///关键部分
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
接着继续看parseStatementNode()方法,这个方法就是最关键的最后一步了,最终会构建出一个MappedStatement,这里包含单个sql语句节点里所需要的所有东西,这个方法有点复杂,里面套了很多抽象流程。
//只注释一些必须用得着的关键字段
public void parseStatementNode() {
//取到sql语句的id,也就是我们dao层方法的方法名
String id = context.getStringAttribute("id");
//一般用不着这个字段
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
//关键字段,dao方法中传入的参数,最终会封装在ParameterMapping
String parameterType = context.getStringAttribute("parameterType");
//根据上面的路径,找到这个类的.class文件,框架中都是用反射做一些事情,这里会经常注册为别名
Class parameterTypeClass = resolveClass(parameterType);
//这里就是前面解析的resultMap,会在这里用到,从数据库查出来数据以后,和实体类的映射关系,
//或者相反方向,插入数据库映射关系
String resultMap = context.getStringAttribute("resultMap");
//和resultMap这个字段,二选一,这个没有映射关系,是数据库字段和实体类的对应关系,如果字段完全
//无需映射也可以自动对应上
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
//这块儿会判断出来是增删改查那种节点
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
//这里又是一个关键的地方,单独分析
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//构建者模式构建mappedstatement类,后面分析
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
一些在使用中必须用到的字段都做了注释分析,几个关键的点单独分析。SqlSource这个类的生成,里面包含了sql语句的具体内容,configutation ,parameterMapping等非常关键的信息。下面跟踪SqlSource类的创建过程。
createSqlSource(configuration, context, parameterTypeClass);
可以看到这个函数的三个参数,每一个都是重量级的,第一个不用说了,有你需要的一切, 第二个context,代表这条增删改查节点的所有信息,第三个是补充这条增删改查节点中的未知参数的空的参数。
//将三个重量级参数注入到XMLScriptBuilder类中,辅助后期的sqlsource生成
public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
//每次都是先用关键参数生成一个builder类,然后用这个类构建想要的对象
return builder.parseScriptNode();
}
//这个跟踪起来有点乱
public SqlSource parseScriptNode() {
//首先用原始的增删改查节点封装为一个SqlNode类,必须具体分析下这个封装过程才知道作什么,见下面(比较关键,先看下)
List contents = parseDynamicTags(context);
//返回一个封装原始sql语句的StaticTextSqlNode的list,接着对list转存了下
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
//大多数还是走这个分支,这个类里有一个函数getBoundSql,在执行dao接口函数的时候
//会调用getBoundSql,生成boundSql类,组装sql等,后面分析执行流程的时候分析
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
//生成sqlSource的关键流程,参数原始sql语句,占位参数类型,具体看下面这个构造函数分析,这种只是简单的sql语句,没有其他
//where set if等子节点
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
//封装为SqlNode的过程
private List parseDynamicTags(XNode node) {
List contents = new ArrayList();
//xml节点的处理,具体就是将各条原始sql语句分别处理成类的形式,然后随变取哪个信息
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
//取出其中一条sql包装成类的节点
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
//data就是从节点中取到的具体sql语句,#{}这种表示方式还没换成jdbc的?占位符
String data = child.getStringBody("");
//对sql做了一次封装
TextSqlNode textSqlNode = new TextSqlNode(data);
//里面有对sql做判断,分析什么情况的sql是动态的,大多是走else分支
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
//将sql抽象为StaticTextSqlNode添加到contents中,最终返回。
contents.add(new StaticTextSqlNode(data));
}
//mapper中的where,set,if等字节点走这个分支,最终也会将子节点中的内容存储在contents中
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlers.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//这里就是拿着子节点where set if和被存储的list,执行handleNode方法,里面
//有具体的分支,接口方法,会根据传入的节点选择具体哪个handler,解析后也是存到了contents里
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
}
因为本文实例就是一个简单的sql,所以执行下面的RawSqlSource,继续看关键流程:RawSqlSource的构造流程,这个处理结果会将原始sql的#{}替换为jdbc的占位符?,把关键的参数信息存储起来。
因为这个分支sql比较简单,在初始化的时候基本就把工作做好了,
上面一个动态分支,比较复杂,此处只是将该有的参数注入到类中,等用到的时候在执行相应的逻辑
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
//sqlNode中的sql语句在context中构建了一份
rootSqlNode.apply(context);
//去除这条sql语句
return context.getSql();
}
public RawSqlSource(Configuration configuration, String sql, Class parameterType) {
//SqlSource类中注册进了configuration,后面使用
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
//取到参数.class文件,
Class clazz = parameterType == null ? Object.class : parameterType;
//继续解析
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap());
}
public SqlSource parse(String originalSql, Class parameterType, Map additionalParameters) {
//注册进需要的参数 ,准备ParameterMapping生成的handler
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
//把原始sql中的#{}占位符拿出来了
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
//将原始sql的#{}占位符替换成jdbc需要的‘?’,这个过程还有一个关键的操作是
//builder.append(handler.handleToken(content));构建ParameterMapping的过程,别名注册过程等
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
//暂时将ParameterMapping存在集合中
public String handleToken(String content) {
//add函数中的函数又是一个构建者方式构建出ParameterMapping
parameterMappings.add(buildParameterMapping(content));
return "?";
}
//填充各个字段
private ParameterMapping buildParameterMapping(String content) {
Map propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
Class propertyType;
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
propertyType = metaParameters.getGetterType(property);
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
propertyType = java.sql.ResultSet.class;
} else if (property != null) {
MetaClass metaClass = MetaClass.forClass(parameterType);
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
} else {
propertyType = Object.class;
}
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class javaType = propertyType;
String typeHandlerAlias = null;
for (Map.Entry entry : propertiesMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if ("javaType".equals(name)) {
javaType = resolveClass(value);
builder.javaType(javaType);
} else if ("jdbcType".equals(name)) {
builder.jdbcType(resolveJdbcType(value));
} else if ("mode".equals(name)) {
builder.mode(resolveParameterMode(value));
} else if ("numericScale".equals(name)) {
builder.numericScale(Integer.valueOf(value));
} else if ("resultMap".equals(name)) {
builder.resultMapId(value);
} else if ("typeHandler".equals(name)) {
typeHandlerAlias = value;
} else if ("jdbcTypeName".equals(name)) {
builder.jdbcTypeName(value);
} else if ("property".equals(name)) {
// Do Nothing
} else if ("expression".equals(name)) {
throw new BuilderException("Expression based parameters are not supported yet");
} else {
throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}. Valid properties are " + parameterProperties);
}
}
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();
}
上面整个分析的过程就是返回一个SqlSource,终于把SqlSource的创建完成了,最终返回的是一new StaticSqlSource(configuration, sql, handler.getParameterMappings());
看下参数,处理过的sql语句,参数映射关系类和什么都含有类。
再接着最上面创建完成SqlSource类往下分析,创建完SqlSource其实基本上解析就结束了,剩下就是将Sqlsource构建到MappedStatement这个代表一条sql语句节点的类中,
然后将MappedStatement添加到Configuration中。
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class parameterType,
String resultMap,
Class resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved");
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
statementBuilder.resource(resource);
statementBuilder.fetchSize(fetchSize);
statementBuilder.statementType(statementType);
statementBuilder.keyGenerator(keyGenerator);
statementBuilder.keyProperty(keyProperty);
statementBuilder.keyColumn(keyColumn);
statementBuilder.databaseId(databaseId);
statementBuilder.lang(lang);
statementBuilder.resultOrdered(resultOrdered);
statementBuilder.resulSets(resultSets);
setStatementTimeout(timeout, statementBuilder);
setStatementParameterMap(parameterMap, parameterType, statementBuilder);
setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
确实是预测的这么一个流程,
到此处 configurationElement(parser.evalNode("/mapper"));这个节点的解析流程就结束了,下面还有一处需要分析的地方,就是Dao接口的注册缓存,以后执行接口方法的时候是直接把接口作为键取的。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
再把这个方法拿出来,解析完成/mapper以后,configuration做下标记,下次不用再初始化
接着注册注册dao接口
private void bindMapperForNamespace() {
//namespace,忘了没有就是dao接口的全名称
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class boundType = null;
try {
//加载.class文件
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
//要说的就是这个addMapper,把传过来的Dao接口注册进去
configuration.addMapper(boundType);
}
}
}
}
public void addMapper(Class type) {
mapperRegistry.addMapper(type);
}
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
留意一下这个knownMappers,此处将dao接口的.class存了缓存,同时对接口封装了一下,封装为了MapperProxyFactory
parse下面的三个函数是对解析不充分情况的一个补充,应该没什么用。
此后就一路void返回到SqlSessionFactoryBuilder类的build方法,SqlSessionFactory的最后一步,用一路返回的Confiugration构建出真实可用的SqlSessionFactory。
一路艰辛,终于看到了庐山真面目,领略了庐山风光。路漫漫其修远兮。
最后的最后对整个解析的过程做一个关键类的总结
--------------------------------------------------------------------------好累-----------------------------20170803---------------0:43------------------------------
1.SqlSessionFactoryBuilder类
名字就可以看出,构建sqlSessionFactory的类,使用的也是构建者模式,分两步,第一步初始化配置文件封装为Configuration,第二步用Configuration build出一个DefaultSqlSessionFactory。
2.sqlSessionFactory类
创造SqlSession的地方,执行sql语句的大门,SqlSession类似于jdbc的connection,用它来操作它下面的四大护法完成sql执行。
3.XML...ConfigBuilder
这种类型的类就是用传入的xml节点,来解析出具体内容,最后构建出一个...对象,这里的构建者模式和1中的有一点不一样的感觉,显然这里的更常用
4.SqlSource
这个类也是够复杂的类,感觉上面完全没有分析清楚,组装成完整sql的资源库,基本上parameterObject,parameterMapping,sql都能从这个类中以某种方式获得
5,MappedStatement
比SqlSource还要高一个层次,是一个增删改查的资源库,后面的执行流程到处都是他的身影
6. Configuration
要什么有什么类
设计模式也主要就是构建者模式令人影响深刻些。