在使用Mybatis時,咱們一般將其配置在Spring容器中,當Spring啓動的時候會自動加載Mybatis的全部配置文件而後生成注入到Spring中的Bean,本文從實用的角度進行Mybatis源碼解讀,會關注如下一些方面:java
先看一段初始化Mybatis環境而且執行SQL語句的Java代碼:mysql
package org.apache.ibatis.session; import java.io.Reader; import org.apache.ibatis.io.Resources; public class MyTest { public static void main(String[] args) throws Exception { // 開始初始化 final String resource = "org/apache/ibatis/builder/MapperConfig.xml"; final Reader reader = Resources.getResourceAsReader(resource); SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader); // 開始執行SQL SqlSession session = sqlMapper.openSession(); Integer count = session.selectOne("org.apache.ibatis.domain.blog.mappers.BlogMapper.selectCountOfPosts"); System.out.println(count); } }
這段代碼完成了這些事情:spring
在這裏前三行代碼包括讀取配置文件和建立SqlSessionFactory,這就是Mybatis的一次初始化過程。sql
若是查看一下Spring配置Mybatis的文件,就會發現它使用mybatis-spring的包也主要是初始化了這個SqlSessionFactory對象:數據庫
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:conf/MapperConfig.xml" /> <property name="mapperLocations"> <list> <value>classpath*:mapper /*.xml</value> </list> </property> </bean>
該Spring配置sqlSessionFactory接收了三個參數,分別是數據源dataSource、Mybatis的主配置文件MapperConfig.xml、mapper.xml文件的掃描路徑。apache
能夠看出Mybatis的初始化過程就是讀取配置文件而後構建出sqlSessionFactory的過程。緩存
上面的Java代碼中初始化Mybatis只使用了配置文件MapperConfig.xml,然而在Spring配置文件中構建sqlSessionFactory時也使用了mapper.xml配置文件,其實Mybatis最多也就這兩類文件,主配置文件MapperConfig.xml能夠經過<mappers>XML元素包含普通的mapper.xml配置文件。session
主配置文件:MapperConfig.xml
一個包含了全部屬性的MapperConfig.xml實例:mybatis
<?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> <properties resource="org/apache/ibatis/databases/blog/blog-derby.properties" /> <settings> <setting name="cacheEnabled" value="true" /> <setting name="lazyLoadingEnabled" value="false" /> <setting name="multipleResultSetsEnabled" value="true" /> <setting name="useColumnLabel" value="true" /> <setting name="useGeneratedKeys" value="false" /> <setting name="defaultExecutorType" value="SIMPLE" /> <setting name="defaultStatementTimeout" value="25" /> </settings> <typeAliases> <typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author" /> <typeAlias alias="Blog" type="org.apache.ibatis.domain.blog.Blog" /> </typeAliases> <typeHandlers> <typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.CustomStringTypeHandler" /> </typeHandlers> <objectFactory type="org.apache.ibatis.builder.ExampleObjectFactory"> <property name="objectFactoryProperty" value="100" /> </objectFactory> <plugins> <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin"> <property name="pluginProperty" value="100" /> </plugin> </plugins> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="" value="" /> </transactionManager> <dataSource type="UNPOOLED"> <property name="driver" value="${driver}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </dataSource> </environment> </environments> <databaseIdProvider type="DB_VENDOR"> <property name="SQL Server" value="sqlserver" /> <property name="DB2" value="db2" /> <property name="Oracle" value="oracle" /> </databaseIdProvider> <mappers> <mapper resource="org/apache/ibatis/builder/AuthorMapper.xml" /> <mapper resource="org/apache/ibatis/builder/BlogMapper.xml" /> </mappers> </configuration>
主配置文件只有一個XML節點,就是configuration,它包含9種配置項:架構
能夠看出,前8個配置項用戶設定Mybatis運行的一些環境,而第9個mappers映射器纔是須要執行的SQL的配置,在正常狀況下,咱們只須要配置第9個mapper映射器的地址便可,前面的的Mybatis行爲配置都有默認值正常狀況下不須要設定。
雖然咱們常常寫mapper文件,知道有select/insert/update/delete四種元素,還有sql/resultmap等配置項,就感受配置項好多好多,但其實mapper總共也就8種配置,咱們經常使用的6種就包含在內:
正常狀況下,咱們不多使用Mybatis提供的cache機制而是使用外部的Redis等緩存,因此這裏的1和2的cache配置幾乎不會使用,主要也就是咱們平時使用的6種配置。
以上就是Mybatis全部提供給咱們配置的地方,改變Mybatis行爲的有8個配置項,每一個XML配置文件恰好也最多有8個配置項,總共有16個配置項。
閱讀Mybatis源碼最好的方式,就是從源碼中的單測做爲入口,而後DEBUG一步步的執行,在本身關注的地方多多停留一會仔細查看。
如下以代碼的流程進行解析,只貼出主要的代碼塊:
@BeforeClass public static void setup() throws Exception { createBlogDataSource(); final String resource = "org/apache/ibatis/builder/MapperConfig.xml"; final Reader reader = Resources.getResourceAsReader(resource); sqlMapper = new SqlSessionFactoryBuilder().build(reader); }
這裏看到,進入了new SqlSessionFactoryBuilder().build(reader)方法。
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
主要兩行在try塊內,第一行的內容是調用XPathParser加載了Mybatis的主配置文件,而第二步包含兩個步驟,parser.parse()方法返回的是一個Configuration對象,包裹它的build方法只有一行代碼:
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
這就能夠看出,其實初始化過程就是建立Configuration對象的過程,對照MapperConfig.xml的根元素是<configuration>,不難猜想到Configuration是一個很是重要的、包含了Mybatis全部數據配置的對象。
接下來進入了XMLConfigBuilder.parse()方法,該方法解析XML文件的/configuration節點,而後挨個解析了上面配置文件中提到的9大配置:
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { try { // issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
咱們挨個查看,這些配置項的解析,都產出了什麼內容;
進入propertiesElement方法,咱們發現初始化了一個Properties對象,將XML中全部的子節點按照KEY-VALUE存入properties以後,和Configuration.variables變量進行了合併,而Configuration.variables自己,也是個Properties對象;
private void propertiesElement(XNode context) throws Exception { if (context != null) { Properties defaults = context.getChildrenAsProperties(); String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); if (resource != null && url != null) { throw new BuilderException( "The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); } if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); } Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } parser.setVariables(defaults); configuration.setVariables(defaults); } }
將properties配置解析後合併到Configuration.variables以後,後續的配置文件均可以使用這些變量。
setting配置的讀取,包含兩個步驟,第一步,將XML中全部的配置讀取到properties對象:
private Properties settingsAsProperties(XNode context) { if (context == null) { return new Properties(); } Properties props = context.getChildrenAsProperties(); // Check that all settings are known to the configuration class MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); 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)."); } } return props; }
這個函數讀取了setting的配置項,經過反射訪問Configuration.class,若是不存在某個配置項的set方法則報錯;
而後在settingsElement方法中,將這些讀取的配置項存入了Configuration中:
private void settingsElement(Properties props) throws Exception { 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.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.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true)); configuration .setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false)); configuration.setLogPrefix(props.getProperty("logPrefix")); @SuppressWarnings("unchecked") Class<? extends Log> logImpl = (Class<? extends Log>) resolveClass(props.getProperty("logImpl")); configuration.setLogImpl(logImpl); configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); }
由於setting變量直接改變的是Mybatis的行爲,因此配置項直接存於Confirguration的屬性中。
進入typeAliasesElement方法,用於對typeAliases配置的解析:
private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { if ("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) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } }
該方法將typeAliases的配置項提取以後,存入了typeAliasRegistry這個對象,該對象是在BaseBuilder中初始化的:
public abstract class BaseBuilder { 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(); }
在Configuration類中,咱們看到了該對象的聲明:
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
打開該類的代碼,發現特別簡單的,用一個MAP存儲了別名和對應的類的映射:
public class TypeAliasRegistry { private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>(); public TypeAliasRegistry() { registerAlias("string", String.class); registerAlias("byte", byte.class); registerAlias("long", long.class); registerAlias("short", short.class); registerAlias("int", Integer.class); registerAlias("integer", Integer.class); registerAlias("double", double.class); registerAlias("float", float.class); registerAlias("boolean", Boolean.class);
在構造函數中Mybatis已經默認註冊了一些經常使用的別名和類的關係,因此咱們能夠在mappers的xml文件中使用這些短名字。
mybatis提供了大部分數據類型的typeHandlers,若是咱們要定製本身的類型處理器好比實現數據庫中0/1兩個數字到中文男/女的映射,就能夠本身實現typeHandler
private void typeHandlerElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String typeHandlerPackage = child.getStringAttribute("name"); typeHandlerRegistry.register(typeHandlerPackage); } else { String javaTypeName = child.getStringAttribute("javaType"); String jdbcTypeName = child.getStringAttribute("jdbcType"); String handlerTypeName = child.getStringAttribute("handler"); Class<?> javaTypeClass = resolveClass(javaTypeName); JdbcType jdbcType = resolveJdbcType(jdbcTypeName); Class<?> typeHandlerClass = resolveClass(handlerTypeName); if (javaTypeClass != null) { if (jdbcType == null) { typeHandlerRegistry.register(javaTypeClass, typeHandlerClass); } else { typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); } } else { typeHandlerRegistry.register(typeHandlerClass); } } } } }
在該方法中,經過反射獲得了javaTypeClass、jdbcType、typeHandlerClass三個變量,這三個變量組成(javaType、jdbcType、typeHandler)三元組,當遇到javaType到jdbcType的轉換,或者遇到jdbcType到javaType的轉換時就會使用該typeHandler。
而後該方法調用了TypeHandlerRegistry.register進行註冊,TypeHandlerRegistry對象是從BaseBuilder中的Configuration對象中獲取的:
public abstract class BaseBuilder { 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(); }
在TypeHandlerRegistry中,創建了幾個Map映射:
public final class TypeHandlerRegistry { private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>( JdbcType.class); private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>(); private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this); private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
第一個是JdbcType爲key的map,第二個是JavaType爲key的map,第三個是未知的處理器、最後一個是包含所有的處理器;
當執行SQL的時候,會將javaBean的JavaType轉換到DB的jdbcType,而查詢出來數據的時候,又須要將jdbcType轉換成javaType,在TypeHandlerRegistry的構造函數中,已經註冊好了不少默認的typeHandler,大部分狀況下不須要咱們添加:
public TypeHandlerRegistry() { register(Boolean.class, new BooleanTypeHandler()); register(Boolean.class, new BooleanTypeHandler()); register(JdbcType.BOOLEAN, new BooleanTypeHandler()); register(JdbcType.BIT, new BooleanTypeHandler()); register(byte.class, new ByteTypeHandler()); register(byte.class, new ByteTypeHandler()); register(JdbcType.TINYiNT, new ByteTypeHandler()); register(short.class, new ShortTypeHandler()); register(short.class, new ShortTypeHandler()); register(JdbcType.SMALLiNT, new ShortTypeHandler());
要實現一個typeHandler,須要實現接口,該接口提供的就是從javaType到jdbcType的setParameter方法,以及從jdbcType到javaType轉換的getResult方法:
public interface TypeHandler<T> { void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
若是想本身控制查詢數據庫的結果到JavaBean映射的生成,則能夠建立本身的objectFactory,解析代碼以下:
private void objectFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties properties = context.getChildrenAsProperties(); ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance(); factory.setProperties(properties); configuration.setObjectFactory(factory); } }
能夠看到,該配置項包含type屬性,以及properties子節點,建立好ObjectFactory對象後,就會設置到configuration中:
// Configuration對象的objectFactory成員變量 protected ObjectFactory objectFactory = new DefaultObjectFactory();
要實現ObjectFactory,須要繼承該接口:
public interface ObjectFactory { void setProperties(Properties properties); <T> T create(Class<T> type); <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs); <T> boolean isCollection(Class<T> type); }
該工廠接口提供了設置屬性列表,還有建立對象的工廠方法。
plugin,即mybatis的插件,可讓咱們本身進行開發用於擴展mybatis。
進入pluginElement方法進入解析:
private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } }
該段代碼,首先獲取intercepter元素做爲攔截器,而後讀取該節點的全部子節點做爲配置項,最後調用configuration.addInterceptor方法添加到了configuration中的interceptorChain中,該對象是攔截器鏈的一個包裝對象:
public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<Interceptor>(); public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { // target變量每次也在變化着 target = interceptor.plugin(target); } return target; } public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } public List<Interceptor> getInterceptors() { return Collections.unmodifiableList(interceptors); } }
該類中使用List<Interceptor>存儲了全部配置的攔截器,並提供了addInterceptor用於添加攔截器,提供了getInterceptors用於獲取當前全部添加的插件列表,提供了pluginAll接口調用全部的Interceptor.plugin(Object)方法進行插件的執行。
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")); DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); } } } }
代碼中經過isSpecifiedEnvironment方法判斷當前的id是否是指定要讀取的environment,若是是的話經過反射獲取事務管理器和數據源,而後用Environment.Builder建立Enviroment對象並設置到Configuration中,在Configuration中能夠看到Enviroment成員變量:
protected Environment environment;
而Enviroment對象也只包含了這三個屬性:
public final class Environment { private final String id; private final TransactionFactory transactionFactory; private final DataSource dataSource;
mybatis固然不僅是支持mysql,也會支持oracle、sqlserver等不一樣的數據庫,解析代碼以下:
private void databaseIdProviderElement(XNode context) throws Exception { DatabaseIdProvider databaseIdProvider = null; if (context != null) { String type = context.getStringAttribute("type"); // awful patch to keep backward compatibility if ("VENDOR".equals(type)) { type = "DB_VENDOR"; } Properties properties = context.getChildrenAsProperties(); databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance(); databaseIdProvider.setProperties(properties); } Environment environment = configuration.getEnvironment(); if (environment != null && databaseIdProvider != null) { String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource()); configuration.setDatabaseId(databaseId); } }
解析databaseIdProvider后里面的Properties中存儲了各類數據庫的映射,而且databaseIdProvider提供了一個根據dataSource獲取對應的databseId的方法,以VendorDatabaseIdProvider爲例,是經過connection.getMetaData().getDatabaseProductName()獲取數據庫的產品名稱,而後從剛纔databaseIdProvider中獲取對應的databaseId:
public class VendorDatabaseIdProvider implements DatabaseIdProvider { private static final Log log = LogFactory.getLog(VendorDatabaseIdProvider.class); private Properties properties; @Override public String getDatabaseId(DataSource dataSource) { if (dataSource == null) { throw new NullPointerException("dataSource cannot be null"); } try { return getDatabaseName(dataSource); } catch (Exception e) { log.error("Could not get a databaseId from dataSource", e); } return null; } @Override public void setProperties(Properties p) { this.properties = p; } private String getDatabaseName(DataSource dataSource) throws SQLException { String productName = getDatabaseProductName(dataSource); if (this.properties != null) { for (Map.Entry<Object, Object> property : properties.entrySet()) { if (productName.contains((String) property.getKey())) { return (String) property.getValue(); } } // no match, return null return null; } return productName; } private String getDatabaseProductName(DataSource dataSource) throws SQLException { Connection con = null; try { con = dataSource.getConnection(); DatabaseMetaData metaData = con.getMetaData(); return metaData.getDatabaseProductName(); } finally { if (con != null) { try { con.close(); } catch (SQLException e) { // ignored } } } } }
在獲取了databaseId以後,最後將databaseId設置到configuration,後續當執行SQL的時候會自動根據該databaseId來映射具體數據庫的SQL。
mappers的解析最爲複雜,咱們假設mapper文件均是url指定的xml文件,來進行解析流程的查看:
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"); if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); // 使用XMLMapperBuilder加載mapper.xml,而後進入parse()方法 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."); } } } } }
加註釋部分顯示,讀取每一個mapper.xml資源文件的地址後,進入了XMLMapperBuilder.parse()方法:
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); } 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); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } }
而後進入了configurationElement(parser.evalNode(「/mapper」));方法後會讀取全部xml中mapper下的子元素,在這裏咱們只查看buildStatementFromContext方法:
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 { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
該方法出現了一個XMLStatementBuilder用於select/insert/update/delete語句各自的解析,在XMLStatementBuilder.parseStatementNode方法中解析了各類語句的屬性和參數以及動態SQL的處理,最後調用builderAssistant.addMappedStatement方法,全部的參數和內容被構建成MappedStatement,添加到了configuration中:
MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement);
如下是configuration中的mappedStatements對象:
// Configuration的mappedStatements對象 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>( "Mapped Statements collection");
這裏的StrictMap就是一個HashMap,在addMappedStatement方法中能夠看到該map的Key是各個SQL的ID:
public void addMappedStatement(MappedStatement ms) { mappedStatements.put(ms.getId(), ms); }
由此能夠推測整個Mybatis執行SQL的過程:
以上就是對Mybatis初始化過程的詳解,其最終產出瞭如下對象列表:
Configuration中的各個對象:
最後,歡迎作Java的工程師朋友們加入Java高級架構進階Qqun:963944895
羣內有技術大咖指點難題,還提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)
比你優秀的對手在學習,你的仇人在磨刀,你的閨蜜在減肥,隔壁老王在練腰, 咱們必須不斷學習,不然咱們將被學習者超越!
趁年輕,使勁拼,給將來的本身一個交代!