Mybatis官方文档:https://mybatis.org/mybatis-3/zh/index.html
Mybatis源码(3.5.8-SNAPSHOT版本)下载:https://github.com/mybatis/mybatis-3
从官网Demo代码来入手:https://mybatis.org/mybatis-3/zh/getting-started.html
底层使用java.io下的InputStream和Reader两个抽象类,通过Mybatis的Resources获取两种流的方法:
// 官网Demo代码
// 配置文件路径
String resource = "org/mybatis/example/mybatis-config.xml";
// 读取mybatis-config.xml,通过Resources获取文件输入流
// 详情见Resources和ClassLoaderWrapper源码分析
InputStream inputStream = Resources.getResourceAsStream(resource);
// 解析xml配置文件,得到SqlSessionFactory,具体解析流程如下
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactory为工厂类,单例模式,仅仅存储了Mybatis配置对象Configuration信息;就像名称一样,专门用来生产SqlSession对象,即会话;
SqlSessionFactoryBuilder:
// SqlSessionFactoryBuilder源码
package org.apache.ibatis.session;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
public class SqlSessionFactoryBuilder {
// SqlSessionFactoryBuilder来读取InputStream字节流的配置信息
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建XMLConfigBuilder对象
// 对应下面源码分析3、SqlSessionFactoryBuilder().build()中构建XMLConfigBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse()来生成Configuration配置对象,传给最终的build方法生成SqlSessionFactory对象
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.
}
}
}
// SqlSessionFactoryBuilder来读取Reader字符流的配置信息
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
// SqlSessionFactory对象生成的必要方法,必要参数为Configuration配置对象
public SqlSessionFactory build(Configuration config) {
// DefaultSqlSessionFactory为实现SqlSessionFactory接口的默认类
return new DefaultSqlSessionFactory(config);
}
}
DefaultSqlSessionFactory:
// DefaultSqlSessionFactory源码
package org.apache.ibatis.session.defaults;
// DefaultSqlSessionFactory类实现了SqlSessionFactory接口
public class DefaultSqlSessionFactory implements SqlSessionFactory {
// 唯一的成员变量configuration来管理Configuration配置对象
private final Configuration configuration;
// 唯一的构造方法在对象实例化时来给configuration赋值
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public Configuration getConfiguration() {
return configuration;
}
....
}
SqlSessionFactory接口:
// SqlSessionFactory 接口
package org.apache.ibatis.session;
import java.sql.Connection;
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
XMLConfigBuilder是用来解析XML配置文件,属于BaseBuilder子类,使用的是建造者设计模式,通过XMLConfigBuilder可以生成configuration对象,因为SqlSeesionFactory内唯一的成员变量configuration来管理Configuration配置对象。
继承BaseBuilder的类有:
XMLConfigBuilder:
1、XMLConfigBuilder构造函数中会先构建XPathParse对象:
XPathParse类的主要内容:
2、parse()方法用来生成Configuration配置对象
3、parseConfiguration()方法根据XNode节点遍历解析设置Configuration对象各项属性
可结合mybatis-config.xml配置文档来看:https://mybatis.org/mybatis-3/zh/configuration.html
// XMLConfigBuilder源码
package org.apache.ibatis.builder.xml;
public class XMLConfigBuilder extends BaseBuilder {
// 是否解析xml配置
private boolean parsed;
// XPathParser对象属性,parser.evalNode可以解析xml制定的节点生成XNode对象
private final XPathParser parser;
private String environment;
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
// 构造函数中先创建XPathParse对象
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
// 构造函数给对应属性设置相应的值
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// new Configuration()初始化默认的Configuration对象
// 调用父类BaseBuilder的构造函数,设置configuration、typeAliasRegistry、typeHandlerRegistry等属性
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
// 设置Properties到configuration中的属性variables
this.configuration.setVariables(props);
// 是否解析XML标志
this.parsed = false;
this.environment = environment;
// 存储XPathParser对象
this.parser = parser;
}
// 解析XML配置文件,返回Configuration对象
public Configuration parse() {
// 如果XML配置解析了就不需要再parse
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 使用XPathParser parser来从XML的根节点configuration开始解析,parser.evalNode()方法实际使用Xpath.evaluate来查询XML中节点和元素
// 根据parser.evalNode返回的XNode节点来解析将节点对应到Configuration配置对象
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//根据XNode节点遍历解析设置Configuration对象各项属性,root为configuration根节点XNode
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
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);
// 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);
}
}
}
XMLConfigBuilder中属性标签的解析源码:
属性标签的配置文档:https://mybatis.org/mybatis-3/zh/configuration.html#properties
/**
**/
// XMLConfigBuilder 属性标签的解析源码
package org.apache.ibatis.builder.xml;
// 属性标签的解析
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 读取properties标签下面property的配置name和value,放入Properties defaults中
//
Properties defaults = context.getChildrenAsProperties();
// 获取properties标签的resource属性值,如
String resource = context.getStringAttribute("resource");
// 获取properties标签的resource属性值,如
String url = context.getStringAttribute("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.");
}
if (resource != null) {
// 通过Resources.getResourceAsProperties获取本地属性放入defaults中
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
// Resources.getUrlAsProperties获取网络资源属性放入defaults中
defaults.putAll(Resources.getUrlAsProperties(url));
}
// configuration对象的variables属性用来存在这些动态属性,所以获取所有属性
Properties vars = configuration.getVariables();
if (vars != null) {
// 整合
defaults.putAll(vars);
}
// 重新设置XPathParser对象和configuration对象的Variables
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
XMLConfigBuilder中typeAliases标签的解析源码:
typeAliases标签官方配置文档:https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases
/**
// 直接通过直接某个bean的别名
// 指定一个包名,会扫描包下面的所有java bean,
// 如果包下面java bean有注解则使用@Alias注解的值
// 如果包下面java bean没有注解则使用bean的首字母小写非限定类名来作为它的别名
**/
// XMLConfigBuilder typeAliases标签的解析源码
package org.apache.ibatis.builder.xml;
// XMLConfigBuilder中解析typeAliases标签
private void typeAliasesElement(XNode parent) {
if (parent != null) {
// 获取typeAliases下面所有子节点遍历
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 如果配置是package标签,获取package标签下面到name属性值,即包名
String typeAliasPackage = child.getStringAttribute("name");
// configuration.getTypeAliasRegistry()为拿到配置对象的typeAliasRegistry别名注册器的方法
// registerAliases(typeAliasPackage)为包名批量注册
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属性值时,获取通过类class.getAnnotation(Alias.class)获取类上面的@Alias注解的值
// 如果也没有注解则获取类名,class.getSimpleName();
// 注册别名和class到configuration的typeAliasRegistry属性上面
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
TypeAliasRegistry根据包来批量注册别名和单个注册核心源码解析:
// TypeAliasRegistry 别名管理注册源码
package org.apache.ibatis.type;
public void registerAliases(String packageName) {
// 只指定包名时,传入Object.class表示包下的所有类均在别名注册的考虑范围
registerAliases(packageName, Object.class);
}
// 包名批量注册源码,最终也是单独注册
// superType 限定要注册的类的来源,只有继承自给定类型的类才能被注册
public void registerAliases(String packageName, Class> superType) {
// 工具类ResolverUtil为类加载器用于定位类路径下指定包下面的必要类
ResolverUtil> resolverUtil = new ResolverUtil<>();
// 搜索packageName包下面,继承自给定类型superType的类
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 获取已搜索的包下指定条件类集合
Set>> typeSet = resolverUtil.getClasses();
for (Class> type : typeSet) {
// Ignore inner classes and interfaces (including package-info.java)
// Skip also inner classes. See issue #6
// isAnonymousClass()用于检查基础类是否为匿名类(anonymousClass)
// isInterface()用于检查此Class是否是接口
// isMemberClass()用于检查基础类是否为成员类(memberClass)
// https://blog.csdn.net/idealcitier/article/details/106728539
// https://www.zhihu.com/question/67270393
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
// 排除匿名类、接口、成员类后的类再进行单个注册
registerAlias(type);
}
}
}
// 逐个注册别名核心源码
public void registerAlias(Class> type) {
// getSimplName()获取类的简写名称(不包含包路径)
// getName()获取得到类名称(包含路径)
String alias = type.getSimpleName();
// 获取类上Alias注解
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
// 如果注解值不为null则使用类上注解@Alias("xxx")的值xxx为别名来注册
alias = aliasAnnotation.value();
}
// 没有注解则使用类的SimpleName为别名注册
registerAlias(alias, type);
}
public void registerAlias(String alias, Class> value) {
if (alias == null) {
// 别名为null抛错
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
// 别名转小写
String key = alias.toLowerCase(Locale.ENGLISH);
// Map> typeAliases为TypeAliasRegistry中的管理器
// 如果已经注册过则不重复注册
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
}
// 放入Map
typeAliases.put(key, value);
}
TypeAliasRegistry类的源码方法解析:
构造函数会初始化一些内置类的别名,如:java的基本数据类型等,所以在Mybatis的xml中可以使用一些别名来替代java的基本数据类型,mapper里的parameterType和resultType。
// TypeAliasRegistry
package org.apache.ibatis.type;
public class TypeAliasRegistry {
// 别名机制管理器Map
private final Map> typeAliases = new HashMap<>();
// 构造函数,注册一些java内部的类到别名机制管理器上面
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);
}
// TypeAliasRegistry中解析别名方法
public Class resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// issue #748
String key = string.toLowerCase(Locale.ENGLISH);
Class value;
if (typeAliases.containsKey(key)) {
// 如果typeAliases里面包含key,直接从HashMap中取
value = (Class) typeAliases.get(key);
} else {
// 如果typeAliases里面不包含key,通过Resources.classForName获取类路径对应的Class实例
// 详情见Resources和ClassLoaderWrapper源码分析
value = (Class) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
...
}
XMLConfigBuilder中mappers标签的解析源码:
mappers映射器的配置规则 ,可看官方文档:https://mybatis.org/mybatis-3/zh/configuration.html#mappers
/**
**/
// XMLConfigBuilder mappers标签的解析源码
package org.apache.ibatis.builder.xml;
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 循环便利mappers下面的子标签
for (XNode child : parent.getChildren()) {
// 解析package标签配置的包
if ("package".equals(child.getName())) {
// 获取包名称
String mapperPackage = child.getStringAttribute("name");
// configuration对象中根据包名添加包下面所有类
// 其中根据resolverUtil.find(new ResolverUtil.IsA(superType), packageName)来获取包下面所有.class类
// 便利所以class然后addMapper(mapperClass);去添加mapper类接口的动态代理MapperProxyFactory类
// MapperProxyFactory使用了JDK的Proxy.newProxyInstance代理方法实现动态代理
// 添加到configuration对象的mapperRegistry中,用来管理所有mapper
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);
// 外部Mapper.xml生成输入流
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
// 通过XMLMapperBuilder来解析Mapper.xml文件
// 底层也是使用XPathParser去解析
// mapperParser.parse()内去解析mapper.xml的sql语句statement,result、parame等
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(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
Class> mapperInterface = Resources.classForName(mapperClass);
// 添加到configuration对象的mapperRegistry中,用来管理所有mapper
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
DTD(Document Type Definition,文档类型定义)
定义 XML 文档的合法构建模块,它使用一系列的合法元素来定义文档结构,官方文档:https://www.w3school.com.cn/dtd/dtd_intro.asp
XSD(XML Schema Definition,XML Schema 语言)
XML Schema 是基于 XML 的 DTD 替代者。也是描述 XML 文档的结构,官方文档:https://www.w3school.com.cn/schema/index.asp
XML配置文件标签使用的DTD声明位置:org.apache.ibatis.builder.xml.mybatis-3-config.dtd
XML配置文件标签使用的XSD声明位置:org.apache.ibatis.builder.xml.mybatis-config.xsd
XMLMapper文件标签使用的DTD声明位置:org.apache.ibatis.builder.xml.mybatis-3-mapper.dtd
XMLMapper文件标签使用的XSD声明位置:org.apache.ibatis.builder.xml.mybatis-mapper.xsd
XMLConfigBuilder中plugins的解析源码:
属性标签的配置文档:https://mybatis.org/mybatis-3/zh/configuration.html#plugins
可以使用插件拦截的方法有:
// XMLConfigBuilder plugins标签的解析源码
package org.apache.ibatis.builder.xml;
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
// 获取子节点遍历
for (XNode child : parent.getChildren()) {
// 获取子节点的interceptor属性配置值,为插件实现类的完全限定类名
String interceptor = child.getStringAttribute("interceptor");
// 获取子节点内的属性配置
Properties properties = child.getChildrenAsProperties();
//
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}