前言
初始化阶段主要是完成 XML 配置文件和注解配置信息的读取,创建全局单例的 Configuration 配置对象,完成各部分的初始化工作,最底层封装的 XPath 进行解析,具体的创建过程需要三个核心类来完成解析。
核心类
创建 Configuration 的核心类和作用如下:
类名 | 作用 |
XmlConfigBuilder | 加载解析主配置文件 |
XmlMapperBuilder | 加载解析 Mapper 映射文件中非 SQL 节点部分 |
XmlStatementBuilder | 加载解析 Mapper 映射文件中 SQL 节点部分 |
三个类共同完成全局配置文件的加载解析,来构建 Configuration 配置对象。这三个类看起来并没有使用到建造者模式的流式风格,但是借鉴了建造者模式的思想,在 CacheBuilder 里面的 build 方法是典型的建造者模式加载核心配置文件来创建全局的配置对象。
其他的核心类如下:
类名 | 作用 |
Configuration | 单例对象,存在于程序的整个生命周期,包含所有的配置信息(初始化核心就是创建该对象) |
MapperRegistry | Mapper 接口动态代理的注册中心,注册的就是 Java 接口 |
MapperProxy | 动态代理类,实现了 InvocationHandler 接口,用于为接口生成动态代理实例,实例就是 MapperProxy 类型的。 |
MapperProxyFactory | 用于生成 MapperProxy 实例 |
ResultMap | 解析映射配置文件中的 resultMap 节点,内部包含一个 ResultMapping 类型的 List,里面就封装了 id、result 等子元素 |
MappedStatement | 存储映射文件中的 insert、select 等 SQL 语句 |
SqlSource | 映射文件中的 SQL 语句会解析成 SqlSource 对象,解析 SqlSource 后得到的 SQL 语句只包含占位符,可以直接交给 DB 执行 |
Configuration
包含 MyBatis 全部的配置信息、全局单例、生命周期贯穿整个 MyBatis 的生命周期,应用级生命周期。
public class Configuration { // 环境信息 protected Environment environment; // 是否启用行内嵌套语句 protected boolean safeRowBoundsEnabled = false; protected boolean safeResultHandlerEnabled = true; // 是否启用下划线转驼峰命名的属性 protected boolean mapUnderscoreToCamelCase = false; // 延迟加载 protected boolean aggressiveLazyLoading = true; // 是否允许单条 SQL 返回多个数据集(取决于驱动的兼容性)default:true protected boolean multipleResultSetsEnabled = true; // 允许 JDBC 生成主键。需要驱动器支持。如果设为了 true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。default:false protected boolean useGeneratedKeys = false; protected boolean useColumnLabel = true; // 是否开启二级缓存 protected boolean cacheEnabled = true; protected boolean callSettersOnNulls = false; protected boolean useActualParamName = true; // 日志打印所有的前缀 protected String logPrefix; protected Class<? extends Log> logImpl; protected Class<? extends VFS> vfsImpl; protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION; protected JdbcType jdbcTypeForNull = JdbcType.OTHER; // 设置触发延迟加载的方法 protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[]{"equals", "clone", "hashCode", "toString"})); protected Integer defaultStatementTimeout; protected Integer defaultFetchSize; // 执行器类型,有 simple、resue 及 batch protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; // 指定 MyBatis 应如何自动映射列到字段或属性 protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL; protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE; protected Properties variables = new Properties(); protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory(); // MyBatis 每次创建结果对象的新实例时,它都会使用对象工厂(ObjectFactory)去构建 POJO protected ObjectFactory objectFactory = new DefaultObjectFactory(); protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory(); // 延迟加载的全局开关 protected boolean lazyLoadingEnabled = false; // 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具 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; // Mapper 接口的动态代理注册中心 protected final MapperRegistry mapperRegistry = new MapperRegistry(this); protected final InterceptorChain interceptorChain = new InterceptorChain(); // TypeHandler 注册中心 protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(); // TypeAlias 别名注册中心 protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); // Mapper 文件中增删改查操作的注册中心 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection"); // Mapper 文件中配置 cache 节点的二级缓存 protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection"); // Mapper 文件中配置的所有 resultMap 对象,key 为命名空间 + ID protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection"); protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection"); // Mapper 文件中配置 KeyGenerator 的 insert 和 update 节点,key 为命名空间 + ID protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection"); // 加载到的所有 *Mapper.xml 文件 protected final Set<String> loadedResources = new HashSet<String>(); // Mapper 文件中配置的 SQL 元素,key 为命名空间 + ID protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers"); protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>(); protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>(); protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>(); protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>(); 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); } // 相关属性的 get/set 方法,和其他方法,省略 }
初始化过程
入口程序
public class DaoTest { private SqlSession sqlSession; @Before public void before() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream); sqlSession = sessionFactory.openSession(); inputStream.close(); } }
SqlSessionFactoryBuilder#build
public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); } // XMLConfigBuilder#parse 方法是配置解析的主要方法 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 构建 XMLConfigBuilder 对象 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 建造者模式,XMLConfigBuilder 对象的 parse 方法就可以构造 Configuration 对象,屏蔽了所有实现细节 // 并且将返回的 Configuration 对象作为参数构造 SqlSessionFactory 对象 // SqlSessionFactory 的默认实现类 DefaultSqlSessionFactory 拿到了配置对象之后,就具备生产 SqlSession 的能力了 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } // DefaultSqlSessionFactory 是 SqlSessionFactory 的默认实现 public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
XMLConfigBuilder#parse
XMLConfigBuilder#parse 方法是配置解析的核心方法。
public Configuration parse() { // 1.判断是否已经解析过,不重复解析 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 2.读取主配置文件的 Configuration 节点下面的配置信息,parseConfiguration 方法完成解析的流程 parseConfiguration(parser.evalNode("/configuration")); return configuration; }
XMLConfigBuilder#parseConfiguration
XMLConfigBuilder#parseConfiguration 方法完成全部的配置解析主流程。解析核心配置文件的关键方法,读取节点的信息,并通过对应的方法去解析配置,解析到的配置全部会放在 Configuration 里面。
private void parseConfiguration(XNode root) { try { Properties settings = settingsAsPropertiess(root.evalNode("settings")); // 解析<properties>节点 propertiesElement(root.evalNode("properties")); // 解析<settings>节点 loadCustomVfs(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); // 解析<environments>节点 environmentsElement(root.evalNode("environments")); // 解析<databaseIdProvider>节点 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析<typeHandlers>节点 typeHandlerElement(root.evalNode("typeHandlers")); // 解析<mappers>节点,里面会使用XMLMapperBuilder mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
我们看到这个方法的解析流程都很类似,最后面引入了 XMLMapperBuilder 解析节点,我们在前面挑一个解析 typeAliases 的看看细节,其他的就不逐个分析了,然后看 XMLMapperBuilder 解析阶段的分析。
XMLConfigBuilder#typeAliasesElement
typeAliasesElement 解析别名节点,typeAliases 有两种配置方式,如下所示:
<typeAliases> <package name="world.xuewei.entity"/> <typeAlias alias="Product" type="world.xuewei.entity.Product"/> </typeAliases>
private void typeAliasesElement(XNode parent) { if (parent != null) { // 1. 非空才会处理,依次遍历所有节点 for (XNode child : parent.getChildren()) { // 2. 处理 package 类型配置 if ("package".equals(child.getName())) { // 2.1 获取包名 String typeAliasPackage = child.getStringAttribute("name"); // 2.2 注册包名,将包名放到 typeAliasRegistry 里面,里面拿到包名之后还会进一步处理 // 最后会放到 TypeAliasRegistry.TYPE_ALIASES 这个 Map 里面去 configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { // 3. 处理 typeAlias 类型配置 // 3.1 获取别名 String alias = child.getStringAttribute("alias"); // 3.2 获取类名 String type = child.getStringAttribute("type"); try { Class<?> clazz = Resources.classForName(type); // 3.3 下面的注册逻辑其实比前面注册包名要简单,注册包名要依次处理包下的类,也会调用 registerAlias 方法, // 这里直接处理类,别名没有配置也没关系,里面会生成一个 getSimpleName 或者根据 Alias 注解去取别名 if (alias == null) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { // 4.其他类型直接报错 throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } }
XMLConfigBuilder#mapperElement
XMLConfigBuilder#mapperElement 方法解析节点,这个方法包含解析 mapper 节点的主流程,但是解析的细节还看不到,解析的细节在后面的 XMLMapperBuilder#parse 里面,我们先通过这个方法看一下解析的主流程,mappers 有多种配置方式,如下所示:
<mappers> <!-- 直接映射到相应的 mapper 文件 --> <mapper resource="mybatis/mapper/EmployeeMapper.xml"/> <mapper url="xx"/> <mapper class="yy"/> <package name="world.xuewei.mapper"/> </mappers>
解析配置文件的 mappers 子节点,方法主要是实现一个大体框架,按照 resource->url->class 的优先级读取配置,具体的解析细节是依赖于 XMLMapperBuilder 来实现的, XMLMapperBuilder 通过 parse 方法屏蔽了细节,内部完成解析过程。
private void mapperElement(XNode parent) throws Exception { if (parent != null) { // 1. 节点非空,遍历子节点逐个处理,因为 mapperElement(root.evalNode("mappers")) 解析的 mappers 里面可能有多个标签 for (XNode child : parent.getChildren()) { // 1.1 处理 package 类型的配置 if ("package".equals(child.getName())) { // 1.2 按照包来添加,扫包之后默认会在包下找与 Java 接口名称相同的 mapper 映射文件,name 就是包名 String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { // 1.3 一个一个 Mapper.xml 文件的添加,resource、url 和 class 三者是互斥的,resource 优先级最高 String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { // 1.4 按照 resource 属性实例化 XMLMapperBuilder 来解析 xml 配置文件 ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // 1.5 解析配置,因为 XMLMapperBuilder 继承了 BaseBuilder,BaseBuilder 内部持有 Configuration 对象 // 因此 XMLMapperBuilder 解析之后直接把配置设置到 Configuration 对象 mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { // 1.6 按照 url 属性实例化 XMLMapperBuilder 来解析 xml 配置文件 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) { // 1.7 按照 class 属性实例化 XMLMapperBuilder 来解析 xml 配置文件 Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { // resource、url 和 class 三者是互斥的,配置了多个或者不配置都抛出异常 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
结合代码注释和配置文件的结构,流程还是比较清晰的,这里面会将 mappers 里面的节点信息全部注册到 Configuration 所持有的 MapperRegistry 对象里面去, MapperRegistry 是 Mapper 接口动态代理的注册中心,总而言之 Configuration 里包含所有的配置信息,最后一种情况使用的是 configuration.addMapper 方法,其实前面几种情况最后也是走的这个方法,这里我们看到了主体流程,下面我们开始看解析的具体过程分析。
XMLMapperBuilder#parse
XMLMapperBuilder#parse 进入了 XMLMapperBuilder 解析 mapper 节点的详细流程,最后把 mapper 节点信息保存到 Configuration 的 MapperRegistry 里面去,并且会对 Mapper.xml 映射文件的内存进行解析,映射文件的内容是非常复杂的,我们慢慢跟进看。
public void parse() { // 1. 首先判断是否已经加载过了,没有加载才继续加载 // loadedResources 是一个 set 集合,保存了已经加载的映射文件,如果一个配置在 mappers 里面写了两次,那么第二次就不加载了 if (!configuration.isResourceLoaded(resource)) { // 2. 处理 mapper 子节点 // 这里使用 XPathParser 来解析 xml 文件,XPathParser 在 XMLMapperBuilder 构造方法执行的时候就已经初始化好了 configurationElement(parser.evalNode("/mapper")); // 3. 将解析过的文件添加到已经解析过的 set 集合里面 configuration.addLoadedResource(resource); // 4. 注册 mapper 接口 bindMapperForNamespace(); } // 5. 处理解析失败的节点 // 把加载失败的节点重新加载一遍,因为这些节点可能在之前解析失败了,比如他们继承的节点还未加载导致 // 因此这里把失败的部分再加载一次,之前加载失败的节点会放在一个 map 里面 // 6. 处理解析失败的 ResultMap 节点 parsePendingResultMaps(); // 7. 处理解析失败的 CacheRef 节点 parsePendingChacheRefs(); // 8. 处理解析失败的 Sql 语句节点 parsePendingStatements(); }
XMLMapperBuilder#configurationElement
configurationElement 方法是解析 mapper 映射文件的主流程,我们可以看到每种类型节点的解析过程,注意第八步是解析 SQL 信息,我们前面说过,XMLMapperBuilder 解析 mapper 文件但是不解析 SQL 节点,SQL 节点是由 XmlStatementBuilder 来负责的,后面我们会看到 XmlStatementBuilder 的作用。
private void configurationElement(XNode context) { try { // 1. 获取 namespace 属性(对应 Java 接口的全路径名称)不能为空 String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } // 2. 把 namespace 属性交给建造助手 builderAssistant builderAssistant.setCurrentNamespace(namespace); // 3. 解析 cache-ref 节点 cacheRefElement(context.evalNode("cache-ref")); // 4. 重点:解析 cache 节点,和缓存相关 cacheElement(context.evalNode("cache")); // 5. 解析 parameterMap 节点(已废弃) parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 6. 重点:解析 resultMap 节点 resultMapElements(context.evalNodes("/mapper/resultMap")); // 7. 重点:解析 SQL 节点 sqlElement(context.evalNodes("/mapper/sql")); // 8. 重点:解析 SQL 语句,解析 select、insert、update、delete 节点 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { // 异常处理 throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } }
XMLMapperBuilder#bindMapperForNamespace
bindMapperForNamespace 负责将 mapper 映射文件对应的 Java 接口类(这个接口类的名称就是 mapper 文件里面的 namespace)注册到 Configuration 的 MapperRegistry 里面,这代表该接口已经注册。
private void bindMapperForNamespace() { // 1. 获取命名空间,在 bindMapperForNamespace 前面的 configurationElement 方法将 namespace 已经 set 到 builderAssistant 了,这里直接取出 String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { // 1.1 通过命名空间获取 Mapper 接口的 Class 对象 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { // ignore, bound type is not required } if (boundType != null) { // 1.2 是否已经注册过该 mapper 接口? 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 // 1.3 如果没有注册,将命名空间添加至 configuration.loadedResource 集合中 configuration.addLoadedResource("namespace:" + namespace); // 1.4 将 mapper 接口添加到 mapper 注册中心 configuration.addMapper(boundType); } } } }
XMLMapperBuilder#parsePendingResultMaps
处理之前加载失败的 resultMap。
private void parsePendingResultMaps() { // 1. 之前加载失败的 resultMap 会保存在这个集合中,这里需要做的就是遍历这个集合并处理 Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps(); synchronized (incompleteResultMaps) { Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator(); while (iter.hasNext()) { try { // 2. 遍历并且调用 resolve 处理即可,如果异常直接忽略,因为最后还会重试的 iter.next().resolve(); // 3. 处理完就从集合移除 iter.remove(); } catch (IncompleteElementException e) { // ResultMap is still missing a resource... } } } }
parsePendingChacheRefs(),parsePendingStatements() 和 parsePendingResultMaps 方法是类似的,就不多解析了,这一步我们主要是梳理 XMLMapperBuilder#parse 方法,并对里面几个关键的方法做了大概的跟踪,但是底层的实现逻辑我们暂时不跟进,否则内容太多。
XMLMapperBuilder#buildStatementFromContext
前面的 XMLMapperBuilder 我们跟了一部分源码,看到了 XMLMapperBuilder 解析 mapper 映射文件的主体流程,现在我们看 XmlStatementBuilder 是如何解析 mapper 中的 SQL 语句的,解析 SQL 语句属于解析 mapper 文件的一部分,我们 XMLMapperBuilder#configurationElement 的最后一个流程方法 buildStatementFromContext 里面跟进去,就可以看到他的身影,我们跟进去看看主流程。
buildStatementFromContext(context.evalNodes("select|insert|update|delete")); // 解析 SQL 语句节点并注册到 Configuration 对象 private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { // XMLStatementBuilder 和 XMLMapperBuilder 一样都是继承自 BaseBuilder,BaseBuilder 持有 Configuration 对象,因此 //直接解析,然后将配置信息 set 进去即可 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { // 如果解析异常,就把对象添加到未完成的 Map 集合里面 configuration.addIncompleteStatement(statementParser); } } }
XMLStatementBuilder#parseStatementNode
XMLStatementBuilder#parseStatementNode 是解析 SQL 节点的核心方法,该方法是解析 SQL 节点的主流程,包括四种 SQL 节点,解析完毕之后会将 SQL 语句信息封装之后,注册到 Configuration 对象里面的 mappedStatements 集合里面去。
public void parseStatementNode() { // 1. 获取 sql 节点的 id String id = context.getStringAttribute("id"); // 2. 获取 databaseId String databaseId = context.getStringAttribute("databaseId"); // 不符合就返回 if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } // 3. 获取 sql 节点的各种属性 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"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); // 4. 根据 sql 节点的名称获取操作的类型 SqlCommandType(INSERT, UPDATE, DELETE, SELECT) String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); // 5. 获取操作的各种配置,比如缓存,resultOrdered 之类的 boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 6. flushCache 默认值是 !isSelect,如果是查询语句,没有配置 flushCache 就是 false,不是查询语句,没有配置 flushCache 就是 true boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing // 7. 在解析 sql 语句之前先解析 <include> 节点 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. // 8. 在解析 sql 语句之前,处理 <selectKey> 子节点,并在 xml 节点中删除 processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) // 9. 解析 sql 语句是解析 mapper.xml 的核心,实例化 sqlSource,使用 sqlSource 封装 sql 语句 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); // 10. 获取 resultSets 属性 String resultSets = context.getStringAttribute("resultSets"); // 11. 获取主键信息 keyProperty String keyProperty = context.getStringAttribute("keyProperty"); // 12. 获取主键信息 keyColumn String keyColumn = context.getStringAttribute("keyColumn"); // 13. 根据 <selectKey> 获取对应的 SelectKeyGenerator 的 id KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); // 14. 获取 keyGenerator 对象,如果是 insert 类型的 sql 语句,会使用 KeyGenerator 接口获取数据库生产的 id; if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); } // 15. 通过 MapperBuilderAssistant 类型的 builderAssistant 实例化 MappedStatement,并注册至 configuration 对象 // 相当于自身主要复杂属性的解析和读取,构造对象的过程交给助手来做 // 注意前面解析了 mapper 节点之后是注册到 mapperRegistry,那是注册 mapper 节点信息,注册的实际上是对应的 java 接口 // 这里是注册到 MappedStatement,注册的是 sql 语句信息,但是二者都是保存在 Configuration 对象里面的Map集合 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
MapperBuilderAssistant#addMappedStatement
在 XMLStatementBuilder#parseStatementNode 的最后一步,通过调用 builderAssistant.addMappedStatement 方法来完成 MappedStatement 对象实例化和注册到 Configuration 对象的 mappedStatements 集合,sql 语句解析之后,对应到的 java 的数据结构类是 MappedStatement,它是保存 sql 语句的数据结构。sql 语句的节点的全部配置,都能够在 MappedStatement 中找到对应的属性。
到此 XmlConfigBuilder、XmlMapperBuilder 和 XmlStatementBuilder 三者分工协作,依次完成了主配置文件、mapper 映射文件(不含 sql 信息)和 mapper 文件 sql 节点信息配置的加载,最终都把这些信息放到了 Configuration 这个大配置对象里面去了,到此处虽然还有很多细节没有分析,但是基本上初始化的流程已经看得出个大概了。 下面是代码细节:
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) { // 1. 确保 Cache-ref 节点已经被解析过了 if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 2. 典型的建造者模式,创建 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); } // 3. build 方法创建 MappedStatement 对象 MappedStatement statement = statementBuilder.build(); // 4. 添加 MappedStatement 对象到全局 Configuration 配置对象的对应 Map 中 configuration.addMappedStatement(statement); return statement; }