本文对mybatis的配置不做讲解,只从源码层面来分析mybatis的加载过程。
我们在使用mybatis时,一般流程如下,实例化一个sqlSessionFactory,然后通过sqlSessionFactory来获取一个sqlSession。
InputStream inputStream=null; try{ inputStream= Resources.getResourceAsStream("mybatis-config.xml"); //配置文件 sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession=sqlSessionFactory.openSession()
}catch(Exception e){ e.printStackTrace(); }
先看下sqlSessionFactory的build过程,首先将mybatis-config.xml作为一个输入流传入给build()方法,build的代码如下:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { SqlSessionFactory var5; try { XMLConfigBuilder e = new XMLConfigBuilder(inputStream, environment, properties); //将配置文件加载为XMLConfig var5 = this.build(e.parse()); //构建SqlSessionFactory } catch (Exception var14) { throw ExceptionFactory.wrapException("Error building SqlSession.", var14); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException var13) { ; } } return var5; }
//XMConfigBuilder中使用的XPathParser,XPathParser中使用的createDocument方法来将DOM加载到XPathParser中 private Document createDocument(InputSource inputSource) { try { DocumentBuilderFactory e = DocumentBuilderFactory.newInstance(); e.setValidating(this.validation); e.setNamespaceAware(false); e.setIgnoringComments(true); e.setIgnoringElementContentWhitespace(false); e.setCoalescing(false); e.setExpandEntityReferences(true); DocumentBuilder builder = e.newDocumentBuilder(); builder.setEntityResolver(this.entityResolver); builder.setErrorHandler(new ErrorHandler() { public void error(SAXParseException exception) throws SAXException { throw exception; } public void fatalError(SAXParseException exception) throws SAXException { throw exception; } public void warning(SAXParseException exception) throws SAXException { } }); return builder.parse(inputSource);//将inputSource加载成Document,最后存储到XpathParser中 } catch (Exception var4) { throw new BuilderException("Error creating document instance. Cause: " + var4, var4); } }
再从最开始的var5 = this.build(e.parse())看,先看一下e.parse()方法:
public Configuration parse() { if(this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true; this.parseConfiguration(this.parser.evalNode("/configuration"));//解析configuration标签下的内容到Configuration对象中 return this.configuration; } } private void parseConfiguration(XNode root) { //分别解析各个标签下内容到Configuration对象中 try { this.propertiesElement(root.evalNode("properties")); //properties标签 Properties e = this.settingsAsProperties(root.evalNode("settings"));//settings标签 this.loadCustomVfs(e); this.typeAliasesElement(root.evalNode("typeAliases")); this.pluginElement(root.evalNode("plugins")); this.objectFactoryElement(root.evalNode("objectFactory")); this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectorFactoryElement(root.evalNode("reflectorFactory")); this.settingsElement(e); this.environmentsElement(root.evalNode("environments"));//environments标签 /this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); this.mapperElement(root.evalNode("mappers")); //Mappers对象 } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } }
这里解析mybatis-config.xml中的所有标签出来,并加载到Configuration对象中,mybatis-config.xml的文件如下:
<configuration> <properties resource="jdbc.properties"/> <!-- 属性文件,也可以直接在本文件中写properties属性 --> <settings> <setting name="cacheEnabled" value="true"/> </settings> <typeAliases> <package name="com.mymvc.bean"></package> <package name="com.java1234.model"></package> </typeAliases> <environments default="development"> <!-- 默认开发环境 --> <environment id="development"> <!-- 开发环境 --> <transactionManager type="JDBC" /> <!-- 事务管理器,JDBC和MANAGED(tomcat不支持) --> <dataSource type="POOLED"> <!-- 有UNPOOLED,POOLED,JNDI三种 --> <property name="driver" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </dataSource> </environment> <environment id="test"> <!-- 测试环境 --> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </dataSource> </environment> </environments> <mappers> <package name="com.java1234.mappers" /> </mappers> </configuration>
这里挑一两个例子来讲解怎么加载到Configuration对象中的,首先看下settings的加载,settings的加载将内存加载成Properties类型的对象:
private Properties settingsAsProperties(XNode context) { if(context == null) { return new Properties(); } else { Properties props = context.getChildrenAsProperties();//将节点转换为键值对的Properties类型,上述config.xml中toString打印即为{cacheEnabled=true} MetaClass metaConfig = MetaClass.forClass(Configuration.class, this.localReflectorFactory); Iterator var4 = props.keySet().iterator(); //metaConfig有什么用呢? Object key; do { if(!var4.hasNext()) { return props; } key = var4.next(); } while(metaConfig.hasSetter(String.valueOf(key))); //while中可以看到,这里会判断settings中标签的合法性,也就是说在metaConfig中存在setter方法,才会认为是合法的标签。(必须在Configuration对象中存在相应的setter方法) throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); } }
其他标签的加载类似,另外再讲下mappers的加载,在配置mybatis时,会经常配置到到mybatis的mapper.xml文件(sql文件映射),而且在config.xml文件中会指定对应的mappers的package在哪个地方,如:
<mappers>
<package name="com.java1234.mappers" />
</mappers>
具体是怎么来加载的呢?
private void mapperElement(XNode parent) throws Exception { if(parent != null) { Iterator var2 = parent.getChildren().iterator(); while(true) { while(var2.hasNext()) { XNode child = (XNode)var2.next(); String resource; if("package".equals(child.getName())) { //子节点的名称为package
/**
类似
<mappers>
<package name="com.java1234.mappers" />
</mappers>
*/ resource = child.getStringAttribute("name"); this.configuration.addMappers(resource); } else {
/**
<mappers>
<mapper resource="mybatis/xxx.xml" />
</mappers>
*/ resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); XMLMapperBuilder mapperParser; InputStream mapperInterface1; if(resource != null && url == null && mapperClass == null) { //resource不为空 ErrorContext.instance().resource(resource); mapperInterface1 = Resources.getResourceAsStream(resource);
//解析xml的Mapper文件 mapperParser = new XMLMapperBuilder(mapperInterface1, this.configuration, resource, this.configuration.getSqlFragments()); mapperParser.parse(); } else if(resource == null && url != null && mapperClass == null) { //url不为空 ErrorContext.instance().resource(url); mapperInterface1 = Resources.getUrlAsStream(url); mapperParser = new XMLMapperBuilder(mapperInterface1, this.configuration, url, this.configuration.getSqlFragments()); mapperParser.parse(); } else { if(resource != null || url != null || mapperClass == null) { //mapperClass不为空 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } Class mapperInterface = Resources.classForName(mapperClass); this.configuration.addMapper(mapperInterface); } } } return; } } }
先看下mappers标签package的方式:
this.configuration.addMappers(resource); //addMappers会循环调用addMapper public <T> void addMapper(Class<T> type) { if(type.isInterface()) { if(this.hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { this.knownMappers.put(type, new MapperProxyFactory(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type); parser.parse();//解析 loadCompleted = true; } finally { if(!loadCompleted) { this.knownMappers.remove(type); } } } } public void parse() { String resource = this.type.toString(); if(!this.configuration.isResourceLoaded(resource)) { //判断资源是否加载 this.loadXmlResource(); this.configuration.addLoadedResource(resource); this.assistant.setCurrentNamespace(this.type.getName()); this.parseCache(); this.parseCacheRef(); Method[] methods = this.type.getMethods(); Method[] var3 = methods; int var4 = methods.length; for(int var5 = 0; var5 < var4; ++var5) { Method method = var3[var5]; try { if(!method.isBridge()) { this.parseStatement(method); } } catch (IncompleteElementException var8) { this.configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } this.parsePendingMethods(); } private void loadXmlResource() { //加载对应的XML文件,文件名和package下的类名相同 if(!this.configuration.isResourceLoaded("namespace:" + this.type.getName())) { String xmlResource = this.type.getName().replace('.', '/') + ".xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(this.type.getClassLoader(), xmlResource); } catch (IOException var4) { ; } if(inputStream != null) { XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, this.assistant.getConfiguration(), xmlResource, this.configuration.getSqlFragments(), this.type.getName()); xmlParser.parse();//xml解析 } } }
public void parse() { if(!this.configuration.isResourceLoaded(this.resource)) { this.configurationElement(this.parser.evalNode("/mapper")); //加载mapper下的标签到Configuration this.configuration.addLoadedResource(this.resource); this.bindMapperForNamespace(); } this.parsePendingResultMaps(); this.parsePendingChacheRefs(); this.parsePendingStatements(); } private void configurationElement(XNode context) { try { String e = context.getStringAttribute("namespace"); //获取namespace if(e != null && !e.equals("")) { //namespace不能为null或者空 this.builderAssistant.setCurrentNamespace(e); this.cacheRefElement(context.evalNode("cache-ref")); this.cacheElement(context.evalNode("cache")); this.parameterMapElement(context.evalNodes("/mapper/parameterMap")); //parameterMap this.resultMapElements(context.evalNodes("/mapper/resultMap")); //resultMapper this.sqlElement(context.evalNodes("/mapper/sql")); this.buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } else { throw new BuilderException("Mapper\'s namespace cannot be empty"); } } catch (Exception var3) { throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3); } }
这里 configurationElement 就开始在加载mapper.xml中的内容到Configuration对象,具体来看一个例子,比如cacheElement:
XML文件内容 <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true" />
private void cacheElement(XNode context) throws Exception { if(context != null) { String type = context.getStringAttribute("type", "PERPETUAL"); Class typeClass = this.typeAliasRegistry.resolveAlias(type); String eviction = context.getStringAttribute("eviction", "LRU"); Class evictionClass = this.typeAliasRegistry.resolveAlias(eviction); Long flushInterval = context.getLongAttribute("flushInterval"); Integer size = context.getIntAttribute("size"); boolean readWrite = !context.getBooleanAttribute("readOnly", Boolean.valueOf(false)).booleanValue(); boolean blocking = context.getBooleanAttribute("blocking", Boolean.valueOf(false)).booleanValue(); Properties props = context.getChildrenAsProperties(); //同样,也是使用Properties类型存储 this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }
其他的解析说明如下,源码不一一粘贴了:
cacheRefElement方法用于解析<cache-ref>标签,总结如下:
- 解析完的CacheRef放在cacheRefMap中
- cacheRefMap是一个HashMap
- 位于Configuration对象中
- Key为mapper文件的namespace,Value为<cache-ref>中配置的namespace
- cacheElement方法用于解析<cache>标签,总结如下:
- 会根据<cache>中配置的属性new出一个org.apache.ibatis.cache.Cache
- 使用此Cache作为MyBatis缓存
- parameterMapElement方法用于解析<parameterMap>标签,总结如下:
- 解析完的ParameterMap放在parameterMaps中
- parameterMaps是一个StrictMap
- 位于Configuration对象中,StrictMap是HashMap的子类
- Key为当前mapper的namespace+"."+<parameterMap>标签中的id属性,Value为ParameterMap对象
- resultMapElements方法用于解析<resultMap>标签在,总结如下:
- 解析完的ResultMap放在resultMaps中
- resultMaps是一个StrictMap,
- 位于Configuration对象中
- Key为当前mapper的namespace+"."+<resultMap>标签中的id属性,Value为ResultMap对象
- sqlElement方法用于解析<sql>标签,总结如下:
- 解析完的内容放在sqlFragments中
- sqlFragments是一个StrictMap
- 位于XMLMapperBuilder对象中
- Key为当前mapper的namespace+"."+<sql>标签中的id属性,Value为sql这个XNode本身
- buildStatementFromContext用于解析<select>、<insert>、<update>、<delete>这四个标签,总结如下:
- 解析完的内容放在mappedStatements中
- mappedStatements是一个StrictMap
- 位于Configuration对象中
- Key为当前mapper的namespace+"."+<select>|<insert>|<update>|<delete>标签中的id属性,Value为MappedStatement对象
最后一步,就是构建sqlSessionFactory了,将加载的config对象传入
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
最终构建出来的SqlSessionFactory是DefaultSqlSessionFactory
接下来会通过openSession()方法获取到一个sqlSession
//通过openSession()方法,传入的参数默认为this.configuration.getDefaultExecutorType(),null,false
//其中defaultExecutor为Simple,可追踪源码看到Configuration的构造函数中的初始化
//this.defaultExecutorType = ExecutorType.SIMPLE;
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; DefaultSqlSession var8; try { Environment e = this.configuration.getEnvironment(); //获取配置的environment TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(e); //获取事务工厂 tx = transactionFactory.newTransaction(e.getDataSource(), level, autoCommit); //获取事务,传入参数为DataSource,事务隔离级别,是否自动提交,可根据之前的config.xml中的environment中的参数一一对应,即事务为JDBC类型,DataSource为Pooled
Executor executor = this.configuration.newExecutor(tx, execType); //获取一个executor var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);//new一个defaultSqlSession } catch (Exception var12) { this.closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12); } finally { ErrorContext.instance().reset(); } return var8; }
看下executor的获取:
//executor有三种,batch,simple,reuse public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null?this.defaultExecutorType:executorType; executorType = executorType == null?ExecutorType.SIMPLE:executorType; Object executor; if(ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if(ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if(this.cacheEnabled) {//装饰器模式,传入executor,返回给simpleExecutor装上了换成功能 executor = new CachingExecutor((Executor)executor); } Executor executor1 = (Executor)this.interceptorChain.pluginAll(executor); //加载插件 return executor1; }
到这里,sqlSession就获取完了。