1.Mybatis的使用
public static void main(String[] args) throws IOException { //1.獲取配置檔案流 InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); //2.構建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); //3.建立SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //4.獲取mapper UnitMapper mapper = sqlSession.getMapper(UnitMapper.class); //5.執行增刪改查 Unitinfo_source unitinfo_source = mapper.selectById(1); System.out.println(unitinfo_source); }
這是我們在簡單使用mybatis時的程式碼,並未整合Spring,首先來看下Mybatis具體的執行流程是什麼。後續我們再看整合Spring後Mybatis做了什麼處理。以及Springboot中對Mybatis做了怎樣的處理。
2.執行流程
第一步:通過Resources獲取mybatis配置檔案的輸入流
Resources是Mybatis下的一個類,getResourceAsStream讀取到mybatis的配置檔案,以流的形式載入進來
第二步:構建SqlSessionFactory
1 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { 2 SqlSessionFactory var5; 3 try { 4 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 5 var5 = this.build(parser.parse()); 6 } catch (Exception var14) { 7 throw ExceptionFactory.wrapException("Error building SqlSession.", var14); 8 } finally { 9 ErrorContext.instance().reset(); 10 11 try { 12 inputStream.close(); 13 } catch (IOException var13) { 14 } 15 16 } 17 18 return var5; 19 }
呼叫了XMLConfigBuilder的parse()方法,具體看下這個方法是幹什麼的
1 public Configuration parse() { 2 if (this.parsed) { 3 throw new BuilderException("Each XMLConfigBuilder can only be used once."); 4 } else { 5 this.parsed = true; 6 this.parseConfiguration(this.parser.evalNode("/configuration")); 7 return this.configuration; 8 } 9 } 10 11 private void parseConfiguration(XNode root) { 12 try { 13 this.propertiesElement(root.evalNode("properties")); 14 Properties settings = this.settingsAsProperties(root.evalNode("settings")); 15 this.loadCustomVfs(settings); 16 this.loadCustomLogImpl(settings); 17 this.typeAliasesElement(root.evalNode("typeAliases")); 18 this.pluginElement(root.evalNode("plugins")); 19 this.objectFactoryElement(root.evalNode("objectFactory")); 20 this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 21 this.reflectorFactoryElement(root.evalNode("reflectorFactory")); 22 this.settingsElement(settings); 23 this.environmentsElement(root.evalNode("environments")); 24 this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); 25 this.typeHandlerElement(root.evalNode("typeHandlers")); 26 this.mapperElement(root.evalNode("mappers")); 27 } catch (Exception var3) { 28 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); 29 } 30 }
可以看到XMLConfigBuilder的parse()方法來解析配置檔案的配置資訊,可以看到,解析properties標籤,settings,typeAliases別名等等一些資訊,並返回一個Configuration;後續我們會看到,Configuration是Mybatis很重要的一個核心類,包括了很多執行過程中的上下文資訊,我們這裡的配置資訊被載入到了這個類中
1 public SqlSessionFactory build(Configuration config) { 2 return new DefaultSqlSessionFactory(config); 3 }
通過呼叫SqlSessionFactory的build方法,返回了DefaultSqlSessionFactory物件,DefaultSqlSessionFactory是SqlSessionFactory的一個實現類
第三步:建立SqlSession
1 public SqlSession openSession() { 2 return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false); 3 }
1 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { 2 Transaction tx = null; 3 4 DefaultSqlSession var8; 5 try { 6 Environment environment = this.configuration.getEnvironment(); 7 TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment); 8 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); 9 Executor executor = this.configuration.newExecutor(tx, execType); 10 var8 = new DefaultSqlSession(this.configuration, executor, autoCommit); 11 } catch (Exception var12) { 12 this.closeTransaction(tx); 13 throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12); 14 } finally { 15 ErrorContext.instance().reset(); 16 } 17 18 return var8; 19 }
首先獲取到剛才從配置檔案解析到事務,TransactionFactory建立出Transaction, 我們知道sql執行一般都會用到事務提交、回滾等操作。
建立Executor物件,executor是mybatis的核心執行器,用於執行增刪改查操作。一二級快取都在執行器中進行處理。
最後得到了SqlSession物件了,DefaultSqlSession是SqlSession的一個實現。
第四步:獲取Mapper
呼叫SqlSession的getMapper方法
1 public <T> T getMapper(Class<T> type) { 2 return this.configuration.getMapper(type, this); 3 }
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession); }
然後可以發現呼叫了Configuration中的getMapper方法,再呼叫MapperRegistry(註冊mapper的類)獲取Mapper
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); } } }
可以看到呼叫了mapperProxyFactory的newInstance方法
1 protected T newInstance(MapperProxy<T> mapperProxy) { 2 return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); 3 } 4 5 public T newInstance(SqlSession sqlSession) { 6 MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); 7 return this.newInstance(mapperProxy); 8 }
可以看到我們在SqlSession呼叫getMapper時,實際上是通過jdk的動態代理來實現的,通過Proxy.newProxyInstance來建立mapper代理物件
第五步:執行mapper的增刪改查方法
通過第四步中,我們知道了getMapper獲取到了代理物件,也就是說Proxy.newProxyInstance最後一個引數mapperProxy物件類必定實現了InvocationHandler的invoke方法,
1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 2 try { 3 if (Object.class.equals(method.getDeclaringClass())) { 4 return method.invoke(this, args); 5 } 6 7 if (method.isDefault()) { 8 if (privateLookupInMethod == null) { 9 return this.invokeDefaultMethodJava8(proxy, method, args); 10 } 11 12 return this.invokeDefaultMethodJava9(proxy, method, args); 13 } 14 } catch (Throwable var5) { 15 throw ExceptionUtil.unwrapThrowable(var5); 16 } 17 18 MapperMethod mapperMethod = this.cachedMapperMethod(method); 19 return mapperMethod.execute(this.sqlSession, args); 20 }
invoke方法中首先判斷了是否是普通類或者判斷是否是預設方法,如果是普通類,執行其預設方法;如果是預設defaut方法,執行相應的java8或java9相應方法(這個沒具體瞭解,如果感興趣可以自己深入瞭解);我們們主要是看19行, mapperMethod的excute方法
1 public Object execute(SqlSession sqlSession, Object[] args) { 2 Object result; 3 Object param; 4 switch(this.command.getType()) { 5 case INSERT: 6 param = this.method.convertArgsToSqlCommandParam(args); 7 result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); 8 break; 9 case UPDATE: 10 param = this.method.convertArgsToSqlCommandParam(args); 11 result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); 12 break; 13 case DELETE: 14 param = this.method.convertArgsToSqlCommandParam(args); 15 result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); 16 break; 17 case SELECT: 18 if (this.method.returnsVoid() && this.method.hasResultHandler()) { 19 this.executeWithResultHandler(sqlSession, args); 20 result = null; 21 } else if (this.method.returnsMany()) { 22 result = this.executeForMany(sqlSession, args); 23 } else if (this.method.returnsMap()) { 24 result = this.executeForMap(sqlSession, args); 25 } else if (this.method.returnsCursor()) { 26 result = this.executeForCursor(sqlSession, args); 27 } else { 28 param = this.method.convertArgsToSqlCommandParam(args); 29 result = sqlSession.selectOne(this.command.getName(), param); 30 if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) { 31 result = Optional.ofNullable(result); 32 } 33 } 34 break; 35 case FLUSH: 36 result = sqlSession.flushStatements(); 37 break; 38 default: 39 throw new BindingException("Unknown execution method for: " + this.command.getName()); 40 } 41 42 if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { 43 throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); 44 } else { 45 return result; 46 } 47 }
這個就是我們核心執行增刪改查的方法,通過判斷 sql的執行型別,執行相應的方法,返回相應的結果
也許有人有疑問,是從哪裡解析到sql語句的呢,其實在解析配置檔案的時候就已經將sql解析到Configuration類物件中了,我們看下原始碼
1 private void parseConfiguration(XNode root) { 2 try { 3 this.propertiesElement(root.evalNode("properties")); 4 Properties settings = this.settingsAsProperties(root.evalNode("settings")); 5 this.loadCustomVfs(settings); 6 this.loadCustomLogImpl(settings); 7 this.typeAliasesElement(root.evalNode("typeAliases")); 8 this.pluginElement(root.evalNode("plugins")); 9 this.objectFactoryElement(root.evalNode("objectFactory")); 10 this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 11 this.reflectorFactoryElement(root.evalNode("reflectorFactory")); 12 this.settingsElement(settings); 13 this.environmentsElement(root.evalNode("environments")); 14 this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); 15 this.typeHandlerElement(root.evalNode("typeHandlers")); 16 this.mapperElement(root.evalNode("mappers")); 17 } catch (Exception var3) { 18 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); 19 } 20 }
我們看到在16行,有this.mapperElement(root.evalNode("mappers"))來解析mapper節點資訊
1 private void mapperElement(XNode parent) throws Exception { 2 if (parent != null) { 3 Iterator var2 = parent.getChildren().iterator(); 4 5 while(true) { 6 while(var2.hasNext()) { 7 XNode child = (XNode)var2.next(); 8 String resource; 9 if ("package".equals(child.getName())) { 10 resource = child.getStringAttribute("name"); 11 this.configuration.addMappers(resource); 12 } else { 13 resource = child.getStringAttribute("resource"); 14 String url = child.getStringAttribute("url"); 15 String mapperClass = child.getStringAttribute("class"); 16 XMLMapperBuilder mapperParser; 17 InputStream inputStream; 18 if (resource != null && url == null && mapperClass == null) { 19 ErrorContext.instance().resource(resource); 20 inputStream = Resources.getResourceAsStream(resource); 21 mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments()); 22 mapperParser.parse(); 23 } else if (resource == null && url != null && mapperClass == null) { 24 ErrorContext.instance().resource(url); 25 inputStream = Resources.getUrlAsStream(url); 26 mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments()); 27 mapperParser.parse(); 28 } else { 29 if (resource != null || url != null || mapperClass == null) { 30 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); 31 } 32 33 Class<?> mapperInterface = Resources.classForName(mapperClass); 34 this.configuration.addMapper(mapperInterface); 35 } 36 } 37 } 38 39 return; 40 } 41 } 42 }
通過原始碼我們可以看到在16行以下,XMLBuilderMapper的parse方法解析mapper的xml檔案,在這個方法中會將sql解析。
其實在執行器執行的時候會根據解析到的id與sql的對映去拿到執行的SQL,再交由底層jdbc來執行相應的sql語句