接着上文太长的那个文章开始分析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 extends TypeHandler>> typeHandlerClass = (Class extends TypeHandler>>) 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 extends TypeHandler>> 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
要什么有什么类
设计模式也主要就是构建者模式令人影响深刻些。