Mybatis如何获取数据源
在mybatis中如何获取数据源==>如何解析以下这些标签的
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
一、进入mybatis入口
我们从以下代码进入mybatis的事务中
// 获取配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);很明显,这个是mybatis的入口,前面两行代码,只是java的代码。
我们首先查看build(inputStream)方法,里面也是一个流函数,我们继续查看build方法,就得到以下代码:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建 XMLConfigBuilder 对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 执行 XML 解析
// DefaultSqlSessionFactory对象
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.
}
}
}
二、解析mybatis-config.xml文件
其中XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);是对xml文件进行解析的,解析完后再调用parser.parse()方法,我们的重点就是查看这个方法。
public Configuration parse() {
// 只加载一次xml文件,否则抛出异常
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 解析xml文件中configuration标签
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parsed初始值是false,调用一次后就变为true,表示只加载一次xml文件。
三、解析 configuration 标签下的所有属性配置
parseConfiguration(parser.evalNode("/configuration"));这行代码表示的就是解析xml文件下的configuration标签。我们深入查看parseConfiguration这个方法。
/**
* 功能描述: 解析 configuration 标签下的所有属性配置
* @date 2020/2/27
*/
private void parseConfiguration(XNode root) {
try {
// 读取mybatis-config.xml中的properties,加载config.properties文件中的参数
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// 加载environments节点
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 加载mapper文件
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
注意点:因为代码的执行顺序是从上往下的,所以这也决定了,标签的使用前后是有顺序的,上面代码的顺序就决定了mybatis-config.xml文件里面标签的使用顺序。
我们对参数root进行copy value,发现,这个root对应的就是mybatis-config.xml文件里面<configuration></configuration>

<configuration>
<properties resource="config.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis/UserMapper.xml"/>
</mappers>
</configuration>
这个我们获得了其中的内容。
四、标签的了解与使用
但是,对于mybatis-config.xml里面的标签,我们不太熟悉,现在我们来了解一下。
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
我们可以点开上面的dtd文件,得到关于标签的一些内容
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright ${license.git.copyrightYears} the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
<!ELEMENT databaseIdProvider (property*)>
<!ATTLIST databaseIdProvider
type CDATA #REQUIRED
>
<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>
<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT settings (setting+)>
<!ELEMENT setting EMPTY>
<!ATTLIST setting
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT typeAliases (typeAlias*,package*)>
<!ELEMENT typeAlias EMPTY>
<!ATTLIST typeAlias
type CDATA #REQUIRED
alias CDATA #IMPLIED
>
<!ELEMENT typeHandlers (typeHandler*,package*)>
<!ELEMENT typeHandler EMPTY>
<!ATTLIST typeHandler
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
handler CDATA #REQUIRED
>
<!ELEMENT objectFactory (property*)>
<!ATTLIST objectFactory
type CDATA #REQUIRED
>
<!ELEMENT objectWrapperFactory EMPTY>
<!ATTLIST objectWrapperFactory
type CDATA #REQUIRED
>
<!ELEMENT reflectorFactory EMPTY>
<!ATTLIST reflectorFactory
type CDATA #REQUIRED
>
<!ELEMENT plugins (plugin+)>
<!ELEMENT plugin (property*)>
<!ATTLIST plugin
interceptor CDATA #REQUIRED
>
<!ELEMENT environments (environment+)>
<!ATTLIST environments
default CDATA #REQUIRED
>
<!ELEMENT environment (transactionManager,dataSource)>
<!ATTLIST environment
id CDATA #REQUIRED
>
<!ELEMENT transactionManager (property*)>
<!ATTLIST transactionManager
type CDATA #REQUIRED
>
<!ELEMENT dataSource (property*)>
<!ATTLIST dataSource
type CDATA #REQUIRED
>
<!ELEMENT mappers (mapper*,package*)>
<!ELEMENT mapper EMPTY>
<!ATTLIST mapper
resource CDATA #IMPLIED
url CDATA #IMPLIED
class CDATA #IMPLIED
>
<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>
例如我们分析properties,发现它其实可以两种方式,一种是我们用的resource,还有一种是url。以此类推,可以了解其他的标签怎么使用,非常的清晰明了。
五、解析 environments 标签
接下来,回到主题,我们的需求是知道mybatis是如何获取数据源的。其中数据源是在哪里呢?在environments标签下,所以我们需要查看解析environments标签的方法environmentsElement(root.evalNode("environments"))。
/***
* 功能描述:解析 environments 标签
* @date 2020/2/27
*/
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 解析environment下的dataSource标签
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
我们发现这里参数列表,我们按照上面的操作方法得到context的数据。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="lxc186"/>
</dataSource>
</environment>
</environments>
细心的同学已经发现了,value值已经是具体的值了,不是${xxxx}的形式了。从config.properties文件里面获取到的。
六、解析 dataSource 标签
数据源的具体信息在dataSource里面,所以我们继续查看DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"))的dataSourceElement()方法。
/**
* 功能描述:解析 dataSource 标签
* @date 2020/2/27
*/
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
用同样的方法,得到参数列表中的context值。
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="lxc186"/>
</dataSource>
七、得到数据源
我们查看Properties props = context.getChildrenAsProperties()这行代码的方法。
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
可以看出是循环property标签,获得其name和value属性,并放入properties对象中。
然后接下来查看DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
其中resolveClass(type)在这里是resolveClass("POOLED") ,跟踪一下代码就很容易发现,resolveClass(type)最终调用的是typeAliasRegistry.resolveAlias(alias);。通过resolveClass(type)拿到Class类型后,就能通过反射生成PooledDataSourceFactory实例了。
最后,将props放到factory里面,再放入configuration里面,configuration.setEnvironment(environmentBuilder.build())
这样我们就得到了数据源。

流程总结:
