MyBatis(二)MyBatis基本流程源码分析
MyBatis體系結構
MyBatis的工作流程
- 在MyBatis啟動的時候我們要去解析配置文件,包括全局配置文件和映射器配置文件,我們會把它們解析成一個Configuration對象,里面會包含各種配置文件的參數信息
- 創建一個包含Configuration會話工廠SqlSessionFactory ,通過它來創建SqlSession對象,SqlSession是我們操作數據庫的接口,代表跟數據庫之間的一次連接
- 執行具體的SQL或接口方法 實際底層是通過SqlSession實現類里的Executor封裝了對數據庫的操作
MyBatis的主要工作流程里面,不同的功能是由很多不同的類協作完成的,可以看下Mybatis jar包的結構
大體可以把相關的類按照功能劃分為如下幾個層次
MyBatis源碼解讀和工作原理
我們可以從MyBatis最基本的使用著手看源碼
@Test public void Test() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//1.通過SqlSessionFactoryBuilder解析配置文件創建工廠類SqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//2.通過SqlSessionFactory創建SqlSessionSqlSession session = sqlSessionFactory.openSession();try {//3.獲得一個Mapper對象BlogMapper mapper = session.getMapper(BlogMapper.class);//4.執行對應的方法Blog blog = mapper.selectBlogById(1);System.out.println(blog);} finally {session.close();}}配置解析
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {SqlSessionFactory var5;try {//MyBatis有各種各樣的XMLXXXBuilder,ConfigBuilder用來解析mybatis-config.xml,還有XMLMapperBuilder,XMLStatementBuilder等//它們都繼承自抽象父類BaseBuilderXMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);var5 = this.build(parser.parse());} catch (Exception var14) {throw ExceptionFactory.wrapException("Error building SqlSession.", var14);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException var13) {}}return var5;}public Configuration parse() {//如果解析過配置文件就不會報錯,MyBatis的配置文件只在啟動的時候解析一次就夠了if (this.parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");} else {this.parsed = true;//真正解析配置文件的方法,從根節點configuration開始解析mybatis-config.xmlthis.parseConfiguration(this.parser.evalNode("/configuration"));return this.configuration;}}//解析mybatis-config.xml里的各種標簽 private void parseConfiguration(XNode root) {try {this.propertiesElement(root.evalNode("properties"));Properties settings = this.settingsAsProperties(root.evalNode("settings"));this.loadCustomVfs(settings);this.loadCustomLogImpl(settings);this.typeAliasesElement(root.evalNode("typeAliases"));this.pluginElement(root.evalNode("plugins"));this.objectFactoryElement(root.evalNode("objectFactory"));this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));this.reflectorFactoryElement(root.evalNode("reflectorFactory"));this.settingsElement(settings);this.environmentsElement(root.evalNode("environments"));this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));//解析TypeHandler,最后存儲在Map<Type, Map<JdbcType, TypeHandler<?>>> 的嵌套Map里this.typeHandlerElement(root.evalNode("typeHandlers"));this.mapperElement(root.evalNode("mappers"));} catch (Exception var3) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);}}private void propertiesElement(XNode context) throws Exception {//解析Properties標簽的時候會獲取子標簽和屬性字段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 = this.configuration.getVariables();if (vars != null) {defaults.putAll(vars);}//把解析得到的Properties對象defaults賦值給XPathParser和configuration對象this.parser.setVariables(defaults);this.configuration.setVariables(defaults);}}private void typeAliasesElement(XNode parent) {if (parent != null) {Iterator var2 = parent.getChildren().iterator();while(var2.hasNext()) {XNode child = (XNode)var2.next();String alias;if ("package".equals(child.getName())) {alias = child.getStringAttribute("name");this.configuration.getTypeAliasRegistry().registerAliases(alias);} else {alias = child.getStringAttribute("alias");String type = child.getStringAttribute("type");try {Class<?> clazz = Resources.classForName(type);if (alias == null) {this.typeAliasRegistry.registerAlias(clazz);} else {//會把配置的別名和類型注冊到typeAliasRegistry里this.typeAliasRegistry.registerAlias(alias, clazz);}} catch (ClassNotFoundException var7) {throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + var7, var7);}}}}//解析對應的plugins標簽,保存到InterceptorChain的Interceptor集合里private void pluginElement(XNode parent) throws Exception {if (parent != null) {Iterator var2 = parent.getChildren().iterator();while(var2.hasNext()) {XNode child = (XNode)var2.next();String interceptor = child.getStringAttribute("interceptor");Properties properties = child.getChildrenAsProperties();Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();interceptorInstance.setProperties(properties);this.configuration.addInterceptor(interceptorInstance);}}}public void addInterceptor(Interceptor interceptor) {this.interceptorChain.addInterceptor(interceptor);}//對于settings標簽的子標簽的處理 //二級標簽里面有很多的配置,比如二級緩存,延遲加載等。之前提到的所有的默認值,都是在這里賦值的 private void settingsElement(Properties props) {this.configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));this.configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));this.configuration.setCacheEnabled(this.booleanValueOf(props.getProperty("cacheEnabled"), true));this.configuration.setProxyFactory((ProxyFactory)this.createInstance(props.getProperty("proxyFactory")));this.configuration.setLazyLoadingEnabled(this.booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));this.configuration.setAggressiveLazyLoading(this.booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));this.configuration.setMultipleResultSetsEnabled(this.booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));this.configuration.setUseColumnLabel(this.booleanValueOf(props.getProperty("useColumnLabel"), true));this.configuration.setUseGeneratedKeys(this.booleanValueOf(props.getProperty("useGeneratedKeys"), false));this.configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));this.configuration.setDefaultStatementTimeout(this.integerValueOf(props.getProperty("defaultStatementTimeout"), (Integer)null));this.configuration.setDefaultFetchSize(this.integerValueOf(props.getProperty("defaultFetchSize"), (Integer)null));this.configuration.setMapUnderscoreToCamelCase(this.booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));this.configuration.setSafeRowBoundsEnabled(this.booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));this.configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));this.configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));this.configuration.setLazyLoadTriggerMethods(this.stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));this.configuration.setSafeResultHandlerEnabled(this.booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));this.configuration.setDefaultScriptingLanguage(this.resolveClass(props.getProperty("defaultScriptingLanguage")));this.configuration.setDefaultEnumTypeHandler(this.resolveClass(props.getProperty("defaultEnumTypeHandler")));this.configuration.setCallSettersOnNulls(this.booleanValueOf(props.getProperty("callSettersOnNulls"), false));this.configuration.setUseActualParamName(this.booleanValueOf(props.getProperty("useActualParamName"), true));this.configuration.setReturnInstanceForEmptyRow(this.booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));this.configuration.setLogPrefix(props.getProperty("logPrefix"));this.configuration.setConfigurationFactory(this.resolveClass(props.getProperty("configurationFactory")));} ...對于其他的標簽就不一一解釋了再分析下關于Mapper的解析
//如果Mapper引入的時候使用的Mapper的class,會調用addMapper注冊到MapperRegistry里,保存在Map<Class<?>, MapperProxyFactory<?>>容器里 //如果引入使用的mapper.xml,會通過XMLMapperBuilder來解析mapper配置文件private void mapperElement(XNode parent) throws Exception {if (parent != null) {Iterator var2 = parent.getChildren().iterator();while(true) {while(var2.hasNext()) {XNode child = (XNode)var2.next();String resource;//子節點有兩種情況,package:把包下的所有的mapper解析為映射器,mapper:針對指定的mapper文件解析成映射器if ("package".equals(child.getName())) {resource = child.getStringAttribute("name");this.configuration.addMappers(resource);} else {resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");//根據resource,url,class三個屬性的值選擇不同的解析方法 XMLMapperBuilder mapperParser;InputStream inputStream;if (resource != null && url == null && mapperClass == null) {//通過XMLMapperBuilder來解析MapperErrorContext.instance().resource(resource);inputStream = Resources.getResourceAsStream(resource);mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);inputStream = Resources.getUrlAsStream(url);mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());mapperParser.parse();} else {if (resource != null || url != null || mapperClass == null) {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}Class<?> mapperInterface = Resources.classForName(mapperClass);this.configuration.addMapper(mapperInterface);}}}return;}}}//XMLMapperBuilder的解析過程 public void parse() {if (!this.configuration.isResourceLoaded(this.resource)) {//解析mappr.xml里的所有標簽,其中buildStatementFromContext()方法最終會將MappedStatement對象添加到configurationthis.configurationElement(this.parser.evalNode("/mapper"));this.configuration.addLoadedResource(this.resource);//通過namespace綁定Mapper類,如果是configurationElement里沒有注冊到mapperRegistry的會調用Configuration的addMapper方法注冊進去this.bindMapperForNamespace();}this.parsePendingResultMaps();this.parsePendingCacheRefs();this.parsePendingStatements();}如果<mappers>標簽里引入的是mapper.xml,那么會解析對應的xml文件,組裝成MappedStatement對象,然后添加到Configuration的 Map<String, MappedStatement> mappedStatements 里
------------MapperRegistry 類 public <T> void addMapper(Class<T> type) {if (type.isInterface()) {if (this.hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {//把Mapper的class和對應的MapperProxyFactory添加到knownMappers map里this.knownMappers.put(type, new MapperProxyFactory(type));MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);//通過MapperAnnotationBuilder解析Mapperparser.parse();loadCompleted = true;} finally {if (!loadCompleted) {this.knownMappers.remove(type);}}}}----- MapperAnnotationBuilder 的parse()方法public void parse() {String resource = this.type.toString();if (!this.configuration.isResourceLoaded(resource)) {this.loadXmlResource();this.configuration.addLoadedResource(resource);this.assistant.setCurrentNamespace(this.type.getName());//對@CacheNamespace 和@CacheNamespaceRef注解進行處理this.parseCache();this.parseCacheRef();Method[] methods = this.type.getMethods();Method[] var3 = methods;int var4 = methods.length;for(int var5 = 0; var5 < var4; ++var5) {Method method = var3[var5];try {if (!method.isBridge()) {//遍歷Mapper的所有方法,解析得到MappedStatement添加到configuration里,以namespace + statement id為key,MappedStatement為valuethis.parseStatement(method);}} catch (IncompleteElementException var8) {this.configuration.addIncompleteMethod(new MethodResolver(this, method));}}}this.parsePendingMethods();}在addMapper里,會創建一個MapperAnnotationBuilder對象來對注解進行處理
parseCache() 和 parseCacheRef() 方 法 其 實 是 對 @CacheNamespace 和@CacheNamespaceRef這兩個注解的處理。
parseStatement()方法里也都是對注解的解析,比如@Options,@SelectKey,@ResultMap等。
最后同樣會解析成 MappedStatement 對象
解析完成后的build方法會使用Configuration創建一個DefaultSqlSessionFactory對象
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {SqlSessionFactory var5;try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);var5 = this.build(parser.parse());} catch (Exception var14) {throw ExceptionFactory.wrapException("Error building SqlSession.", var14);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException var13) {}}return var5; }public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config); }會話創建過程
//這里創建DefaultSqlSession會先通過configuration里的environment創建一個事務工廠 //再通過事務和定義的執行器類型創建對應的Executor,這是真正執行SQL的核心類 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;DefaultSqlSession var8;try {Environment environment = this.configuration.getEnvironment();TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);Executor executor = this.configuration.newExecutor(tx, execType);var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);} catch (Exception var12) {this.closeTransaction(tx);throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);} finally {ErrorContext.instance().reset();}return var8;}//根據不同的executorType創建對應的Executor,默認是Simple public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? this.defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Object executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}//如果配置了開啟二級緩存(默認cacheEnabled = true),會使用CachingExecutor裝飾executor---裝飾器模式if (this.cacheEnabled) {executor = new CachingExecutor((Executor)executor);}//這里通過配置的plugin插件對executor進行處理包裝 具體的原理后面說明Executor executor = (Executor)this.interceptorChain.pluginAll(executor);return executor;}三種不同的Executor:
SimpleExecutor:每執行一次update或select,就開啟一個Statement對象,用完立刻關閉Statement對象。
ReuseExecutor:會重復使用Statement對象。使用完之后,不關閉 Statement 對象,而是存儲在 Map 內,供下一次使用
BatchExecutor:支持批處理的Executor 。
獲得Mapper對象
我們通過SqlSession調用getMapper接口最終會通過mapperRegistry根據type和當前會話獲取Mapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return this.mapperRegistry.getMapper(type, sqlSession); }public <T> T getMapper(Class<T> type, SqlSession sqlSession) {MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");} else {try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception var5) {throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);}} }-------MapperProxyFactoryprotected T newInstance(MapperProxy<T> mapperProxy) {return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);}public T newInstance(SqlSession sqlSession) {MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);return this.newInstance(mapperProxy);}從上面的源碼不難看出,我們獲取到的Mapper實際是根據解析配置時注冊的MapperProxyFactory通過動態代理生成的代理對象,這樣就解釋了為什么我們可以不需要創建Mapper的實現類就可以直接調用方法,因為MyBatis幫我們返回了代理對象。而我們使用Mapper只是為了根據接口類型+方法的名稱,找到對應namespace下的Statement ID,所以不需要實現類,在?MapperProxy 里面直接執行SQL就可以。
執行SQL
由于所有的 Mapper 都是使用?MapperProxy 增強的代理對象,所以執行任何方法其實真實執行的都是MapperProxy的invoke()方
法。
以select舉例來說,之后都會執行到DefaultSqlSession的selectList方法
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {List var5;try {//先通過對應的Statement ID 即command name從configuration中獲得MappedStatement//MappedStatement包含SQL上所有的參數id,resultMap,parameterMap等MappedStatement ms = this.configuration.getMappedStatement(statement);//使用sqlSession的executor執行query方法var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception var9) {throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);} finally {ErrorContext.instance().reset();}return var5; }執行query之后會先調用BaseExecutor的query()方法,最后會執行具體的執行器的doQuery方法(模板模式)
創建StatementHandler
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;List var9;try {Configuration configuration = ms.getConfiguration();//先創建對應的StatementHandlerStatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);//通過handler創建對應的Statementstmt = this.prepareStatement(handler, ms.getStatementLog());var9 = handler.query(stmt, resultHandler);} finally {this.closeStatement(stmt);}return var9;}----------RoutingStatementHandler 在創建StatementHandler的時候會根據 MappedStatement 里面的 statementType 決定 StatementHandler的類型默認是PREPARED,包含ParameterHandler和ResultHandler,這里是在BaseStatementHandler(StatementHandler實現類的抽象父類)初始化的時候創建的public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {switch(ms.getStatementType()) {case STATEMENT:this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case PREPARED:this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case CALLABLE:this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;default:throw new ExecutorException("Unknown statement type: " + ms.getStatementType());}}通過StatementHandler創建Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Connection connection = this.getConnection(statementLog);Statement stmt = handler.prepare(connection, this.transaction.getTimeout());//對statement進行預編譯,處理參數handler.parameterize(stmt);return stmt;}public void parameterize(Statement statement) throws SQLException {this.parameterHandler.setParameters((PreparedStatement)statement);}最后一步:執行SQL
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement)statement;ps.execute();return this.resultSetHandler.handleResultSets(ps);}畫了下時序圖,這里的SQL執行畫的是update:
總結
以上是生活随笔為你收集整理的MyBatis(二)MyBatis基本流程源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MongoDB入门教程(1)
- 下一篇: MyBatis(四)MyBatis插件原