继续“解密”,Mybatis整体分为两大部分:配置信息的初始化阶段和具体使用阶段,MyBatis初始化过程说白了就是主要解析mybatis-config.xml和各个mapper.xml(注解方式略微不同),生成内存配置信息供后面使用,那我们本篇就介绍一下mybatis的配置信息解析流程。
从初始化入口开始
我们还需要明确一点,初始化最最主要的目的就是根据XML配置文件生成SqlSessionFactory工厂,然后使用的时候从工厂中产生SqlSession,当然Sql具体的执行也是委托给Executor的,这个后面再说。还是先看一下之前的使用demo:
// 初始化配置阶段
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream)
// 使用阶段
SqlSession sqlSession = sqlSessionFactory.openSession()
UserMapper mapper = sqlSession.getMapper(UserMapper.class)
List<User> users = mapper.listUsers()
复制代码
以输入流方式读入mybatis-config.xml配置文件,然后交给SqlSessionFactoryBuilder构造器来进行具体sqlSessionFactory的构建过程,先跟踪build方法干些什么是,最后我们可以定位到如下方法:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
复制代码
我们的关注点开业进一步定位到parse方法了,parse()调用parseConfiguration()方法就开始按流程解析
mybatis-config.xml
配置文件了:
propertiesElement(root.evalNode("properties"));
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
复制代码
我们先不挨着分析,看准重心,着重分析mappers的解析过程,这个过程是重要且复杂的,ok,继续跟进mapperElement方法:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// package方式,xxxMaper.java必须和xxxMapper.xml同名且在同一路径下
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name")
// 加入到全局mapperRegistry对应的Map<Class<?>, MapperProxyFactory<?>> knownMappers中
configuration.addMappers(mapperPackage)
} else {
String resource = child.getStringAttribute("resource")
String url = child.getStringAttribute("url")
String mapperClass = child.getStringAttribute("class")
// resource方式加载,从resource路径下寻找xml文件进行解析
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) {
// url方式加载
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方式加载、解析 具体同package方式类似,只是变为解析单个文件
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.")
复制代码
通过分析上一个文件我们知道,这一阶段的主要目的就是将所有的mapper存入到knownMappers中,knownMappers中保的key就是各具体的Mapper接口名,为了方便使用,内部具体会保存两份,全限定接口名("com.boykait.user.mapper.UserMapper")和具体文件名("UserMapper"),这个时候xxxMaper就对应指向了具体的MapperProxyFactory,做这个映射干嘛?你要知道我们具体xxxMaper接口没有实现类,凭什么能够使用?so,它实际的工作就是通过动态代理去完成的,具体的后面再说,现在先知道Maper接口是通过代理执行对应的CURD操作的。
好的,我们就以resource方式继续跟进,进入mapperParser.parse():
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
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);
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
复制代码
在具体的开发过程中,比如做查询,我们一般会使用include|if|foreach|where|tirm等条件来创建满足需求的查询语句,所以追踪上面的重点继续,我发誓,这是最后一次粘贴源码:
public void parseStatementNode() {
String id = context.getStringAttribute("id")
String databaseId = context.getStringAttribute("databaseId")
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return
String nodeName = context.getNode().getNodeName()
// 具体是CURD哪种操作命令
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH))
boolean isSelect = sqlCommandType == SqlCommandType.SELECT
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect)
boolean useCache = context.getBooleanAttribute("useCache", isSelect)
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false)
// Include Fragments before parsing
// 将sql片段进行替换
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant)
includeParser.applyIncludes(context.getNode())
String parameterType = context.getStringAttribute("parameterType")
Class<?> parameterTypeClass = resolveClass(parameterType)
String lang = context.getStringAttribute("lang")
LanguageDriver langDriver = getLanguageDriver(lang)
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver)
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true)
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId)
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE
// 重点 判断是哪一种SqlSource DynamicSqlSource|ProviderSqlSource|RawSqlSource|StaticSqlSource
// ${}对应DynamicSqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass)
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()))
Integer fetchSize = context.getIntAttribute("fetchSize")
Integer timeout = context.getIntAttribute("timeout")
String parameterMap = context.getStringAttribute("parameterMap")
String resultType = context.getStringAttribute("resultType")
Class<?> resultTypeClass = resolveClass(resultType)
String resultMap = context.getStringAttribute("resultMap")
String resultSetType = context.getStringAttribute("resultSetType")
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType)
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType()
String keyProperty = context.getStringAttribute("keyProperty")
String keyColumn = context.getStringAttribute("keyColumn")
String resultSets = context.getStringAttribute("resultSets")
// 创建Map<String, MappedStatement> mappedStatements,存入全局configuration中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets)
复制代码
梳理总结
还是懒,这篇写到这点了吧,到这,简要总结一下:
初始化的主线流程
应该知道mybatis mapper文件加载方式的优先级别
xxxMaper接口方法的使用是需要经过代理的(后面具体详细研究)
SqlSource具体的判断和创建(后面研究)
waynaqua
MyBatis