MyBatis源码中第二部分是一个庞大的部分,同时也是源码笔记的核心。主要内容涉及资源加载,SQL加载,SQL执行和结果集的处理。
首先一个比较大的模块是资源的加载。
mybatis中我们定义的主要资源主要被分为三个部分
- 配置文件
- mapper.xml(定义在xml中的SQL)
- mapper接口
后续还有很多东西需要说,但是首先学习这三部分的加载过程。
本篇主要学习的是配置文件的加载,也就是mybatis-config.xml数据的加载
mybatis加载的入口
org.apache.ibatis.session.SqlSessionFactoryBuilder
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 创建XML的配置对象
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 执行XML的解析工作,并且构建SQL会话工厂
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.
}
}
}
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
这里插一句
BaseBuilder是mybatis的基础构造器,他有很多实现方法,其实从名字上就可以看出来对应不同的功能的工具了。有些后续会学到,有些不会,有兴趣可以单独看看。
XMLConfigBuilder
回过来继续讲XMLConfigBuilder
之前的代码发现
return build(parser.parse());
使用的是其parse方法,解析成Configuration对象,而其在XMLConfigBuilder中的逻辑是下面样子。
public Configuration parse() {
// 已经解析
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 解析node节点名字为configuration的数据
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
可以看到他是将parser的配置节点通过parseConfiguration方法进行解析的
XPathParser
首先我们看看这个parser是XPathParser 的对象
// 基于 Java XPath 解析器
private final XPathParser parser;
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
// org.apache.ibatis.parsing.XPathParser.java
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
XPathParser的参数和赋值逻辑
/**
* eval 方法族 简单的进行类型装换
* eval 节点 用于获得 Node 类型的节点的值
* 用于解析 MyBatis mybatis-config.xml 和 **Mapper.xml 等 XML 配置文件
* @author Clinton Begin
* @author Kazuki Shimizu
*/
public class XPathParser {
// XML Document 对象
private final Document document;
// 是否校验
private boolean validation;
// XML 实体解析器
private EntityResolver entityResolver;
// 变量 Properties 对象
private Properties variables;
// Java XPath 对象
private XPath xpath;
// 无关代码
/**
* 创建 Document 对象
*
* @param inputSource XML 的 InputSource 对象
* @return Document 对象
*/
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
// 创建 DocumentBuilderFactory 对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 是否验证XML
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
// 创建 DocumentBuilder 对象
DocumentBuilder builder = factory.newDocumentBuilder();
// 设置实体解析器
builder.setEntityResolver(entityResolver);
// 错误处理模块
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
// 解析文件
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
}
其内部逻辑就是讲数据流转换成Document 对象。
而其获得节点的方法逻辑是:
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
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);
}
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);
}
}
使用xpath在document中获取指定值。这点有兴趣的话可以单独去看看java的xpath相关原理,这里先不了扩展了。
parseConfiguration
主要对解析完成的Xpath进行处理的逻辑在parseConfiguration方法里面
private void parseConfiguration(XNode root) {
try {
// 从XNode中解析所需数据
//issue #117 read properties first
// 解析 <properties /> 标签
propertiesElement(root.evalNode("properties"));
// 解析 <settings /> 标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 加载自定义 VFS 实现类
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 解析 <typeAliases /> 标签
typeAliasesElement(root.evalNode("typeAliases"));
// 解析 <plugins /> 标签
pluginElement(root.evalNode("plugins"));
// 解析 <objectFactory /> 标签
objectFactoryElement(root.evalNode("objectFactory"));
// <objectWrapperFactory /> 标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析 <reflectorFactory /> 标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 赋值 <settings /> 到 Configuration 属性
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
// 解析 <databaseIdProvider /> 标签
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析 <typeHandlers /> 标签
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析 <mappers /> 标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
可以看到主要逻辑还是通过从XNode中获取不同参数。
propertiesElement
我们从上面挑几个方法看看,首先这个propertiesElement
// 解析 <properties /> 节点
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 获取子标签集合为属性对象
Properties defaults = context.getChildrenAsProperties();
// 读取对应的属性
String resource = context.getStringAttribute("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) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {// 读取远程 Properties 配置文件进行配置
defaults.putAll(Resources.getUrlAsProperties(url));
}
// 覆盖 configuration 中的 Properties 对象到 defaults
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
// 设置defaults到parser和configuration中
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
会发现mybatis会首先从XNode节点上获取值,假如获取不到的时候,会尝试从Resources中查询,并将结果放回到XPathParser和configuration中。
其他的方法
// 解析 <objectWrapperFactory /> 节点
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
// 获得 ObjectWrapperFactory 的实现类的名称
String type = context.getStringAttribute("type");
// 创建ObjectWrapperFactory对象然后设置进configuration中
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
configuration.setObjectWrapperFactory(factory);
}
}
// 解析 <reflectorFactory /> 节点
private void reflectorFactoryElement(XNode context) throws Exception {
if (context != null) {
// 获得 ReflectorFactory 的实现类的名称
String type = context.getStringAttribute("type");
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
// 创建ReflectorFactory对象然后设置进configuration中
configuration.setReflectorFactory(factory);
}
}
其他的方法其实都很雷同都是从XNode中获取指定key的值,然后保存进configuration中。所以有兴趣的可以看看其他方法,代码上我也注释了相关方法代表什么内容。每个方法都不算太难。
Configuration
从之前的逻辑我们可以发现,最终所有的内容都会被放入Configuration中,那么Configuration到底有什么呢?
/**
* mybatis 的配置独享
* @author Clinton Begin
*/
public class Configuration {
// 环境
protected Environment environment;
// 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。
protected boolean safeRowBoundsEnabled;
// 允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为 false。
protected boolean safeResultHandlerEnabled = true;
// 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。
protected boolean mapUnderscoreToCamelCase;
// 当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载
protected boolean aggressiveLazyLoading;
// 是否允许单一语句返回多结果集(需要驱动支持)。
protected boolean multipleResultSetsEnabled = true;
// 允许 JDBC 支持自动生成主键,需要驱动支持。
protected boolean useGeneratedKeys;
// 使用列标签代替列名。不同的驱动在这方面会有不同的表现,
// 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。
protected boolean useColumnLabel = true;
// 全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。
protected boolean cacheEnabled = true;
// 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法
protected boolean callSettersOnNulls;
// 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译
protected boolean useActualParamName = true;
// 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意
protected boolean returnInstanceForEmptyRow;
// 指定 MyBatis 增加到日志名称的前缀。
protected String logPrefix;
// 日志的实现
protected Class<? extends Log> logImpl;
// vfs的实现
protected Class<? extends VFS> vfsImpl;
// MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
// 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
// 指定哪个对象的方法触发一次延迟加载。
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
// 设置超时时间,它决定驱动等待数据库响应的秒数。
protected Integer defaultStatementTimeout;
// 为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。
protected Integer defaultFetchSize;
// 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements)
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
// 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
// 指定发现自动映射目标未知列(或者未知属性类型)的行为。
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
// 变量 Properties 对象。
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
*/
protected Class<?> configurationFactory;
// MapperRegistry 对象
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
// LanguageDriverRegistry 对象
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
// MappedStatement 映射
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
// 缓存合集 Cache 对象集合 KEY:命名空间 namespace
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
// KeyGenerator 的映射
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
// 已加载资源( Resource )集合
protected final Set<String> loadedResources = new HashSet<>();
// 可被其他语句引用的可重用语句块的集合
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
// 未完成的 MethodResolver 集合
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<>();
public Configuration(Environment environment) {
this();
this.environment = environment;
}
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
}
是不是有炒鸡多的配置,ε=(´ο`*)))唉
因为mybatis的配置都集中在这个文件,后续我们解析mapper文件和mapper接口的时候会频繁的光顾它。