目录
- 1. 简介
- 1.1 系列内容
- 1.2 适合对象
- 1.3 本文内容
- 2. 配置文件
- 2.1 mysql.properties
- 2.2 mybatis-config.xml
- 3. Configuration 类及其解析
- 3.1 解析入口
- 3.2 常用函数
- 3.2.1 获取节点
- 3.2.2 获取子节点
- 3.2.3 获取子节点并存到 Properties 对象中
- 3.3 节点相关成员变量及其解析
- 3.3.1 properties 属性相关
- 3.3.2 settings 属性相关
- 3.3.3 typeAliases 相关属性
- 3.3.4 plugins 相关属性
- 3.3.5 objectFactory 相关属性
- 3.3.6 objectWrapperFactory 相关属性
- 3.3.7 reflectorFactory 相关属性
- 3.3.8 environments 相关属性
- 3.3.9 databaseIdProvider 相关属性
- 3.3.10 typeHandlers 相关属性
- 3.3.11 mappers 相关属性
- 结语
- 一起学 mybatis
@
1. 简介
1.1 系列内容
本系列文章讲解的是mybatis解析配置文件内部的逻辑, 即
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
其背后的逻辑。
1.2 适合对象
- 了解如何使用 mybatis 来访问数据库。可参看《mybatis 初步使用(IDEA的Maven项目, 超详细)》;
- 具备 DOM 解析 XML 的基本知识。 可参考《mybatis源码-解析配置文件(一)之XML的DOM解析方式。
- 了解整个配置文件解析是什么样的一个流程。可参考《mybatis源码-解析配置文件(二)之解析的流程》。
1.3 本文内容
Configuration
类的讲解;parser.parse()
将配置解析到Configuration
中的过程;
2. 配置文件
mybatis使用到了配置文件, 本文中, 不讲解 mapper.xml, 后续会另开章节重点讲解。
2.1 mysql.properties
这是存储数据库信息的对应文件。
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis
mysql.username=root
mysql.password=jim666
2.2 mybatis-config.xml
核心配置文件, 管理 mybatis 的运行行为。
3. Configuration 类及其解析
Configuration
类对应的就是我们的 mybatis-config.xml 配置文件, 我们配置的信息经过解析后就会存储到Configuration
的成员变量中。
3.1 解析入口
解析的入口是以下的函数:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
首先通过parser.evalNode("/configuration")
函数解析解析出 configuration节点, 在使用parseConfiguration
函数解析获得旗下各个节点的信息。
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
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);
}
}
3.2 常用函数
3.2.1 获取节点
其实就是使用 DOM 结合 Xpath 的方法获得各个节点。
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
而后返回相应的 XNode 类对象。
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
最终调用的是xpath.evaluate(expression, root, returnType)
, 不理解这个过程的可参考之前的文章。
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
3.2.2 获取子节点
public List getChildren() {
List children = new ArrayList();
NodeList nodeList = node.getChildNodes();
if (nodeList != null) {
for (int i = 0, n = nodeList.getLength(); i < n; i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
children.add(new XNode(xpathParser, node, variables));
}
}
}
return children;
}
实际上是调用了 DOM 中的 Node
类的 getChildNodes 函数, 并将返回的节点转换为 XNode
, 添加到对应的 List
中返回。
3.2.3 获取子节点并存到 Properties 对象中
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;
}
这些子节点都含有 name 和 value 属性, 存 name->value 的形式。
3.3 节点相关成员变量及其解析
Configuration
中有很多成员变量:
可以分为以下几种, 按配置的顺序:
3.3.1 properties 属性相关
3.3.1.1 成员变量
protected Properties variables = new Properties();
对应 Properties>
类, 可以将相应的配置 .properties 的文件信息存放到里面。
3.3.1.2 对应 XML 节点:
3.3.1.3 作用
如 mysql.properties 文件, 其内容如下:
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis
mysql.username=root
mysql.password=jim666
在解析后, 存放在 variables 成员变量中, 是这样:
这样做的目的是我们可以将这些属性在后面的内容中复用:
3.3.1.4 解析过程
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 先解析下的各个子节点, 以name->value的形式记录到 Properties 对象中
Properties defaults = context.getChildrenAsProperties();
// 解析 resource 和 url 属性
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
// resource 和 url 属性不能同时不为空, 也就是说只能存在一个
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
// 根据 resource 或 url, 将对应的 .properties 文件加载进来, 并将内容全部存到 Properties 对象中
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
// 将Configuration 对象中原先的属性合并进来。
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
// 两个含有该成员变量的类对象更新
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
几个要点:
- 先读取的是
下的 属性; 中, 不能同时存在 resource 和 url 属性; - 更新时, 不单单只是更新 configuration, XPathParser 对象也需要更新, 后面用得到。
3.3.2 settings 属性相关
3.3.2.1 成员变量
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
protected String logPrefix;
protected Class extends Log> logImpl;
protected Class extends VFS> vfsImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set lazyLoadTriggerMethods = new HashSet(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory();
protected Class> configurationFactory;
成员变量名称和以上配置文件的 name 属性一一对应。
3.3.2.2 对应 XML 节点:
...
在 XML 文件中,
一个配置完整的 settings 元素的示例如下:
更具体的内容, 参见 mybatis简介
3.3.2.3 作用
settings 是 MyBatis 中极为重要的设置,它们会改变 MyBatis 的运行时行为。
3.3.1.4 解析过程
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
// 解析所有的子节点, 并存到 Properties 对象中。
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
// 创建对应的 MetaClass 对象
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
// 检测 Configuraion 对象中是否定义了相应的 setter 方法, 不定义代表不存在该属性, 直接抛异常
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).");
}
}
return props;
}
3.3.3 typeAliases 相关属性
3.3.3.1 成员变量
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
3.3.3.2 对应 XML 节点:
typeAliases
, 即类型别名。类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
3.3.3.3 作用
类似以上配置, 当需要使用domain.blog.Author
时, 我们可以用Author
来进行代替。
当开启包扫描之后, 在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。
3.3.3.4 解析过程
private void typeAliasesElement(XNode parent) {
if (parent != null) {
// 处理全部的子类
for (XNode child : parent.getChildren()) {
// 处理 子节点
if ("package".equals(child.getName())) {
// 获取配置处的属性 name , 其对应着包名
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
// 处理 节点
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class> clazz = Resources.classForName(type);
if (alias == null) {
// 如果alias没有配置,则按照约定的方式注册类
typeAliasRegistry.registerAlias(clazz);
} else {
// alias 配置了, 则将类注册到alias
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
3.3.4 plugins 相关属性
3.3.4.1 成员变量
protected final InterceptorChain interceptorChain = new InterceptorChain();
3.3.4.2 对应 XML 节点:
在之前的项目中, 没有配置相应的plugins, 但可以参考使用 Mybatis-PageHelper 的配置
3.3.4.3 作用
插件是 MyBatis 提供的扩展机制之一,用户可以通过添加自定义插件在 SQL 语句执行过程中的某一点进行拦截。
更具体的后面会专门开一章进行讲解。
3.3.4.4 解析过程
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历 下的所有 节点
for (XNode child : parent.getChildren()) {
// 获取对应的 中的 interceptor 属性
String interceptor = child.getStringAttribute("interceptor");
// 获取 下的所有 节点, 并以 name->value 的形式存入 Properties 对象中
Properties properties = child.getChildrenAsProperties();
// 通过反射生成对象
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 设置拦截器的属性
interceptorInstance.setProperties(properties);
// 将拦截器添加到 Configuration 对象中
configuration.addInterceptor(interceptorInstance);
}
}
}
3.3.5 objectFactory 相关属性
3.3.5.1 成员变量
protected ObjectFactory objectFactory = new DefaultObjectFactory();
3.3.5.2 对应 XML 节点:
3.3.5.3 作用
MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。
3.3.5.4 解析过程
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
// 获取 的type属性
String type = context.getStringAttribute("type");
// 获取 下的所有 节点, 并以 name->value 的形式存入 Properties 对象中
Properties properties = context.getChildrenAsProperties();
// 通过反射生成对象
ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
// 设置属性
factory.setProperties(properties);
// 将 ObjectFactory 对象设置到 Configuration 对象中
configuration.setObjectFactory(factory);
}
}
3.3.6 objectWrapperFactory 相关属性
3.3.6.1 成员变量
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
3.3.6.2 对应 XML 节点:
3.3.6.3 作用
启用之后, 可以自己实现驼峰效果。
3.3.6.4 解析过程
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
// 获取 的type属性
String type = context.getStringAttribute("type");
// 通过反射生成 ObjectWrapperFactory 对象
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
// 将 ObjectWrapperFactory 对象设置到 Configuration 对象中
configuration.setObjectWrapperFactory(factory);
}
}
3.3.7 reflectorFactory 相关属性
3.3.7.1 成员变量
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
3.3.7.2 对应 XML 节点:
3.3.7.3 作用
可以自己定义反射类
3.3.7.4 解析过程
private void reflectorFactoryElement(XNode context) throws Exception {
if (context != null) {
// 获取 的type属性
String type = context.getStringAttribute("type");
// 通过反射生成 ReflectorFactory 对象
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
// 将 ReflectorFactory 对象设置到 Configuration 对象中
configuration.setReflectorFactory(factory);
}
}
3.3.8 environments 相关属性
3.3.8.1 成员变量
protected Environment environment;
Enviroment 中, 含有id, 事务, 数据源:
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
...
}
3.3.8.2 对应 XML 节点:
Environment主要用于配置数据源和事务信息。Mybatis支持多环境设置,可以为开发,测试,生产使用不同的配置。
3.3.8.3 作用
就是设置数据库连接的环境, 需要配置事务管理器和数据源来构造相应的对象。
既然environments
可以配置多个environment
, 那么为什么成员变量的类型不是List
?
答: 多个环境,每个 SqlSessionFactory 实例只能选择其一
3.3.8.4 解析过程
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// 未指定, 则使用 default 属性指定的
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 遍历所有的 子节点
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
// 只有 id 与 XMLConfigBuilder 的 environment 匹配上才会进行解析里面的内容
if (isSpecifiedEnvironment(id)) {
// 创建 TransactionFactory 对象
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 创建 DataSourceFactory 对象, 并以之创建 DataSource 对象
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 创建 Environment.Builder 对象, 将以上产生的对象对应设置到该对象中
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// environmentBuilder.build()创建 Environment 对象, 并设置到 Configuration 对象对应的成员变量中
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
3.3.9 databaseIdProvider 相关属性
3.3.9.1 成员变量
protected String databaseId;
3.3.9.2 对应 XML 节点:
3.3.9.3 作用
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。在后续的 XXXXmapper.xml 文件中, 可以指定 databaseId。
3.3.9.4 解析过程
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// awful patch to keep backward compatibility
// 为了兼容, 更改 type 为 VENDOR 至 DB_VENDOR
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
// 解析对应的配置信息
Properties properties = context.getChildrenAsProperties();
// 通过反射生成对象
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
// 设置属性
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
3.3.10 typeHandlers 相关属性
3.3.10.1 成员变量
protected final TypeHandlerRegistry typeHandlerRegistry;
3.3.10.2 对应 XML 节点:
3.3.10.3 作用
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
3.3.10.4 解析过程
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历所有的子节点
for (XNode child : parent.getChildren()) {
// 包(package)子节点
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else { // 非 package 子节点
// 获取相应的属性
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
// 根据属性, 通过反射创建对象
Class> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class> typeHandlerClass = resolveClass(handlerTypeName);
// 将对象注册到 typeHandlerRegistry 对象中
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
3.3.11 mappers 相关属性
3.3.11.1 成员变量
//映射的语句
protected final Map mappedStatements = new StrictMap("Mapped Statements collection");
// 缓存集合
protected final Map caches = new StrictMap("Caches collection");
// 结果集合
protected final Map resultMaps = new StrictMap("Result Maps collection");
// 存储过程参数集合
protected final Map parameterMaps = new StrictMap("Parameter Maps collection");
// 主键生成器合集
protected final Map keyGenerators = new StrictMap("Key Generators collection");
protected final Set loadedResources = new HashSet();
protected final Map sqlFragments = new StrictMap("XML fragments parsed from previous mappers");
// 存储不完整的语句
protected final Collection incompleteStatements = new LinkedList();
protected final Collection incompleteCacheRefs = new LinkedList();
protected final Collection incompleteResultMaps = new LinkedList();
protected final Collection incompleteMethods = new LinkedList();
3.3.11.2 对应 XML 节点:
3.3.11.3 作用
mybatis-config.xml 配置文件中的<mappers>节点会告诉 MyBatis 去哪些位置查找映射配置文件以及使用了配置注解标识的接口。
这也是配置文件解析的重点, 后续会开一篇文章进行讲解。
3.3.11.4 解析过程
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 子节点, 就是包下面的类
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else { // 对应 子节点
// resource / url / class 中 3选1
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 如果节点指定了 resource 或 url, 则创建 XMLMapperBuilder 对象,
// 通过该对象的 parse() 函数将 mapper 添加到 configuration 中
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
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.");
}
}
}
}
}
这个是后面需要更详细的开章节进行讲解的。
结语
至此, 本文章就结束了。 本来是想写更详细一点的, 后面发现篇幅越来越大, 因此有一些部分没有展开。后续会挑某些部分的解析过程进行更深入的讲解。
看完发现人家的命名真是很规范, 值得学习。
如果您觉得还可以, 给个赞吧!
一起学 mybatis
你想不想来学习 mybatis? 学习其使用和源码呢?那么, 在博客园关注我吧!!
我自己打算把这个源码系列更新完毕, 同时会更新相应的注释。快去 star 吧!!
mybatis最新源码和注释