轉載請註明出處。。。java
在先了解mybatis查詢以前,先大體瞭解下如下代碼的爲查詢作了哪些鋪墊,在這裏咱們要事先了解,myabtis會默認使用DefaultSqlSessionFactory做爲sqlSessionFactory的實現類,而sqlSession的默認實現類爲DefaultSqlSessionsql
1 public static SqlSessionFactory getSessionFactory() throws IOException { 2 Reader reader = Resources.getResourceAsReader("mybatis/mybatis-config.xml"); 3 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 4 return builder.build(reader); 5 }
獲取mybatis的配置文件流,交給sqlSessionFactoryBuilder進行解析,在這裏只會涉及到一部分,具體,請你們移步mybatis源碼進行分析數據庫
解析大體步驟(如下說的配置文件,是mybatis配置數據庫鏈接信息的那個配置文件,不是mapper.xml文件)設計模式
解析配置文件的核心類在XMLConfigBuilder類中,數組
代碼以下緩存
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 } 9 10 private void parseConfiguration(XNode root) { 11 try { 12 // 解析properties節點信息 13 propertiesElement(root.evalNode("properties")); 14 // 解析settings節點配置信息,其中二級緩存的總開關就是這裏配置,固然mybatis默認是開啓的,詳細見Configuration類中的cacheEnabled屬性 15 Properties settings = settingsAsProperties(root.evalNode("settings")); 16 loadCustomVfs(settings); 17 loadCustomLogImpl(settings); 18 // 解析別名 19 typeAliasesElement(root.evalNode("typeAliases")); 20 // 解析插件 21 pluginElement(root.evalNode("plugins")); 22 // 這個節點通常不進行配置,myabtis也提供了一個默認實現類DefaultObjectFactory,除非自定義對象工廠實現,才需配置 23 objectFactoryElement(root.evalNode("objectFactory")); 24 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 25 reflectorFactoryElement(root.evalNode("reflectorFactory")); 26 settingsElement(settings); 27 // read it after objectFactory and objectWrapperFactory issue #631 28 environmentsElement(root.evalNode("environments")); 29 databaseIdProviderElement(root.evalNode("databaseIdProvider")); 30 // 處理java類型和數據庫類型的轉換,mybatis提供了許多默認實現,詳細見TypeHandlerRegistry類,若是需自定義,可在此節點中進行配置 31 typeHandlerElement(root.evalNode("typeHandlers")); 32 // 這也是一個核心的配置,mapperElement方法會對mapper.xml文件內容進行一個解析 33 mapperElement(root.evalNode("mappers")); 34 } catch (Exception e) { 35 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); 36 } 37 }
解析mapper.xml文件 的類XMLMapperBuilder,session
1 public void parse() { 2 // 也就是檢測配置文件配置的mapper節點有沒有加載到configuration類中,防止重複加載 3 if (!configuration.isResourceLoaded(resource)) { 4 configurationElement(parser.evalNode("/mapper")); 5 configuration.addLoadedResource(resource); 6 // 這個是綁定,mapper接口的,當處理成功,在configuration類中的mapper註冊器中,會添加一個mapper 7 bindMapperForNamespace(); 8 } 9 10 parsePendingResultMaps();// 解析resultMap節點 11 parsePendingCacheRefs(); // 解析緩存節點,如<cache-ref/> 12 parsePendingStatements();// 解析select|update等節點,並封裝成mappedStatement類 13 }
其中bindMapperForNamespace()方法的操做會致使如下結果mybatis
在configuration類中的MapperRegistry屬性中添加一個mapper,結果存儲在MapperRegistry類的一個map中,key爲mapper的class value爲一個代理工廠,負責產生mapper接口代理類。app
當咱們使用要使用mybatis進行查詢操做,無非大體就是兩種方式ide
1 /** 2 * 經過mapper接口形式查詢數據 3 */ 4 @Test 5 public void testSelectByMapper() throws IOException { 6 SqlSession sqlSession = MybatisUtil.getSessionFactory().openSession(); 7 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 8 User user = mapper.selectByPrimaryKey(10); 9 System.out.println(user); 10 sqlSession.close(); 11 } 12 13 /** 14 * 經過mapper接口的全限定名來進行查詢 15 * @throws IOException 16 */ 17 @Test 18 public void testSelectByString() throws IOException { 19 SqlSessionFactory sessionFactory = MybatisUtil.getSessionFactory(); 20 SqlSession sqlSession = sessionFactory.openSession(); 21 User user = sqlSession.selectOne("com.mybatis.demo.mybatisdemo.mapper.UserMapper.selectByPrimaryKey",10); 22 System.out.println(user); 23 sqlSession.close(); 24 }
先來看第一種的分析,當咱們點擊getMapper進去,它會去調用configuration類中getMapper方法,就如上面介紹的解析出mapper節點後,會存儲在configuration類中的mapper註冊器中,
1 // defaultSqlSession類 2 public <T> T getMapper(Class<T> type) { 3 return configuration.<T>getMapper(type, this); 4 } 5 //configuration類 6 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 7 return mapperRegistry.getMapper(type, sqlSession); 8 } 9 // 最終獲取mapper對象的方法,其主要是建立一個mapper代理工廠,咱們都知道mybatis的mapper接口是沒有實現類的, 10 // 可是咱們直接查詢是能獲取數據,這裏起做用的就是代理(採用的是jdk動態代理) 11 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 12 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 13 if (mapperProxyFactory == null) { 14 throw new BindingException("Type " + type + " is not known to the MapperRegistry."); 15 } 16 try { 17 return mapperProxyFactory.newInstance(sqlSession); 18 } catch (Exception e) { 19 throw new BindingException("Error getting mapper instance. Cause: " + e, e); 20 } 21 }
而後最終會通過代理類MapperProxy的invoke方法,進行返回結果。在這裏爲了更好的能理解這個類,舉個例子,步驟以下
先建立一個接口,再使用一個類去實現java的jdk代理的核心接口InvocationHandler,
public interface TestMapper { User findByUserId(Integer id); }
public class MapperProxyTest implements InvocationHandler { private Class<?> target; public MapperProxyTest(Class<?> target) { this.target = target; } public Object getProxyInstances(){ return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{target},this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } User user = new User(); user.setPassword("123"); user.setUsername("李四"); user.setAddress("123"); user.setRegistertime(new Date()); user.setCellphone("1111111"); user.setAge(25); return user; } }
測試類
public class MapperTest { public static void main(String[] args){ MapperProxyTest proxyTest = new MapperProxyTest(TestMapper.class); TestMapper testMapper = (TestMapper) proxyTest.getProxyInstances(); System.out.println(testMapper.findByUserId(10)); } }
執行結果
User{id=null, username='李四', password='123', age=25, address='123', cellphone='1111111', registertime=Sat Mar 09 15:02:16 CST 2019}
由上面例子也能夠看出最終結果是在invoke方法內,同理在mybatis中的MapperProxy的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 } else if (isDefaultMethod(method)) { 6 return invokeDefaultMethod(proxy, method, args); 7 } 8 } catch (Throwable t) { 9 throw ExceptionUtil.unwrapThrowable(t); 10 } 11 // 交給了mpperMethod類去處理 12 final MapperMethod mapperMethod = cachedMapperMethod(method); 13 return mapperMethod.execute(sqlSession, args); 14 }
mapperMethod類中有兩個重要屬性,也就是它的內部類,
也能夠很清楚的瞭解到SqlCommand是用來存儲當前執行方法的信息,如全限定名,還有該方法是屬於select|update|delete|insert|flush的哪種,
對於methodSignature,則是紀錄該方法的一些信息,如返回值類型,參數等信息,paramNameResolver處理mapper接口中的參數,下面代碼中有一個大體的介紹,之後會作一個詳細的介紹,這裏只貼下代碼,只針對select作介紹
1 public Object execute(SqlSession sqlSession, Object[] args) { 2 Object result; 3 switch (command.getType()) { 4 case INSERT: { 5 Object param = method.convertArgsToSqlCommandParam(args); 6 result = rowCountResult(sqlSession.insert(command.getName(), param)); 7 break; 8 } 9 case UPDATE: { 10 Object param = method.convertArgsToSqlCommandParam(args); 11 result = rowCountResult(sqlSession.update(command.getName(), param)); 12 break; 13 } 14 case DELETE: { 15 Object param = method.convertArgsToSqlCommandParam(args); 16 result = rowCountResult(sqlSession.delete(command.getName(), param)); 17 break; 18 } 19 case SELECT: 20 if (method.returnsVoid() && method.hasResultHandler()) {// 返回值爲void類型,可是有ResultHandler參數,而且只能有一個,否則會報錯 21 executeWithResultHandler(sqlSession, args); 22 result = null; 23 } else if (method.returnsMany()) {// 處理返回值類型爲集合類型或者數組類型 24 result = executeForMany(sqlSession, args); 25 } else if (method.returnsMap()) {//處理返回值類型爲Map類型 26 result = executeForMap(sqlSession, args); 27 } else if (method.returnsCursor()) {//返回值是否爲cursor類型 28 result = executeForCursor(sqlSession, args); 29 } else {//其餘類型 30 Object param = method.convertArgsToSqlCommandParam(args); 31 result = sqlSession.selectOne(command.getName(), param); 32 if (method.returnsOptional() && 33 (result == null || !method.getReturnType().equals(result.getClass()))) { 34 result = Optional.ofNullable(result); 35 } 36 } 37 break; 38 case FLUSH: 39 result = sqlSession.flushStatements(); 40 break; 41 default: 42 throw new BindingException("Unknown execution method for: " + command.getName()); 43 } 44 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { 45 throw new BindingException("Mapper method '" + command.getName() 46 + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); 47 } 48 return result; 49 }
這裏只介紹select部分中經常使用返回多個實例對象的狀況,也就是返回值爲集合類型。
1 private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { 2 List<E> result; 3 // 將mapper接口的參數名稱和args整成一個map結構,最後在會將值賦給sql中對應的變量 4 // 在3.5版本中,默認的mapper結構(假如沒使用@param註解或者處於jdk1.8版本中在代碼編譯時加上 -parameters 參數),結構爲 5 // param1 -> args[0] param2 -> args[1] 6 // arg0 -> args[0] arg1 -> args[1] mybatis以前有些版本不是arg0 而是0 1 。。數字代替。 7 Object param = method.convertArgsToSqlCommandParam(args); 8 if (method.hasRowBounds()) {// 處理參數中帶有rowBounds參數 9 RowBounds rowBounds = method.extractRowBounds(args); 10 result = sqlSession.<E>selectList(command.getName(), param, rowBounds); 11 } else {// 其它狀況 12 result = sqlSession.<E>selectList(command.getName(), param); 13 } 14 // issue #510 Collections & arrays support 15 // 說明返回類型不是集合List類型,而是數組類型或其它集合類型。 16 if (!method.getReturnType().isAssignableFrom(result.getClass())) { 17 if (method.getReturnType().isArray()) { 18 return convertToArray(result); 19 } else { 20 return convertToDeclaredCollection(sqlSession.getConfiguration(), result); 21 } 22 } 23 return result; 24 }
從上面知道,最終仍是回到了sqlSession裏面,
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
MappedStatement存儲的其實就是對每個select|update|delete|insert 標籤的解析結果
關於MappedStatement是怎麼解析得來的,又是怎麼存儲在Configuration中,可沿着如下路線進行查看
SqlSessionFactoryBuilder ---> build方法
XMLConfigBuilder ----> parse、parseConfiguration、mapperElement方法
XMLMapperBuilder ----> parse、parsePendingStatements、parseStatementNode
MapperBuilderAssistant ----> addMappedStatement
這裏不作過多介紹,詳情見源碼
在selectList中executor的默認實現類是,SimpleExecutor,不過它還由Configuration類中的一個屬性決定最後的類型,
1 public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 2 executorType = executorType == null ? defaultExecutorType : executorType; 3 executorType = executorType == null ? ExecutorType.SIMPLE : executorType; 4 Executor executor; 5 if (ExecutorType.BATCH == executorType) { 6 executor = new BatchExecutor(this, transaction); 7 } else if (ExecutorType.REUSE == executorType) { 8 executor = new ReuseExecutor(this, transaction); 9 } else { 10 executor = new SimpleExecutor(this, transaction); 11 } 12 // 若是cacheEnabled爲true,其實這個屬性默認爲true的, 13 // 則由CachingExecutor進行包裝,也就是常說的裝飾設計模式 14 if (cacheEnabled) { 15 executor = new CachingExecutor(executor); 16 } 17 executor = (Executor) interceptorChain.pluginAll(executor); 18 return executor; 19 }
最後回到selectList中來,因而可知,調用了CachingExecutor類中的query方法來執行。
1 @Override 2 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) 3 throws SQLException { 4 // 若是不爲空,則啓用了二級緩存 5 Cache cache = ms.getCache(); 6 if (cache != null) { 7 flushCacheIfRequired(ms); 8 if (ms.isUseCache() && resultHandler == null) { 9 ensureNoOutParams(ms, boundSql); 10 @SuppressWarnings("unchecked") 11 List<E> list = (List<E>) tcm.getObject(cache, key); 12 if (list == null) { 13 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 14 tcm.putObject(cache, key, list); // issue #578 and #116 15 } 16 return list; 17 } 18 } 19 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 20 }
關於二級緩存,相信熟悉的都清楚,要想使用它,須要動兩個地方,
一個是mybatis的配置文件,將cacheEnabled設置爲true,其實mybatis對這個屬性的默認值就是true,因此二級緩存的總開關是打開的。
第二個就是在mpper.xml文件中使用 <cache/> 或<cache-ref/>
這裏對緩存不作介紹。
而後調用了BaseExecutor的query方法,這個類起的做用就是對一級緩存進行了操做,最終調用了SimpleExecutor的doQuery方法進行查詢。