编程语言又是搞砸Mybatis源码的一天

    作者:鸭血粉丝更新于: 2020-02-15 23:18:14

    大神带你学编程,欢迎选课

    又是搞砸Mybatis源码的一天。高级语言的出现使得计算机程序设计语言不再过度地依赖某种特定的机器或环境。这是因为高级语言在不同的平台上会被编译成不同的机器语言,而不是直接被机器执行。最早出现的编程语言之一FORTRAN的一个主要目标,就是实现平台独立。

    源码一章肯定是讲不完的,所以阿粉会分成几个章节,并且讲源码的话,代码肯定比较多,比较干,所以请大家事先准备好开水哦.

    1.上期回顾

    前面初识 mybatis 章节,阿粉首先搭建了一个简单的项目,只用了 mybatis 的 jar 包。然后通过一个测试代码,讲解了几个重要的类和步骤。

    先看下这个测试类:

    1. public class MybatisTest { 
    2.     @Test 
    3.     public void testSelect() throws IOException { 
    4.         String resource = "mybatis-config.XML"
    5.         InputStream inputStream = Resources.getResourceAsStream(resource); 
    6.         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 
    7.         SqlSession session = sqlSessionFactory.openSession(); 
    8.         try { 
    9.             FruitMapper mapper = session.getMapper(FruitMapper.class); 
    10.             Fruit fruit = mapper.findById(1L); 
    11.             System.out.println(fruit); 
    12.         } finally { 
    13.             session.close(); 
    14.         } 
    15.     } 

    这章的话,阿粉会带着大家理解一下源码,基于上面测试的代码。阿粉先申明一下,源码一章肯定是讲不完的,所以阿粉会分成几个章节,并且讲源码的话,代码肯定比较多,比较干,所以请大家事先准备好开水哦。

    2.源码分析

    这里阿粉会根据 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream) 这段代码来分析 mybatis 做了哪些事情。

    2.1设计模式

    通过这段代码,我们首先分析一下用到了哪些设计模式呢?

    首先 SqlSessionFactory 这个类用到了工厂模式,并且上一章阿粉也说到了,这个类是全局唯一的,所以它还使用了单列模式。

    然后是 SqlSessionFactoryBuilder 这个类,一看就知道用到了建造者模式。

    所以,只看这段代码就用到了工厂模式,单列模式和建造者模式。

    2.2mybatis做了什么事情

    这里就开始源码之旅了,首先 ctrl + 左键点击 build 方法,我们会看到

    1. public SqlSessionFactory build(InputStream inputStream) { 
    2.    return build(inputStream, nullnull); 
    3.  } 

    再点击 build ,然后就是

    1. public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { 
    2.     try { 
    3.       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 
    4.       return build(parser.parse()); 
    5.         //后面代码省略 
    6.         ... 
    7.     } 

    首先 XMLConfigBuilder 这个类就是用来解析我们的配置文件 mybatis-config.xml ,调用这个类的构造函数,主要是创建一个 Configuration 对象,这个对象的属性就对应mybatis-config.xml里面的一级标签。这里不贴代码,有兴趣的自己点开看下。

    然后就是 build(parser.parse()) 这段代码,先执行 parse()方法,再执行 build() 方法。不过这里,阿粉先说下 build() 这个方法,首先parse() 方法返回的就是 Configuration 对象,然后我们点击 build

    1. public SqlSessionFactory build(Configuration config) { 
    2.     return new DefaultSqlSessionFactory(config); 
    3.   } 

    这里就是返回的默认的SqlSessionFactory 。

    然后我们再说 parse() 方法,因为这个是核心方法。我们点进去看下。

    1. public Configuration parse() { 
    2.     if (parsed) { 
    3.       throw new BuilderException("Each XMLConfigBuilder can only be used once."); 
    4.     } 
    5.     parsed = true
    6.     parseConfiguration(parser.evalNode("/configuration")); 
    7.     return configuration; 
    8.   } 

    首先是 if 判断,就是防止解析多次。看 parseConfiguration方法。

    1. private void parseConfiguration(XNode root) { 
    2.     try { 
    3.       //issue #117 read properties first 
    4.       propertiesElement(root.evalNode("properties")); 
    5.       Properties settings = settingsAsProperties(root.evalNode("settings")); 
    6.       loadCustomVfs(settings); 
    7.       loadCustomLogImpl(settings); 
    8.       typeAliasesElement(root.evalNode("typeAliases")); 
    9.       pluginElement(root.evalNode("plugins")); 
    10.       objectFactoryElement(root.evalNode("objectFactory")); 
    11.       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 
    12.       reflectorFactoryElement(root.evalNode("reflectorFactory")); 
    13.       settingsElement(settings); 
    14.       // read it after objectFactory and objectWrapperFactory issue #631 
    15.       environmentsElement(root.evalNode("environments")); 
    16.       databaseIdProviderElement(root.evalNode("databaseIdProvider")); 
    17.       typeHandlerElement(root.evalNode("typeHandlers")); 
    18.       mapperElement(root.evalNode("mappers")); 
    19.     } catch (Exception e) { 
    20.       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); 
    21.     } 
    22.   } 

    这里就开始了解析mybatis-config.xml里面的一级标签了。阿粉不全部讲,只说几个主要的。

    1. settings:全局配置,比如我们的二级缓存,延迟加载,日志打印等
    2. mappers:解析dao层的接口和mapper.xml文件
    3. properties:这个一般是配置数据库的信息,如:数据库连接,用户名和密码等,不讲
    4. typeAliases :参数和返回结果的实体类的别名,不讲
    5. plugins:插件,如:分页插件,不讲
    6. objectFactory,objectWrapperFactory:实例化对象用的,比如返回结果是一个对象,就是通过这个工厂反射获取的,不讲
    7. environments:事物和数据源的配置,不讲
    8. databaseIdProvider:这个是用来支持不同厂商的数据库
    9. typeHandlers:这个是java类型和数据库的类型做映射,比如数据库的 varchar类型对应 java 的String 类型,不讲

    2.3解析settings

    我们点击 settingsElement(settings) 这个方法

    1. private void settingsElement(Properties props) { 
    2.   configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior""PARTIAL"))); 
    3.   configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior""NONE"))); 
    4.   configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); 
    5.   configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))); 
    6.   configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false)); 
    7.   configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false)); 
    8.   configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true)); 
    9.   configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true)); 
    10.   configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false)); 
    11.   configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType""SIMPLE"))); 
    12.   configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null)); 
    13.   configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null)); 
    14.   configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); 
    15.   configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); 
    16.   configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope""SESSION"))); 
    17.   configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull""OTHER"))); 
    18.   configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString")); 
    19.   configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true)); 
    20.   configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage"))); 
    21.   configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler"))); 
    22.   configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); 
    23.   configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true)); 
    24.   configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false)); 
    25.     configuration.setLogPrefix(props.getProperty("logPrefix")); 
    26.   configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); 
    27.   } 

    这个就是设置我们的全局配置信息, setXXX 方法的第一个参数是 mybatis-config.xml 里面标签里面配置的。有的同学就会问了,阿粉阿粉,我们里面没有配置那么多啊,这里怎么设置那么多属性,值是什么呢?那我们看下方法的第二个参数,这个就是默认值。比如 cacheEnabled 这个属性,缓存开关,没有配置的话,默认是开启的 true。所有以后看全局配置的默认值,不用去官网看了,直接在这个方法里面看。

    2.4解析mappers

    点击 mapperElement 方法

    1. private void mapperElement(XNode parent) throws Exception { 
    2.     if (parent != null) { 
    3.       for (XNode child : parent.getChildren()) { 
    4.         if ("package".equals(child.getName())) { 
    5.           String mapperPackage = child.getStringAttribute("name"); 
    6.           configuration.addMappers(mapperPackage); 
    7.         } else { 
    8.           String resource = child.getStringAttribute("resource"); 
    9.           String url = child.getStringAttribute("url"); 
    10.           String mapperClass = child.getStringAttribute("class"); 
    11.           if (resource != null && url == null && mapperClass == null) { 
    12.             ErrorContext.instance().resource(resource); 
    13.             InputStream inputStream = Resources.getResourceAsStream(resource); 
    14.             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 
    15.             mapperParser.parse(); 
    16.           } else if (resource == null && url != null && mapperClass == null) { 
    17.             ErrorContext.instance().resource(url); 
    18.             InputStream inputStream = Resources.getUrlAsStream(url); 
    19.             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); 
    20.             mapperParser.parse(); 
    21.           } else if (resource == null && url == null && mapperClass != null) { 
    22.             Class mapperInterface = Resources.classForName(mapperClass); 
    23.             configuration.addMapper(mapperInterface); 
    24.           } else { 
    25.             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); 
    26.           } 
    27.         } 
    28.       } 
    29.     } 
    30.   } 

    这里有4个判断 page (包),resource(相对路径,阿粉配的就是这个,所有按照这个讲解源码),url(绝对路径),class(单个接口)

    XMLMapperBuilder 这个类是用来解析mapper.xml文件的。然后我们点击 parse 方法。

    1. public void parse() { 
    2.     if (!configuration.isResourceLoaded(resource)) { 
    3.       configurationElement(parser.evalNode("/mapper")); 
    4.       configuration.addLoadedResource(resource); 
    5.       bindMapperForNamespace(); 
    6.     } 
    7.     parsePendingResultMaps(); 
    8.     parsePendingCacheRefs(); 
    9.     parsePendingStatements(); 
    10.   } 

    if 判断是判断 mapper 是不是已经注册了,单个Mapper重复注册会抛出异常。

    configurationElement:解析 mapper 里面所有子标签。

    1. private void configurationElement(XNode context) { 
    2.     try { 
    3.       String namespace = context.getStringAttribute("namespace"); 
    4.       if (namespace == null || namespace.equals("")) { 
    5.         throw new BuilderException("Mapper's namespace cannot be empty"); 
    6.       } 
    7.       builderAssistant.setCurrentNamespace(namespace); 
    8.       cacheRefElement(context.evalNode("cache-ref")); 
    9.       cacheElement(context.evalNode("cache")); 
    10.       parameterMapElement(context.evalNodes("/mapper/parameterMap")); 
    11.       resultMapElements(context.evalNodes("/mapper/resultMap")); 
    12.       sqlElement(context.evalNodes("/mapper/sql")); 
    13.       buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 
    14.     } catch (Exception e) { 
    15.       throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); 
    16.     } 
    17.   } 

    cacheRefElement:缓存

    cacheElement:是否开启二级缓存

    parameterMapElement:配置的参数映射

    resultMapElements:配置的结果映射

    sqlElement:公用sql配置

    buildStatementFromContext:解析 select|insert|update|delete 标签,获得 MappedStatement 对象,一个标签一个对象

    bindMapperForNamespace:把namespace对应的接口的类型和代理工厂类绑定起来。

    1. private void bindMapperForNamespace() { 
    2.     String namespace = builderAssistant.getCurrentNamespace(); 
    3.     if (namespace != null) { 
    4.       Class boundType = null
    5.       try { 
    6.         boundType = Resources.classForName(namespace); 
    7.       } catch (ClassNotFoundException e) { 
    8.         //ignore, bound type is not required 
    9.       } 
    10.       if (boundType != null) { 
    11.         if (!configuration.hasMapper(boundType)) { 
    12.           // spring may not know the real resource name so we set a flag 
    13.           // to prevent loading again this resource from the mapper interface 
    14.           // look at MapperAnnotationBuilder#loadXmlResource 
    15.           configuration.addLoadedResource("namespace:" + namespace); 
    16.           configuration.addMapper(boundType); 
    17.         } 
    18.       } 
    19.     } 
    20.   } 

    看最后 addMapper 方法

     

    1. public <T> void addMapper(Class<T> type) { 
    2.     if (type.isInterface()) { 
    3.       if (hasMapper(type)) { 
    4.         throw new BindingException("Type " + type + " is already known to the MapperRegistry."); 
    5.       } 
    6.       boolean loadCompleted = false
    7.       try { 
    8.         knownMappers.put(type, new MapperProxyFactory<>(type)); 
    9.         // It's important that the type is added before the parser is run 
    10.         // otherwise the binding may automatically be attempted by the 
    11.         // mapper parser. If the type is already known, it won't try. 
    12.         MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); 
    13.         parser.parse(); 
    14.         loadCompleted = true
    15.       } finally { 
    16.         if (!loadCompleted) { 
    17.           knownMappers.remove(type); 
    18.         } 
    19.       } 
    20.     } 
    21.   } 

    判断 namespace对应的类是否是接口,然后判断是否已经加载了这个接口,最后将namespace对应的接口和 MapperProxyFactory 放到 map 容器中。MapperProxyFactory 工厂模式,创建 MapperProxy 对象,这个一看就是代理对象。这个就是根据接口里面的方法获取 mapper.xml里面对应的sql语句的关键所在。具体的等下次讲解getMapper()源码的时候在深入的讲解。

    3.时序图 

    编程语言又是搞砸Mybatis源码的一天_编程语言_Python_Python教程_课课家

    4.总结

    在这一步,我们主要完成了 config 配置文件、Mapper 文件、Mapper 接口解析。我们得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。最后,返回了一个DefaultSqlSessionFactory,里面持有了 Configuration 的实例。

    在过去的几十年间,大量的编程语言被发明、被取代、被修改或组合在一起。尽管人们多次试图创造一种通用的程序设计语言,却没有一次尝试是成功的。之所以有那么多种不同的编程语言存在的原因是,编写程序的初衷其实也各不相同;新手与老手之间技术的差距非常大,而且有许多语言对新手来说太难学;还有,不同程序之间的运行成本(runtime cost)各不相同。

课课家教育

未登录

1