MyBatis源码解读之配置
2018-06-20 19:33
309 查看
1. 目的
本文主要介绍MyBatis配置文件解析,通过源码解读mybatis-config.xml(官方默认命名)、Mapper.xml 与Java对象的映射。2. MyBatis结构
查看大图MyBatis结构图,原图实在太模糊了,所以重绘。
MyBatis结构图说明:
Configuration 对象初始化来源于 1个mybatis-config.xml 配置、多个Mapper注解类配置,mybatis-config.xml 包含了多个Mapper.xml配置
配置解析完成后 Configuration 包含多个Mapper对象,每一个Mapper对象包含多个MappedStatement对象
MappedStatement对象包含要执行的SQL语句、SQL操作类型,参数类型等信息
MapperdStatement接收执行参数,执行数据库调用,返回结果对象
3. Configuration 功能介绍
Configuration 是 MyBatis 的核心配置对象,是一个贯穿全剧的对象。3.1 mybatis-config.xml 配置介绍
Configuration 与 mybatis-config.xml 文件内容一一对应,XML的配置影响着MyBatis行为。下面具体看下mybatis-config.xml 结构
3.2 源码解析
mybatis-config.xml 对应解析类为 XMLConfigBuilderConfiguration 解析入口:
/** * #mark 配置解析入口 #20170821 * http://www.mybatis.org/mybatis-3/zh/configuration.html * @return */ public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }
<configuration> 子元素解析:
private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); //settings转化为properties变量 Properties settings = settingsAsProperties(root.evalNode("settings")); //指定VFS的实现 loadCustomVfs(settings); //解析对象别名 typeAliasesElement(root.evalNode("typeAliases")); //插件 pluginElement(root.evalNode("plugins")); //对象工厂(objectFactory) objectFactoryElement(root.evalNode("objectFactory")); //对象包装工厂 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); //反射工厂 reflectorFactoryElement(root.evalNode("reflectorFactory")); //设置settings settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 //配置环境(environments) environmentsElement(root.evalNode("environments")); //数据库多厂商配置 databaseIdProviderElement(root.evalNode("databaseIdProvider")); //类型处理器配置 typeHandlerElement(root.evalNode("typeHandlers")); //映射配置 mapper配置文件解析、注解解析 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
4. Mapper.xml配置介绍
Mapper.xml是SQL映射语句配置Mapper.xml是配置在configuration -> mappers
4.1 Mapper.xml结构
4.2 源码解析
Mapper.xml 解析有2种方式:XML解析方式 对应 XMLMapperBuilder
<mappers> <mapper resource="org/apache/ibatis/builder/BlogMapper.xml"/> <mapper url="file:./src/test/java/org/apache/ibatis/builder/NestedBlogMapper.xml"/> </mappers>
注解方式解析 对应 MapperAnnotationBuilder
<mappers> <mapper class="org.apache.ibatis.builder.CachedAuthorMapper"/> <!-- 包自动检索 --> <package name="org.apache.ibatis.builder.mapper"/> </mappers>
mapper 解析入口:
//映射配置 mapper配置文件解析、注解解析 mapperElement(root.evalNode("mappers")); /** * http://www.mybatis.org/mybatis-3/zh/configuration.html#mappers * @param parent * @throws Exception */ private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { //包自动检索,注解式Mapper解析 String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { // classpath 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); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { 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) { //注解式Mapper解析 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."); } } } } }
4.3 XMLMapperBuilder 源码解析
/** * 解析 mapper * http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html */ public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } //解析未完成的,由于解析异常未找到对应资源 parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); } /** * 解析 mapper 子元素 * @param context */ 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")); //sql片段解析,公用SQL 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); } }
在Mapper.xml配置中有个非常重要元素,那就是ResultMap。
官方文档定义:
resultMap 元素是 MyBatis 中最重要最强大的元素。它就是让你远离 90%的需要从结果 集中取出数据的 JDBC 代码的那个东西, 而且在一些情形下允许你做一些 JDBC 不支持的事 情。
可能resultMap很多配置参数都没在实际中使用,需要了解还是去查阅官方文档。
ResultMap 解析入口
/** * http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html#Result_Maps * resultMap 元素是 MyBatis 中最重要最强大的元素。 * @param list * @throws Exception */ private void resultMapElements(List<XNode> list) throws Exception { for (XNode resultMapNode : list) { try { resultMapElement(resultMapNode); } catch (IncompleteElementException e) { // ignore, it will be retried } } } private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); //获取ResultMap类型 String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); //继承ResultMap String extend = resultMapNode.getStringAttribute("extends"); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); //解析type Class<?> typeClass = resolveClass(type); Discriminator discriminator = null; // List<ResultMapping> resultMappings = new ArrayList<ResultMapping>(); resultMappings.addAll(additionalResultMappings); List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { if ("constructor".equals(resultChild.getName())) { //解析构造方法配置 processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { //解析鉴别器配置 discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { List<ResultFlag> flags = new ArrayList<ResultFlag>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } } /** * 解析嵌套 ResultMap * @param context * @param resultMappings * @return * @throws Exception */ private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception { // association 关联对象 collection 集合对象 case(discriminator子元素) 按条件构建对象 if ("association".equals(context.getName()) || "collection".equals(context.getName()) || "case".equals(context.getName())) { if (context.getStringAttribute("select") == null) { ResultMap resultMap = resultMapElement(context, resultMappings); return resultMap.getId(); } } return null; } /** * SQL 映射语句解析 * @param list * @param requiredDatabaseId */ 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); } } }
4.4 XMLStatementBuilder 源码解析
XMLStatementBuilder 主要用于解析添删改查语句映射public void parseStatementNode() { String id = context.getStringAttribute("id"); //当前的databaseId String databaseId = context.getStringAttribute("databaseId"); //与全局的databaseId不匹配,不解析返回 if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } 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"); //SQL解析驱动 String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); //返回值类型 Class<?> resultTypeClass = resolveClass(resultType); //结果集类型,默认值为 unset (依赖驱动) String resultSetType = context.getStringAttribute("resultSetType"); //SQL语句类型,默认值: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); //resultOrdered设置仅针对嵌套结果 select 语句适用 boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing //解析include XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) #mark 解析动态SQL SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); //多结果集 String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; //主键生成语句id 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); }
4.5 MapperAnnotationBuilder源码解析
/** * #mark 注解Mapper方式构建 * @author Clinton Begin */ public class MapperAnnotationBuilder { /** SQL注解类型 */ private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>(); /** SQL Provider 注解类型 */ private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>(); private final Configuration configuration; private final MapperBuilderAssistant assistant; private final Class<?> type; /** * 构造 * @param configuration 配置对象 * @param type Mapper 类型 */ public MapperAnnotationBuilder(Configuration configuration, Class<?> type) { //记录解析资源文件,用于记录到上下文日志 String resource = type.getName().replace('.', '/') + ".java (best guess)"; //Mapper构建工具类 this.assistant = new MapperBuilderAssistant(configuration, resource); this.configuration = configuration; this.type = type; //初始化注解 sqlAnnotationTypes.add(Select.class); sqlAnnotationTypes.add(Insert.class); sqlAnnotationTypes.add(Update.class); sqlAnnotationTypes.add(Delete.class); sqlProviderAnnotationTypes.add(SelectProvider.class); sqlProviderAnnotationTypes.add(InsertProvider.class); sqlProviderAnnotationTypes.add(UpdateProvider.class); sqlProviderAnnotationTypes.add(DeleteProvider.class); } /** * 注解解析 */ public void parse() { String resource = type.toString(); //资源是否已经加载 if (!configuration.isResourceLoaded(resource)) { //加载Xml资源文件 loadXmlResource(); //设置到已加载资源 configuration.addLoadedResource(resource); //设置当前命名空间 assistant.setCurrentNamespace(type.getName()); //解析缓存 parseCache(); //解析缓存引用 parseCacheRef(); Method[] methods = type.getMethods(); for (Method method : methods) { try { // issue #237 非桥接方法 if (!method.isBridge()) { //解析语句映射注解 parseStatement(method); } } catch (IncompleteElementException e) { //异常暂存未完配置 configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } //解析未完成的配置 parsePendingMethods(); } //省略其他 }
4.6 XML 解析 和 注解解析关联
实际上我们在使用MyBatis的时候,配置文件和注解式同时使用的,具体来看看源码实现:XMLMapperBuilder,通过获取 Mapper 配置的 namespace , 通过 namespace 反射获取到对应的 Mapper Interface , 然后在使用注解解析。
/** * 从命名空间绑定Mapper */ private void bindMapperForNamespace() { String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { //通过命名空间找到对应的Mapper Interface boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //ignore, bound type is not required } if (boundType != null) { if (!configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); //mapper添加到 Configuration , 内部包含注解解析 configuration.addMapper(boundType); } } } }
MapperAnnotationBuilder : 通过获取 Mapper Interface 类路径,将路径转换成xml路径 , 然后在使用Xml解析
/** * 加载Xml配置文件 */ private void loadXmlResource() { // Spring may not know the real resource name so we check a flag // to prevent loading again a resource twice // this flag is set at XMLMapperBuilder#bindMapperForNamespace if (!configuration.isResourceLoaded("namespace:" + type.getName())) { //将 Mapper Interface 路径转换为 xml 路径 String xmlResource = type.getName().replace('.', '/') + ".xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); } catch (IOException e) { // ignore, resource is not required } if (inputStream != null) { //解析Xml配置Mapper XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); xmlParser.parse(); } } }
5. 参考资料
MyBatis官方文档 - http://www.mybatis.org/mybatis-3/zh/index.htmlMyBatis-Spring官方文档 - http://www.mybatis.org/spring/zh/index.html
MyBatis源码 - https://gitee.com/rainwen/mybatis
MyBatis-Spring源码 - https://github.com/rainwen/spring
关于MyBatis源码解读之配置就介绍到这里。如有疑问,欢迎留言,谢谢。
相关文章推荐
- Mybatis如何加载配置文件 源码解读parameterType
- mybatis源码解读(四)——事务的配置
- struct2源码解读(4)之配置文件具体解析过程
- MyBatis源码——解析MyBatis配置文件
- 源码解读Mybatis List列表In查询实现的注意事项
- Mybatis 源码分析--Configuration.xml配置文件加载到内存
- MyBatis 源码分析——配置信息
- Tomcat源码解读系列(一)——server.xml文件的配置
- HttpClient 4.3连接池参数配置及源码解读
- 【MyBatis源码分析】properties,typeAliases解析属性配置元素详述
- 阿里大牛为你进行源码级别解读 mybatis 插件
- 源码级别解读 mybatis 插件
- 源码解读Mybatis List列表In查询实现的注意事项
- [置顶] Mybatis技术(四) 从配置读取到打开连接的源码分析
- mybatis--源码解读---XML的解析
- Mybatis3源码分析(20)-Mapper实现-配置加载
- 阿里大牛为你进行源码级别解读 mybatis 插件
- 源码级别解读 mybatis 插件
- MyBatis源码解读(4)——SqlSession(上)
- mybatis源码中的设计模式解读