文章目录
- 1. 相关代码
- 2. 加载资源
- 3. 创建SqlSessionFactory
- 3.1 整体创建过程
- 3.2. 创建XMLConfigBuilder.
- 3.2.1 XMLConfigBuilder 的核心字段
- 3.2.2 BaseBuilder
- 3.3 创建Configuration
- 3.4 全局配置文件解析过程
- 3.4.1 parseConfiguration方法
- 3.4.2 properties标签解析
- 3.4.3 settings标签解析
- 3.4.4 typeAliases解析
- 3.4.5 plugins解析
- 3.4.6 objectFactory,objectWrapperFactory及reflectorFactory解析
- 3.4.7 settings 子标签赋值
- 3.4.8 environments标签
- 3.4.9 databaseIdProvider
- 3.4.10 typeHandlers标签解析
- 3.4.11 mapper解析
- 3.5 映射文件解析
- 3.5.1 configurationElement()——解析所有的子标签,最终获得MappedStatement对象。
- 3.5.2 bindMapperForNamespace(); // 注册 Mapper 接口
- 4. 总结
1. 相关代码
@Testpublic void test2() throws Exception{// 1.获取配置文件InputStream in = Resources.getResourceAsStream("mybatis-config.xml");// 2.加载解析配置文件并获取SqlSessionFactory对象SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);// 3.根据SqlSessionFactory对象获取SqlSession对象SqlSession sqlSession = factory.openSession();// 4.通过SqlSession中提供的 API方法来操作数据库UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.selectUserById(1);System.out.println(user);// 5.关闭会话sqlSession.close();}
2. 加载资源
Mybatis有自己的资源加载工具Resources,从classpath加载mybatis-config.xml配置文件。
3. 创建SqlSessionFactory
3.1 整体创建过程
SqlSessionFactoryBuilder从名称末尾的Builder可知,这是一个运用建造者模式的类,专门用来创建SqlSessionFactory。SqlSessionFactoryBuilder只是用来创建SqlSessionFactory,完成创建后就可以销毁。
进入build(in)方法查看:
public SqlSessionFactory build(InputStream inputStream) {// test 6666 --> 999 0000return build(inputStream, null, null);}
这里重载了build方法:
/*** 1.创建了一个XMLConfigBuilder对象 会完成很多初始化操作 最终的是创建了 Configuration 对象* 2.parser.parse() 完成全局配置文件的加载解析 并将相关的信息封装到 Configuration 对象中* 同时会完成映射文件(UserMapper.xml)的加载解析,相关的信息同样的会被保存到 Configuration对象中* select/insert/udpate/delete 标签的信息会被封装到 MapperdStatement对象中* 3.build(parser.parse()) ==》直接创建了 DefaultSqlSessionFactory对象*** 1.创建了一个解析器* 2.通过解析器解析全局配置文件* 3.通过得到的Configuration对象来创建DefaultSqlSessionFactory实例* @param inputStream* @param environment* @param properties* @return*/public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {// 用于解析 mybatis-config.xml,同时创建了 Configuration 对象 >> ok 完成了很多的初始化操作XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);// 解析XML,最终返回一个 DefaultSqlSessionFactory >>// 全局配置文件中的信息都被封装到了 Configuration对象中// 映射文件中的 配置信息 同样的也被封装到了 Configuration 对象中// 一个具体的 CRUD 标签的信息 被封装到 MappedStatment 对象中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.}}}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}
总结:XMLConfigBuilder对象完成全局配置文件和映射文件的解析,得到Configuration对象,Configuration作为入参创建DefaultSqlSessionFactory对象。
3.2. 创建XMLConfigBuilder.
从XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties)按F7进入。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {// EntityResolver的实现类是XMLMapperEntityResolver 来完成配置文件的校验,根据对应的DTD文件来实现this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}
然后重载构造函数,这个函数是private,非常有意思的设计,其他构造函数最终都是调用这个函数。
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {super(new Configuration()); // 完成了Configuration的初始化 类型别名的注册ErrorContext.instance().resource("SQL Mapper Configuration");this.configuration.setVariables(props); // 设置对应的Properties属性this.parsed = false; // 设置 是否解析的标志为 falsethis.environment = environment; // 初始化environmentthis.parser = parser; // 初始化 解析器}
3.2.1 XMLConfigBuilder 的核心字段
// 标识是否解析过mybatis-config.xml文件private boolean parsed;// 用于解析mybatis-config.xml 配置文件的 XPathParser对象private final XPathParser parser;// 标签定义的环境名称private String environment;// 心功能是实现对 Reflector 对象的创建和缓存private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
3.2.2 BaseBuilder
super(new Configuration())
XMLConfigBuilder继承了抽象类BaseBuilder,这里super就是调用父类的构造函数。F7跟入:
// Configuration 是MyBatis初始化过程的核心对象,MyBatis中的几乎全部配置信息会保存在 Configuration 对象中protected final Configuration configuration;// 别名的注册器protected final TypeAliasRegistry typeAliasRegistry;// 类型处理器的注册器protected final TypeHandlerRegistry typeHandlerRegistry;public BaseBuilder(Configuration configuration) {this.configuration = configuration;this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();}
BaseBuilder持有三个成员变量:
- configuration,配置核心对象,MyBatis 的初始化过程就是围绕 Configuration 对象展开的,我们可以认为 Configuration 是一个单例对象,MyBatis 初始化解析到的全部配置信息都会记录到 Configuration 对象中。
- typeAliasRegistry,别名注册器
- typeHandlerRegistry,类型注册器。
3.3 创建Configuration
super(new Configuration()); // 完成了Configuration的初始化 类型别名的注册
Configuration持有很多变量,其创建过程值得探究。
3.4 全局配置文件解析过程
parser.parse()
public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;// XPathParser,dom 和 SAX 都有用到 >>parseConfiguration(parser.evalNode("/configuration"));return configuration;}
3.4.1 parseConfiguration方法
/*** 全局配置文件中的配置信息都被加载到 Configuration 对象中* @param root*/private void parseConfiguration(XNode root) {try {//issue #117 read properties first// 对于全局配置文件各种标签的解析 都被保存到了 Configuration 对象中。propertiesElement(root.evalNode("properties"));// 解析 settings 标签 返回的结果就是 settings 中定义的信息 自定义的属性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"));// settings 子标签赋值,默认值就是在这里提供的 >> 加载默认设置settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631// 创建了数据源 >>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);}}
安装顺序解析标签:
- properties
- settings
- typeAliases
- typeHandlers
- objectFactory
- plugins
- environments
- environment
- transactionManager
- dataSource
- environment
- databaseIdProvider
- mappers
3.4.2 properties标签解析
/*** 加载解析 properties 标签 并且将相关的属性信息更新保存到了 Configuration 对象中了* @param context* @throws Exception*/private void propertiesElement(XNode context) throws Exception {if (context != null) {// 创建了一个 Properties 对象,后面可以用到Properties defaults = context.getChildrenAsProperties();String resource = context.getStringAttribute("resource");String url = context.getStringAttribute("url");if (resource != null && url != null) {// url 和 resource 不能同时存在throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");}// 加载resource或者url属性中指定的 properties 文件if (resource != null) {defaults.putAll(Resources.getResourceAsProperties(resource));} else if (url != null) {defaults.putAll(Resources.getUrlAsProperties(url));}// 配置文件中 已经有的 属性信息Properties vars = configuration.getVariables();if (vars != null) {// 和 Configuration中的 variables 属性合并defaults.putAll(vars);}// 更新对应的属性信息parser.setVariables(defaults);// 解析出来的 属性信息 被保存到了 configuration 对象中configuration.setVariables(defaults);}}
3.4.3 settings标签解析
/*** 将 sttings 标签中配置的 信息封装到了 Properties 对象中,并且返回了* @param context* @return*/private Properties settingsAsProperties(XNode context) {if (context == null) {return new Properties();}// 获取settings节点下的所有的子节点Properties props = context.getChildrenAsProperties();// Check that all settings are known to the configuration class 用户到了 反射工具箱 中的内容 实现反射处理// 创建Configuration对应的MetaClass对象MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);for (Object key : props.keySet()) {// 检验配置的拼写是否准确,是否有setter方法if (!metaConfig.hasSetter(String.valueOf(key))) {throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");}}return props;}
- 读取文件
// loadCustomVfs是获取Vitual File System的自定义实现类,比如要读取本地文件,// 或者FTP远程文件的时候,就可以用到自定义的VFS类。 (用到的机会很少)loadCustomVfs(settings);
- 日志设置
loadCustomLogImpl(settings);
private void loadCustomLogImpl(Properties props) {// 获取 logImpl设置的 日志 类型Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));// 设置日志 这块代码是我们后面分析 日志 模块的 关键代码configuration.setLogImpl(logImpl);}
3.4.4 typeAliases解析
typeAliasesElement(root.evalNode("typeAliases"));
private void typeAliasesElement(XNode parent) {// 放入 TypeAliasRegistryif (parent != null) {for (XNode child : parent.getChildren()) {// 解析包下面的类,默认别名的类名小写,如果类上有@Alias,就取注释的valueif ("package".equals(child.getName())) {String typeAliasPackage = child.getStringAttribute("name");configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);} else {String alias = child.getStringAttribute("alias");String type = child.getStringAttribute("type");try {Class<?> clazz = Resources.classForName(type);if (alias == null) {// 扫描 @Alias 注解使用typeAliasRegistry.registerAlias(clazz);} else {// 直接注册typeAliasRegistry.registerAlias(alias, clazz);}} catch (ClassNotFoundException e) {throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);}}}}}
3.4.5 plugins解析
pluginElement(root.evalNode("plugins"));
private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {// 获取<plugin> 节点的 interceptor 属性的值String interceptor = child.getStringAttribute("interceptor");// 获取<plugin> 下的所有的properties子节点Properties properties = child.getChildrenAsProperties();// 初始化 Interceptor 对象Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();// 设置 interceptor的 属性interceptorInstance.setProperties(properties);// 将Interceptor对象添加到Configuration的插件链中保存,等待后续使用configuration.addInterceptor(interceptorInstance);}}}
3.4.6 objectFactory,objectWrapperFactory及reflectorFactory解析
- objectFactory
加载自定义的ObjectFactory。ObjectFactory用来创建返回的对象。
private void objectFactoryElement(XNode context) throws Exception {if (context != null) {// 获取<objectFactory> 节点的 type 属性String type = context.getStringAttribute("type");// 获取 <objectFactory> 节点下的配置信息Properties properties = context.getChildrenAsProperties();// 获取ObjectFactory 对象的对象 通过反射方式ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();// ObjectFactory 和 对应的属性信息关联factory.setProperties(properties);// 将创建的ObjectFactory对象绑定到Configuration中configuration.setObjectFactory(factory);}}
- objectWrapperFactory
ObjectWrapperFactory用来对对象做特殊的处理。比如:select没有写别名,查询返回的是一个Map,可以在自定义的objectWrapperFactory中把下划线命名变成驼峰命名。
private void objectWrapperFactoryElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();configuration.setObjectWrapperFactory(factory);}}
- reflectorFactory
ReflectorFactory是反射的工具箱,对反射的操作进行了封装。
private void reflectorFactoryElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();configuration.setReflectorFactory(factory);}}
3.4.7 settings 子标签赋值
// settings 中的属性信息都是直接保存在 Configuration 对象的属性中的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")));}
3.4.8 environments标签
private void environmentsElement(XNode context) throws Exception {if (context != null) {if (environment == null) {environment = context.getStringAttribute("default");}for (XNode child : context.getChildren()) {String id = child.getStringAttribute("id");if (isSpecifiedEnvironment(id)) {// 事务工厂TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));// 数据源工厂(例如 DruidDataSourceFactory )DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));// 数据源DataSource dataSource = dsFactory.getDataSource();// 包含了 事务工厂和数据源的 EnvironmentEnvironment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);// 放入 Configuration Environment 存储了 DataSourceFactory TransactionFactoryconfiguration.setEnvironment(environmentBuilder.build());}}}}
3.4.9 databaseIdProvider
解析databaseIdProvider标签,生成DatabaseIdProvider对象(用来支持不同厂商的数据库)。
在 MyBatis 中编写的都是原生的 SQL 语句,而很多数据库产品都会有一些 SQL 方言,这些方言与标准 SQL 不兼容。
在 mybatis-config.xml 配置文件中,我们可以通过 标签定义需要支持的全部数据库的 DatabaseId,在后续编写 Mapper 映射配置文件的时候,就可以为同一个业务场景定义不同的 SQL 语句(带有不同的 DataSourceId),来支持不同的数据库,这里就是靠 DatabaseId 来确定哪个 SQL 语句支持哪个数据库的。
3.4.10 typeHandlers标签解析
typeHandlerElement(root.evalNode("typeHandlers"));
private void typeHandlerElement(XNode parent) {if (parent != null) {for (XNode child : parent.getChildren()) { // 处理全部<typeHandler>子标签if ("package".equals(child.getName())) { // 如果指定了package属性,则扫描指定包中所有的类,// 并解析@MappedTypes注解,完成TypeHandler的注册String typeHandlerPackage = child.getStringAttribute("name");typeHandlerRegistry.register(typeHandlerPackage);} else {// 如果没有指定package属性,则尝试获取javaType、jdbcType、handler三个属性String javaTypeName = child.getStringAttribute("javaType");String jdbcTypeName = child.getStringAttribute("jdbcType");String handlerTypeName = child.getStringAttribute("handler");// 根据属性确定TypeHandler类型以及它能够处理的数据库类型和Java类型Class<?> javaTypeClass = resolveClass(javaTypeName);JdbcType jdbcType = resolveJdbcType(jdbcTypeName);Class<?> typeHandlerClass = resolveClass(handlerTypeName);// 调用TypeHandlerRegistry.register()方法注册TypeHandlerif (javaTypeClass != null) {if (jdbcType == null) {typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);} else {typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);}} else {typeHandlerRegistry.register(typeHandlerClass);}}}}}
3.4.11 mapper解析
private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) { // 循环遍历 <mappers> 标签的所有的子节点// 不同的定义方式的扫描,最终都是调用 addMapper()方法(添加到 MapperRegistry)。这个方法和 getMapper() 对应// package 包if ("package".equals(child.getName())) { // <package>String mapperPackage = child.getStringAttribute("name");// 扫描指定的包,并向MapperRegistry注册Mapper接口// 每一个类型 创建一个对应的 MapperProxyFactory 对象configuration.addMappers(mapperPackage);} else {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 解析映射文件 XMLConfigurationBuilderXMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());// 解析 Mapper.xml,总体上做了两件事情 >>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.");}}}}}
3.5 映射文件解析
mapperParser.parse();
public void parse() {// 总体上做了两件事情,对于语句的注册和接口的注册// 判断是否已经加载过了 映射文件if (!configuration.isResourceLoaded(resource)) {// 1、具体增删改查标签的解析。// 一个标签一个MappedStatement。 >>configurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);// 2、把namespace(接口类型)和工厂类绑定起来,放到一个map。// 一个namespace 一个 MapperProxyFactory >>bindMapperForNamespace(); // 注册 Mapper 接口}// 处理 configurationElement 方法中解析失败的 <resultMap> 节点parsePendingResultMaps();// 处理 configurationElement 方法中解析失败的 <cache-ref> 节点parsePendingCacheRefs();// 处理 configurationElement 方法中解析失败的 SQL 语句节点parsePendingStatements();}
3.5.1 configurationElement()——解析所有的子标签,最终获得MappedStatement对象。
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);// 添加缓存对象 如果我们希望多个 namespace 共用同一个二级缓存 就可以使用cacheRefElement(context.evalNode("cache-ref"));// 解析 cache 属性,添加缓存对象cacheElement(context.evalNode("cache"));// 创建 ParameterMapping 对象 以废弃 不推荐parameterMapElement(context.evalNodes("/mapper/parameterMap"));// 创建 List<ResultMapping>resultMapElements(context.evalNodes("/mapper/resultMap"));// 解析可以复用的SQL includsqlElement(context.evalNodes("/mapper/sql"));// 解析增删改查标签,得到 MappedStatement >> 一个 CRUD标签中的信息都被封装到了 MappedStatement 对象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);}}
在buildStatementFromContext()方法中,创建了用来解析增删改查标签的XMLStatementBuilder,并且把创建的MappedStatement添加到mappedStatements中。
private void buildStatementFromContext(List<XNode> list) {// 多数据库if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}// 解析 Statement >>buildStatementFromContext(list, null);}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {// 用来解析增删改查标签的 XMLStatementBuilderfinal XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {// 解析 Statement,添加 MappedStatement 对象 >>// 解析具体的 insert/update/delete/selectstatementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}}
关键步骤:
MappedStatement statement = statementBuilder.build();// 最关键的一步,在 Configuration 添加了 MappedStatement >> 对应的就是一个 CRUD 标签configuration.addMappedStatement(statement);return statement;
3.5.2 bindMapperForNamespace(); // 注册 Mapper 接口
private void bindMapperForNamespace() {// 约定 namespace 要和对应的接口的全类路径名称保持一致String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {// 根据名称空间加载对应的接口类型boundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {//ignore, bound type is not required}if (boundType != null) {// 判断 在MapperRegistry中是否注册的有当前类型的 MapperProxyFactory对象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#loadXmlResourceconfiguration.addLoadedResource("namespace:" + namespace);// 添加到 MapperRegistry,本质是一个 map,里面也有 Configuration >>configuration.addMapper(boundType);}}}}
通过源码分析发现主要是是调用了addMapper()。addMapper()方法中,把接口类型注册到MapperRegistry中:
实际上是为接口创建一个对应的MapperProxyFactory(用于为这个type提供工厂类,创建MapperProxy)。
public <T> void addMapper(Class<T> type) {if (type.isInterface()) { // 检测 type 是否为接口if (hasMapper(type)) { // 检测是否已经加装过该接口throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {// !Map<Class<?>, MapperProxyFactory<?>> 存放的是接口类型,和对应的工厂类的关系knownMappers.put(type, new MapperProxyFactory<>(type));// It's important that the type is added before the parser is run// otherwise the binding may automatically be attempted by the// mapper parser. If the type is already known, it won't try.// 注册了接口之后,根据接口,开始解析所有方法上的注解,例如 @Select >>MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}
再进入parse()方法:
public void parse() {String resource = type.toString();if (!configuration.isResourceLoaded(resource)) {// 先判断 Mapper.xml 有没有解析,没有的话先解析 Mapper.xml(例如定义 package 方式)loadXmlResource();configuration.addLoadedResource(resource);assistant.setCurrentNamespace(type.getName());// 处理 @CacheNamespaceparseCache();// 处理 @CacheNamespaceRefparseCacheRef();// 获取所有方法Method[] methods = type.getMethods();for (Method method : methods) {try {// issue #237if (!method.isBridge()) {// 解析方法上的注解,添加到 MappedStatement 集合中 >>parseStatement(method);}} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));}}}parsePendingMethods();}
4. 总结
总结:
- 我们主要完成了config配置文件、Mapper文件、Mapper接口中注解的解析。
- 我们得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。
- 最后,返回了一个DefaultSqlSessionFactory,里面持有了Configuration的实例。