版本
mybatis:3.5.3
源码分析
使用(不集成Spring)
public class App {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
Reader reader;
try {
//将XML配置文件构建为Configuration配置类
reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
// 数据源 执行器 DefaultSqlSession
SqlSession session = sqlMapper.openSession();
try {
// 执行查询 底层执行jdbc
TimeZoneName timeZoneName = (TimeZoneName)session.selectOne("com.whc.mapper.TimeZoneNameMapper.selectById", 12);
TimeZoneNameMapper timeZoneNameMapper = session.getMapper(TimeZoneNameMapper.class);
TimeZoneName zoneName = timeZoneNameMapper.selectById(11);
System.out.println(zoneName);
session.commit();
System.out.println(timeZoneName);
} catch (Exception e) {
e.printStackTrace();
}finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用(不集成Spring)
public class App {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
Reader reader;
try {
//将XML配置文件构建为Configuration配置类
reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
// 数据源 执行器 DefaultSqlSession
SqlSession session = sqlMapper.openSession();
try {
// 执行查询 底层执行jdbc
TimeZoneName timeZoneName = (TimeZoneName)session.selectOne("com.whc.mapper.TimeZoneNameMapper.selectById", 12);
TimeZoneNameMapper timeZoneNameMapper = session.getMapper(TimeZoneNameMapper.class);
TimeZoneName zoneName = timeZoneNameMapper.selectById(11);
System.out.println(zoneName);
session.commit();
System.out.println(timeZoneName);
} catch (Exception e) {
e.printStackTrace();
}finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
流程主要分为以下几步:
1.解析配置文件
2.解析mapper
3.获取SqlSession
4.使用
解析配置文件
最终在org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration 中解析xml配置文件各个属性
private void parseConfiguration(XNode root) {
try {
//1.解析 properties节点
propertiesElement(root.evalNode("properties"));
//2.解析 settings节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
//3.加载自定义文件系统
loadCustomVfs(settings);
//4.加载自定义日志实现类
loadCustomLogImpl(settings);
//5.加载别名
typeAliasesElement(root.evalNode("typeAliases"));
//6.加载插件
pluginElement(root.evalNode("plugins"));
//不常用
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//7.设置settings 和默认值
settingsElement(settings);
//8.environments节点
environmentsElement(root.evalNode("environments"));
//9.解析数据库厂商
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//10.解析我们的类型处理器节点
typeHandlerElement(root.evalNode("typeHandlers"));
//11.解析mapper
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
1.解析属性(properties)
2.解析设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
3.加载自定义文件系统
VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序 解析到:org.apache.ibatis.session.Configuration#vfsImpl
4.加载自定义日志实现类
指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
* SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING *
解析到org.apache.ibatis.session.Configuration#logImpl
5.加载类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。
6.加载插件
configuration.addInterceptor(interceptorInstance);
会加入到 InterceptorChain 拦截器链
7.设置settings 和默认值
private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultscriptingLanguage(resolveClass(props.getProperty("defaultscriptingLanguage")));
configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersonNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
8.environments环境配置节点
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
9.解析数据库厂商
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:
10.解析我们的类型处理器typeHandlers节点
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
11.解析映射器(mappers)
指定mapper映射文件可以通过如下4中方式:类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名
解析mapper文件
入口在:org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
我们是通过
if ("package".equals(child.getName()))分支,
通过configuration.addMappers(mapperPackage)来解析
在MapperRegistry#addMappers(java.lang.String, java.lang.Class>)中循环指定包名下的每个类,使用 MapperAnnotationBuilder 的parse()解析每个类,
最终用XMLMapperBuilder.parse() 方法解析mapper.xml文件
我们来看看org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement,具体解析mapper.xml
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
当配置了
二级缓存对象通过装饰器模式,一层层包装
SynchronizedCache-->LoggingCache-->SerializedCache-->LruCache-->PerpetualCache
而解析select | insert |update |delete节点会在XMLMapperBuilder#buildStatementFromContext(java.util.List
递归解析 selectById这个sql元素会解析成
1层 MixedSqlNode
把解析结果放入 mappedStatement
获取SqlSession通过SqlSessionFactory.openSession()获得回话
openSession()主要作用:
1.创建执行器 Executor : newExecutor
2.依次调用每个插件的plugin方法,包装Executor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
//可重复使用的执行器
executor = new ReuseExecutor(this, transaction);
} else {
//简单的sql执行器对象
executor = new SimpleExecutor(this, transaction);
}
//判断mybatis的全局配置文件是否开启缓存
if (cacheEnabled) {
//把当前的简单的执行器包装成一个CachingExecutor
}
executor = new CachingExecutor(executor);
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
执行查询
TimeZoneNameMapper timeZoneNameMapper = session.getMapper(TimeZoneNameMapper.class);
TimeZoneName zoneName = timeZoneNameMapper.selectById(11);
由于我们是返回单个对象,所以调用selectOne
最后我们得到了
O:className
R:jdbcTypes
M:columnNames
根据对应关系,我们得到了返回值对象
流程图 总体 解析xml 执行sql



