MyBatis源码分析(上)
相信大家平时工作中都有用过MyBatis这个ORM框架,但不知大家是否在工作之余,有研究过框架源码?本篇文章会带大家一起简单过一遍MyBatis的关键源码,没看过源码的同学可以快速了解整个流程,看过源码的同学则可以温故一下相关知识点。
一个标准的MyBatis用法代码如下:
1 String resource = "org/mybatis/example/mybatis-config.xml"; 2 3 InputStream inputStream = Resources.getResourceAsStream(resource); 4 5 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 6 7 SqlSession session = sqlSessionFactory.openSession(); 8 9 try { 10 BlogMapper mapper = session.getMapper(BlogMapper.class); 11 Blog blog = mapper.selectBlog(101); 12 } finally { 13 session.close(); 14 }
第1行和第3行代码就不用详细说了,就是获取项目中Mybatis的配置文件,并将之转换为流以供之后创建SqlSessionFactory使用。本篇文章会详细分析第5行和第7行代码,看看Mybatis在这里究竟干了些啥事。
SqlSessionFactory,将资源文件中所有元素对应的内容设置到Configuration对象中
顾名思义,这是一个工厂类,它主要的作用就是用来创建SqlSession这个对象。从第5行代码中可以看到其实是调用了 SqlSessionFactoryBuilder这个类的build方法,并传入了Mybatis的配置文件对应的流作为参数,最终返回了SqlSessionFacotory这个工厂对象。我们进入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 } catch (Exception e) { 6 throw ExceptionFactory.wrapException("Error building SqlSession.", e); 7 } finally { 8 ErrorContext.instance().reset(); 9 try { 10 inputStream.close(); 11 } catch (IOException e) { 12 // Intentionally ignore. Prefer previous error. 13 } 14 } 15 }
这里第3行代码我们可以看到其实时新建了一个 XMLConfigBuilder这个对象,同样进入这个构造方法看下做了哪些事。
1 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { 2 super(new Configuration()); 3 ErrorContext.instance().resource("SQL Mapper Configuration"); 4 this.configuration.setVariables(props); 5 this.parsed = false; 6 this.environment = environment; 7 this.parser = parser; 8 }
从源码跟踪下来实际调用的是这个方法,从第2行代码中可以看到先是创建了一个 Configuration对象,这个对象非常重要,它包含的其实就是Mybatis配置文件中用户自定义的所有属性。我们来看一下这个类长什么样子
1 public class Configuration { 2 3 protected Environment environment; 4 5 protected boolean safeRowBoundsEnabled; 6 protected boolean safeResultHandlerEnabled = true; 7 protected boolean mapUnderscoreToCamelCase; 8 protected boolean aggressiveLazyLoading; 9 protected boolean multipleResultSetsEnabled = true; 10 protected boolean useGeneratedKeys; 11 protected boolean useColumnLabel = true; 12 protected boolean cacheEnabled = true; 13 protected boolean callSettersOnNulls; 14 protected boolean useActualParamName = true; 15 protected boolean returnInstanceForEmptyRow; 16 17 protected String logPrefix; 18 protected Class<? extends Log> logImpl; 19 protected Class<? extends VFS> vfsImpl; 20 protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION; 21 protected JdbcType jdbcTypeForNull = JdbcType.OTHER; 22 protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString")); 23 protected Integer defaultStatementTimeout; 24 protected Integer defaultFetchSize; 25 protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; 26 protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL; 27 protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE; 28 29 protected Properties variables = new Properties(); 30 protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory(); 31 protected ObjectFactory objectFactory = new DefaultObjectFactory(); 32 protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory(); 33 34 protected boolean lazyLoadingEnabled = false; 35 protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL 36 37 protected String databaseId; 38 /** 39 * Configuration factory class. 40 * Used to create Configuration for loading deserialized unread properties. 41 * 42 * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a> 43 */ 44 protected Class<?> configurationFactory; 45 46 protected final MapperRegistry mapperRegistry = new MapperRegistry(this); 47 protected final InterceptorChain interceptorChain = new InterceptorChain(); 48 protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(); 49 protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); 50 protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); 51 52 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection") 53 .conflictMessageProducer((savedValue, targetValue) -> 54 ". please check " + savedValue.getResource() + " and " + targetValue.getResource()); 55 protected final Map<String, Cache> caches = new StrictMap<>("Caches collection"); 56 protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection"); 57 protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection"); 58 protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection"); 59 60 protected final Set<String> loadedResources = new HashSet<>(); 61 protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers"); 62 63 protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>(); 64 protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>(); 65 protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>(); 66 protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>(); 67 68 /* 69 * A map holds cache-ref relationship. The key is the namespace that 70 * references a cache bound to another namespace and the value is the 71 * namespace which the actual cache is bound to. 72 */ 73 protected final Map<String, String> cacheRefMap = new HashMap<>(); 74 75 public Configuration(Environment environment) { 76 this(); 77 this.environment = environment; 78 } 79 80 public Configuration() { 81 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); 82 typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); 83 84 typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); 85 typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); 86 typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); 87 88 typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); 89 typeAliasRegistry.registerAlias("FIFO", FifoCache.class); 90 typeAliasRegistry.registerAlias("LRU", LruCache.class); 91 typeAliasRegistry.registerAlias("SOFT", SoftCache.class); 92 typeAliasRegistry.registerAlias("WEAK", WeakCache.class); 93 94 typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); 95 96 typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); 97 typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); 98 99 typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); 100 typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); 101 typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); 102 typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); 103 typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); 104 typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); 105 typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); 106 107 typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); 108 typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); 109 110 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); 111 languageRegistry.register(RawLanguageDriver.class); 112 } 113 }
这里我截取了Configuration对象的一些属性和构造方法,来看一下其中比较关键的一些属性.
首先第3行的environment属性,对应的是Mybatis配置文件中 <environment></environment>的值,一般我们会根据不同的环境配置开发、测试、生产三个不同环境的配置文件,这个属性用来记录当前使用的是哪一个环境的配置文件。
然后第20行,可以看到localCache(本地缓存,即所谓的一级缓存),其默认的作用域被设置为SESSION级别。
第25行,可以看到有个ExecutorType对象,且其默认设置为了SIMPLE(对应的是SimpleExecutor.java 这个类)。这个对象其实就是执行数据库增删改查操作的入口,之后我们会分析它,这里先留个印象。
第46行,可以看到有个MapperRegistry对象,对应的是Mybatis配置文件中的 <mappers></mappers>下声明的所有mapper,存放的是所有已经加载过的Mapper资源文件。它会在创建SqlSessionFactory时,就把所有用户自定义的XML格式的Mapper文件及对应的元素放到自身的一个Map类型的属性 knownMappers 中。从名字也很好理解,就是这个Map中所有存在的Mapper我都认识了,后面就可以直接拿来用了;而这个Map中没有的Mapper我全部都不认识,那么如果之后有碰到这种Mapper就会抛出异常(后文可以看到这段逻辑的相关代码)。
第48行,可以看到有个TypeHandlerRegistry对象,这个对象会把所有Mybatis原生支持的数据类型全部加载一下(比如LONG,INT,STRING等等),如果用户有自定义typeHandler(如需要将JDK1.8 的 LocalDateTime 类型转换为数据库的 TimeStamp 类型,就需要手动引入 TypeHandler- JSR133这个引用),那么同样也会存储到TypeHandlerRegistry对象中。
第49行,可以看到有个TypeAliasRegistry对象,这个对象主要存储了Mybatis原生的别名以及用户自定义的别名,可以看到在80~112行的Configuration构造方法中,就设置了一些Mybatis本身需要的别名。
第52行,有个MappedStatement对象,这个对象存放的是xml格式的Mapper文件中具体的数据库增删改查语句,对应的是 <select></select> 、<insert></insert>、<update></update>、<delete></delete>中id属性值和具体的sql语句。之后执行sql语句时会从这个对象中来查找应该执行哪条sql语句。
第56行,有个resultMaps对象,它是一个Map格式的对象,存储了具体哪条sql语句返回的是哪个resultMap。
第57行,有个parameterMaps对象,它也是一个Map格式的对象,存储了执行具体哪条sql语句时传入参数对应的类型。
OK我们回到上面的 XMLConfigBuilder对象,创建完Configuration对象后,设置一些properties属性,然后将 parsed 变量设置为false(表示当前的XMLConfigBuilder还没有解析过Mybatis配置文件,一个XMLConfigBuilder对象只能解析一次文件,防止多次解析撑爆内存),再设置一下环境以及xPath XML解析器 parser对象,然后该方法就结束了。
我们回到最上方的build方法中,此时第3行的代码执行完毕,即Configuration对象已经预创建好,环境也已经准备好,xml解析器也准备好,下面就执行 第4行的代码,这里首先会执行 parser.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 }
可以看到这里首先判断parsed这个布尔类型变量的值,如果已经解析过配置文件则直接抛出异常,如果没有解析过就将该变量设置为true,然后执行第6行 parseConfiguration() 方法。这个方法就是使用xml解析器去解析配置文件中 <configuration></configuration>标签下的所有元素,然后设置到之前预创建的 Configuration对应的属性中。下面代码应该已经可以很清晰的展现这个过程了:
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 }
我们这里以第18行的 mapperElement为例,之前已经说过这里会解析所有<mappers></mappers>下配置的mapper文件,并放入MapperRegistry对象的knownMappers属性中,来看下是不是这样
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 }
这里有很多的if-else代码块,其实就是在判断声明<mapper></mapper>时具体使用的是哪种方式 (package声明、resource声明、url声明和class声明),但不管使用哪种声明方式最后都会调用 configuration.addMapper()这个方法(可能有同学会疑惑第11行和第16行的else-if语句块最后调用的是 mapperParser.parse() 这个方法,其实这个方法最终调用的就是 configuration.addMapper() 方法,不相信的同学可以自己下载源码跟踪到最里面的 bindMapperFoeNamespace() 方法的第441行看下就知道了),这个方法实现如下
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 }
从第8行代码可以印证上面所述的逻辑,并且大家注意下这里往 knownMappers中存放的values是一个 MapProxy对象,为什么存放的是这个MapProxy对象在下篇文章会说明。
到这里,所有的前置工作已经全部做好,我们回到 SqlSessionFactoryBuilder这个类的build方法第4行,接着就会执行 build(Configuration configuration) 这个方法了,我们看下实现:
1 public SqlSessionFactory build(Configuration config) { 2 return new DefaultSqlSessionFactory(config); 3 }
可以看到这里将设置好所有参数的Configuration对象作为参数,调用了 DefaultSqlSessionFactory这个类的构造方法后直接返回这个对象作为SqlSessionFactory的实现类,至此 SqlSessionFactory对象就已经创建完成,接着就是走最开始的第7行代码创建SqlSession了。
SqlSession,包含了预加载完成的Configuration对象,以及一系列数据库操作方法
现在我们已经得到了一个SqlSessionFactory的实现类(也就是DefaultSqlSessionFactory这个类),最开始第7行代码使用了 sqlSessionFactory.openSession() 这个方法来创建一个SqlSession,我们看下具体的实现
1 @Override 2 public SqlSession openSession() { 3 return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); 4 }
1 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { 2 Transaction tx = null; 3 try { 4 final Environment environment = configuration.getEnvironment(); 5 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); 6 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); 7 final Executor executor = configuration.newExecutor(tx, execType); 8 return new DefaultSqlSession(configuration, executor, autoCommit); 9 } catch (Exception e) { 10 closeTransaction(tx); // may have fetched a connection so lets call close() 11 throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); 12 } finally { 13 ErrorContext.instance().reset(); 14 } 15 }
实际调用的是 openSessionFromDataSource()这个方法,设置了环境、事务以及使用的执行器,然后返回的是 DefaultSqlSession这个对象,即调用如下构造方法将这些参数设置到对应属性中:
1 public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) { 2 this.configuration = configuration; 3 this.executor = executor; 4 this.dirty = false; 5 this.autoCommit = autoCommit; 6 }
好了,到这里我们已经成功获取到了SqlSession对象,MyBatis整个流程的前半部分已经结束。下篇文章会讲解后半部分的流程,主要是通过DefaultSqlSession获取对应的Mapper代理对象,然后在执行代理后的方法时,通过executor执行器执行对应sql语句,最后将结果通过ResultHandler包装返回的流程。

浙公网安备 33010602011771号