mybatis 初始化过程
基于 SqlSession 的使用案例如下:
// 加载全局配置⽂件
InputStream resourceAsStream = Resources.getResourceAsStream("myabtis-config.xml");
// 获得 sqlSession ⼯⼚对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 方式一
try (SqlSession session = sqlSessionFactory.openSession()) {
User user = (User) session.selectOne("com.stydu.mybatis.mapper.UserMapper.findById", 101);
}
// 方式二
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.findById(101);
}
可以看到,先是通过 Resources.getResourceAsStream("myabtis-config.xml")
得到配置文件流,然后通过 SqlSessionFactoryBuilder.builder
方法得到 SqlSessionFactory 对象,然后就可以使用 sqlSession 提供的方法与数据库交互了。这里先看 SqlSessionFactory 具体怎么得到的
-
文件流传入 build 方法
// org.apache.ibatis.session.SqlSessionFactoryBuilder#build public SqlSessionFactory build(InputStream inputStream) { return this.build((InputStream)inputStream, (String)null, (Properties)null); }
-
调用重载的 build 方法
// org.apache.ibatis.session.SqlSessionFactoryBuilder#build public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { SqlSessionFactory var5; try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // parser.parse() 得到一个 Configuration var5 = this.build(parser.parse()); } catch (Exception var14) { ... } finally { ... } return var5; }
从源码可以看出,1 创建了一个 XMLConfigBuilder 对象,2 然后调用了 parse() 方法,返回一个 Configuration 对象,3 根据 Configuration 对象得到 SqlSessionFactory(最终实现类是 DefaultSqlSessionFactory,后面 session 那块专门讲)
前面说配置文件时有说到:可以配置多个环境,但是 SqlSessionFactory 只能选择一个环境,这里创建 XMLConfigBuilder 对象时,环境对象是空,说明要使用默认的环境
Configuration 对象就是配置文件和映射文件解析之后得到的对象,看下对象怎么得到的以及这个对象有哪些重要属性
// 分析这两行代码 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); Configuration configuration = parser.parse();
-
创建 XMLConfigBuilder 源码如下
// org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } // org.apache.ibatis.parsing.XPathParser#XPathParser public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) { commonConstructor(validation, variables, entityResolver); // 主要是这里,得到 文件流 的 文档对象 this.document = createDocument(new InputSource(inputStream)); } // org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { // 创建一个 Configuration 设置给 XMLConfigBuilder super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; // 赋值给 parser,XMLConfigBuilder 也具有了 文档对象 this.parser = parser; }
总结一下,主要信息如下:
1,先是根据文件流创建一个 XPathParser 对象,这个对象主要包含了文件流的 document 对象;
2,创建 XMLConfigBuilder 对象,同时初始化一个 Configuration,并设置 parser 为 XPathParser 对象。
至此 XMLConfigBuilder 包含了一个 Configuration 对象,也具有了配置文件信息,后续就是调用 parser 方法来解析配置文件,并把解析结果设置给 configuration
-
parse() 方法源码如下:
// org.apache.ibatis.builder.xml.XMLConfigBuilder#parse public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 先获取 configuration 节点,也就是根节点 parseConfiguration(parser.evalNode("/configuration")); return configuration; } // org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration private void parseConfiguration(XNode root) { try { 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); 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); } }
parseConfiguration 调用了一些方法来解析 configuration 标签的各个子标签,这里分析下插件和映射文件怎么解析的
插件解析
插件配置在全局配置文件中,配置如下
<!-- mybatis-config.xml --> <plugins> <plugin interceptor="org.mybatis.example.ExamplePlugin"> <property name="someProperty" value="100"/> </plugin> </plugins>
解析源码
// org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement private void pluginElement(XNode parent) throws Exception { if (parent != null) { // 遍历所有插件,也就是 plugin 标签 for (XNode child : parent.getChildren()) { // 拿到 plugin 标签的 interceptor 属性 String interceptor = child.getStringAttribute("interceptor"); // 插件的属性配置,比如上面的 `<property name="someProperty" value="100"/>` Properties properties = child.getChildrenAsProperties(); // 反射创建一个拦截器 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); // 拿到的属性设置到拦截器中 interceptorInstance.setProperties(properties); // 设置到 configuration 属性中 configuration.addInterceptor(interceptorInstance); } } } // org.apache.ibatis.session.Configuration#addInterceptor public void addInterceptor(Interceptor interceptor) { // 添加到连接器链中 interceptorChain.addInterceptor(interceptor); }
总结一下,遍历所有插件,一个插件就创建一个拦截器,把所有拦截器保存到 configuration.interceptorChain 属性中
插件就是拦截器的应用,和 mvc 的 handlerChain 一样也是使用了包装设计模式。这里只分析初始化过程,插件的应用后面专门讲
映射文件解析
多种方式配置映射文件,解析的时候也会有多种
<!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers> <!-- 使用完全限定资源定位符(URL) --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper url="file:///var/mappers/BlogMapper.xml"/> <mapper url="file:///var/mappers/PostMapper.xml"/> </mappers> <!-- 使用映射器接口实现类的完全限定类名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers> <!-- 将包内的映射器接口全部注册为映射器 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
解析源码
// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement private void mapperElement(XNode parent) throws Exception { if (parent != null) { // 遍历所有子节点 for (XNode child : parent.getChildren()) { // 如果子节点是 package 标签 if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { // 如果不是 package 标签,那就只能是 mapper 标签了 // 如果是 mapper 标签,可以配置 resource、url、class(这里感觉可以优化,因为只会三选一) String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { // 解析 resource ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); // 和全局配置文件类似,这里构建一个 XMLMapperBuilder XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // 通过 XMLMapperBuilder 的 parser 方法完成解析 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 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."); } } } } } // org.apache.ibatis.builder.xml.XMLMapperBuilder#parse // XMLMapperBuilder 怎么构建就不写了,与 全局配置文件的 XMLConfigurationBuilder 类似,这里看下 parser 方法 public void parse() { if (!configuration.isResourceLoaded(resource)) { // 解析 mapper 映射文件 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); } // org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement 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")); // 解析 cache-ref cacheElement(context.evalNode("cache")); // 解析 cache parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 入参 resultMapElements(context.evalNodes("/mapper/resultMap")); // 返回对象 sqlElement(context.evalNodes("/mapper/sql")); // 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); } } // org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } // org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext 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); } } } // org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } // 解析<select|update|delete|insert>标签属性 Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); // 获取LanguageDriver对象 String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // 获取 Mapper 返回结果类型 Class 对象 Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); // 默认 Statement 类型为 PREPARED StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); 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> 标签内容,替换为 <sql> 标签定义的 SQL 片段 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // 解析 <selectKey> 标签 processSelectKeyNodes(id, parameterTypeClass, langDriver); // 通过 LanguageDriver 解析 SQL 内容,生成 SqlSource 对象 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); 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; } // 构建 MappedStatement 并加入 configuration builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } // org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement public MappedStatement addMappedStatement( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } // id 改为 namespace + id id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 构建 MappedStatement,建造者模式 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } // MappedStatement 构建完成 MappedStatement statement = statementBuilder.build(); // 加入 configuration configuration.addMappedStatement(statement); return statement; } // org.apache.ibatis.session.Configuration#addMappedStatement public void addMappedStatement(MappedStatement ms) { // 终于维护完成,真 tn 长 mappedStatements.put(ms.getId(), ms); }
需要注意:并没有注册 MapperRegistry,因为这是传统的 mybatis 的初始化过程,spring + mybatis 的项目就会注册 MapperRegistry,后面专门讲(其实底层还是一样的,多了一步扫描 mapper 接口的步骤而已)
-