MyBatis 是 Java 開發中很是流行的 ORM 框架,其封裝了 JDBC 而且解決了 Java 對象與輸入參數和結果集的映射,同時又可以讓用戶方便地手寫 SQL 語句。MyBatis 的行爲相似於如下幾行代碼:java
Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection(url, usr, password); PraparedStatement st = conn.prepareStatement(sql); st.setInt(0, 1); st.execute(); ResultSet rs = st.getResultSet(); while (rs.next()) { String result = rs.getString(colname); }
上面是 JDBC 的使用流程,MyBatis 其實就是對上面的代碼進行分解包裝。本文將對 MyBatis 的代碼進行分析,探究其中的邏輯。mysql
首先從 MyBatis 的基本用法開始,下面是 MyBatis 官網的入門示例:sql
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
其中 mabatis-config.xml
是 MyBatis 的核心配置文件,其中包括數據源、事務管理器、別名以及 SQL 對應的 Mapper 文件等,以下所示:segmentfault
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>
有了 SqlSessionFactory
後就能夠建立 SqlSession
來調用 select
以及 update
等方法請求數據了:緩存
try { BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(101); } finally { session.close(); }
咱們按照上面的代碼流程開始分析源碼,首先是配置文件的解析:SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream)
,SqlSessionFactoryBuilder
顯然是爲了構建 SqlSessionFactory
,並且是從配置文件的輸入流構建,代碼以下:session
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 建立 XMLConfigBuilder XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // parse.parse() 進行解析 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. } } }
首先是建立了一個 XMLConfigBuilder
對象,它是用來解析 Config 文件的。XMLConfigBuilder
繼承自 BaseBuilder
,BaseBuilder
中有個 Configuration
類型的變量,這個類須要重點關注,Config 文件中解析出來的全部信息都保存在這個變量中。mybatis
建立了 XMLConfigBuilder
後調用了其 parse
方法:app
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 在這個函數中解析 parseConfiguration(parser.evalNode("/configuration")); return configuration; }
這裏主要邏輯在 parseConfiguration
中:框架
private void parseConfiguration(XNode root) { try { // 解析 properties propertiesElement(root.evalNode("properties")); //issue #117 read properties first // 解析 type alias typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析 setting settingsElement(root.evalNode("settings")); // 解析 environment environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 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); } }
這裏解析了 config 文件中全部的標籤,包括 properties
、settings
以及 mappers
等,下面挑幾個看一下。ide
settings 是對 MyBatis 的一些配置項,包括緩存的開啓以及是否使用駝峯轉換(mapUnderscoreToCamelCase)等,代碼以下:
private void settingsElement(XNode context) throws Exception { if (context != null) { // 將配置項保存到 Properties 中 Properties props = context.getChildrenAsProperties(); // Check that all settings are known to the configuration class MetaClass metaConfig = MetaClass.forClass(Configuration.class); for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); } } configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); // 默認開啓緩存 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"), true)); 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.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.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); configuration.setLogPrefix(props.getProperty("logPrefix")); configuration.setLogImpl(resolveClass(props.getProperty("logImpl"))); } }
能夠看出,settings 的子節點保存在 Properties
中,而後校驗是否有不合法的子節點,最後提取出其中的屬性保存到 Configuration
中,上面提到這個類專門用於保存 Config 文件解析出的信息。
從上面也能夠看到 MyBatis 的一些默認屬性,例如一級緩存若是沒有配置,那麼默認是開啓的。
environments 包含了數據源(dataSource) 和事務管理器(transactionManager) 的配置,代碼以下:
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)) { // 解析 transactionManager TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); // 解析 dataSource DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); // 設置 environment 到 configuration configuration.setEnvironment(environmentBuilder.build()); } } } } private TransactionFactory transactionManagerElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); // 經過反射實例化 TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a TransactionFactory."); } private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); // 經過反射實例化 DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); }
其中主要是兩部分,第一部分解析 transactionManager
,第二部分解析 dataSource
。從 transactionManagerElement
和 dataSourceElement
中能夠看出經過對應 Class
文件的 newInstance
實例化出對應的工廠對象。最終解析出的 transactionManager
和 dataSource
依然是設置到 Configuration
中。
mappers 對應了具體的 SQL Mapper 文件,也是咱們要分析的重點。
mappers 標籤能夠多種子標籤,上面的示例中是 mapper
配合 resource
:
<mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers>
咱們下面看一下此種形式在源碼中的解析:
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // 這個分支解析 resource 形式的標籤 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); // 建立 XMLMapperBuilder 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) { 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."); } } } } }
resource
標籤解析的對應分支是 (resource != null && url == null && mapperClass == null)
,其中建立了一個 XMLMapperBuilder
對象而後調用 parse
方法進行解析:
public void parse() { if (!configuration.isResourceLoaded(resource)) { // 解析 mapper 下面的標籤,包括 namespace、cache、parameterMap、resultMap 等 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); } private void configurationElement(XNode context) { try { // namespace 對應 Mapper 對應接口的全名(包名 + 類名) String namespace = context.getStringAttribute("namespace"); builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); // 解析生成 ParameterMap parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析生成 ResultMap resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); // 每個 sql 語句生成一個 MappedStatement buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e); } }
configurationElement
用於解析具體的子標籤,如 namespace
、cache
、parameterMap
、resultMap
以及 select|insert|update|delete
等。
namespace
對應了 Mapper 接口類的包名 + 類名,經過 namespace
能夠惟必定位一個 Class
文件,解析的 namespace
保存在 builderAssistant
中,後面會用到。
parameterMap
和 resultMap
解析會生成 ParameterMap
和 ResultMap
對象。每一個 SQL 語句解析會生成 MappedStatement
。
在上面的 parse
方法中,解析完標籤後調用了 bindMapperForNamespace
,這個實現了加載 namespace
對應的 Class
,而且爲每一個 Class
建立了代理類工廠對象(MapperProxyFactory
)。
MapperProxyFactory
用於爲 Mapper 接口類建立代理對象,代理對象指的是BlogMapper mapper = session.getMapper(BlogMapper.class)
生成的對象。
下面從 bindMapperForNamespace
開始:
private void bindMapperForNamespace() { 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) { 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 和 MapperProxyFactory configuration.addMapper(boundType); } } } }
其中先從 builderAssistant
取出 namespace
,而後加載對應的 Class
(boundType = Resources.classForName(namespace)
)。最後調用 configuration.addMapper(boundType)
添加到 configuration
中。 configuration.addMapper(boundType)
很關鍵,看代碼:
public <T> void addMapper(Class<T> type) { if (type.isInterface()) { 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<T>(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. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
關鍵的一行是 knownMappers.put(type, new MapperProxyFactory<T>(type))
,其中 knownMappers
的類型是 Map<Class<?>, MapperProxyFactory<?>>
,即 key 是 Class
,value 是 MapperProxyFactory
。這裏的 MapperProxyFactory
便是動態代理對象的工廠,下面是其 newInstance
方法的代碼:
protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
從中能夠看出,這裏用的是 Java 的動態代理,Proxy.newProxyInstance
方法生成指定接口的代理對象,這個方法的第三個參數是用於方法攔截的對象,這裏是 MapperProxy
的實例。
由此能夠知道,具體的執行 SQL 語句的操做是由這個類攔截而且執行的,看看這個類的 invoke
方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
若是是 Object
類中聲明的方法,則直接執行,不然調用 MapperMethod
的 execute
,其中即是 JDBC 相關的邏輯了。限於篇幅,具體內容留到下一篇文章再看。
在分析完配置文件的解析後,再回到 XMLConfigBuilder
中:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 構建 SqlSessionFactory 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. } } }
在 parser.parse()
執行完後,生成一個 Configuration
對象,最後調用 build
構建 SqlSessionFactory
,代碼以下:
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
能夠看到,最終建立的是 DefaultSqlSessionFactory
,這個類內部持有 Configuration
,而且提供了多個重載的 openSession
方法用於建立 SqlSession
。
到這裏,初始化部分就結束了。
MyBatis 的初始化流程主要是解析配置文件,將相關信息保存在 Configuration
中,同時對每一個 namespace
表明的 Class
生成代理對象工廠。最後,利用 Configuration
生成了一個 DefaultSqlSessionFactory
,經過這個對象能夠建立 SqlSession
執行 SQL 請求,相關內容將在下一篇(MyBatis 源碼解析(二):SqlSession 執行流程)分析。
若是個人文章對您有幫助,不妨點個贊支持一下(^_^)