您好,登錄后才能下訂單哦!
一、前言
在先了解mybatis查詢之前,先大致了解下以下代碼的為查詢做了哪些鋪墊,在這里我們要事先了解,myabtis會默認使用DefaultSqlSessionFactory作為sqlSessionFactory的實現(xiàn)類,而sqlSession的默認實現(xiàn)類為DefaultSqlSession
public static SqlSessionFactory getSessionFactory() throws IOException { Reader reader = Resources.getResourceAsReader("mybatis/mybatis-config.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); return builder.build(reader); }
獲取mybatis的配置文件流,交給sqlSessionFactoryBuilder進行解析,在這里只會涉及到一部分,具體,請大家移步mybatis源碼進行分析
解析大致步驟(以下說的配置文件,是mybatis配置數(shù)據(jù)庫連接信息的那個配置文件,不是mapper.xml文件)
解析配置文件的核心類在XMLConfigBuilder類中,
代碼如下
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 { // 解析properties節(jié)點信息 propertiesElement(root.evalNode("properties")); // 解析settings節(jié)點配置信息,其中二級緩存的總開關(guān)就是這里配置,當然mybatis默認是開啟的,詳細見Configuration類中的cacheEnabled屬性 Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); // 解析別名 typeAliasesElement(root.evalNode("typeAliases")); // 解析插件 pluginElement(root.evalNode("plugins")); // 這個節(jié)點一般不進行配置,myabtis也提供了一個默認實現(xiàn)類DefaultObjectFactory,除非自定義對象工廠實現(xiàn),才需配置 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")); // 處理java類型和數(shù)據(jù)庫類型的轉(zhuǎn)換,mybatis提供了許多默認實現(xiàn),詳細見TypeHandlerRegistry類,如果需自定義,可在此節(jié)點中進行配置 typeHandlerElement(root.evalNode("typeHandlers")); // 這也是一個核心的配置,mapperElement方法會對mapper.xml文件內(nèi)容進行一個解析 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
解析mapper.xml文件 的類XMLMapperBuilder,
public void parse() { // 也就是檢測配置文件配置的mapper節(jié)點有沒有加載到configuration類中,防止重復(fù)加載 if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); // 這個是綁定,mapper接口的,當處理成功,在configuration類中的mapper注冊器中,會添加一個mapper bindMapperForNamespace(); } parsePendingResultMaps();// 解析resultMap節(jié)點 parsePendingCacheRefs(); // 解析緩存節(jié)點,如<cache-ref/> parsePendingStatements();// 解析select|update等節(jié)點,并封裝成mappedStatement類 }
其中bindMapperForNamespace()方法的操作會導(dǎo)致以下結(jié)果
在configuration類中的MapperRegistry屬性中添加一個mapper,結(jié)果存儲在MapperRegistry類的一個map中,key為mapper的class value為一個代理工廠,負責產(chǎn)生mapper接口代理類。
二、查詢操作
當我們使用要使用mybatis進行查詢操作,無非大致就是兩種方式
/** * 通過mapper接口形式查詢數(shù)據(jù) */ @Test public void testSelectByMapper() throws IOException { SqlSession sqlSession = MybatisUtil.getSessionFactory().openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.selectByPrimaryKey(10); System.out.println(user); sqlSession.close(); } /** * 通過mapper接口的全限定名來進行查詢 * @throws IOException */ @Test public void testSelectByString() throws IOException { SqlSessionFactory sessionFactory = MybatisUtil.getSessionFactory(); SqlSession sqlSession = sessionFactory.openSession(); User user = sqlSession.selectOne("com.mybatis.demo.mybatisdemo.mapper.UserMapper.selectByPrimaryKey",10); System.out.println(user); sqlSession.close(); }
先來看第一種的分析,當我們點擊getMapper進去,它會去調(diào)用configuration類中g(shù)etMapper方法,就如上面介紹的解析出mapper節(jié)點后,會存儲在configuration類中的mapper注冊器中,
// defaultSqlSession類 public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } //configuration類 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } // 最終獲取mapper對象的方法,其主要是創(chuàng)建一個mapper代理工廠,我們都知道m(xù)ybatis的mapper接口是沒有實現(xiàn)類的, // 但是我們直接查詢是能獲取數(shù)據(jù),這里起作用的就是代理(采用的是jdk動態(tài)代理) public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
然后最終會經(jīng)過代理類MapperProxy的invoke方法,進行返回結(jié)果。在這里為了更好的能理解這個類,舉個例子,步驟如下
先創(chuàng)建一個接口,再使用一個類去實現(xiàn)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)); } }
執(zhí)行結(jié)果
User{id=null, username='李四', password='123', age=25, address='123', cellphone='1111111', registertime=Sat Mar 09 15:02:16 CST 2019}
由上面例子也可以看出最終結(jié)果是在invoke方法內(nèi),同理在mybatis中的MapperProxy的invoke方法也是負責返回最終結(jié)果的
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 交給了mpperMethod類去處理 final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
mapperMethod類中有兩個重要屬性,也就是它的內(nèi)部類,
也可以很清楚的了解到SqlCommand是用來存儲當前執(zhí)行方法的信息,如全限定名,還有該方法是屬于select|update|delete|insert|flush的哪一種,
對于methodSignature,則是紀錄該方法的一些信息,如返回值類型,參數(shù)等信息,paramNameResolver處理mapper接口中的參數(shù),下面代碼中有一個大致的介紹,以后會做一個詳細的介紹,這里只貼下代碼,只針對select做介紹
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) {// 返回值為void類型,但是有ResultHandler參數(shù),并且只能有一個,不然會報錯 executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) {// 處理返回值類型為集合類型或者數(shù)組類型 result = executeForMany(sqlSession, args); } else if (method.returnsMap()) {//處理返回值類型為Map類型 result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) {//返回值是否為cursor類型 result = executeForCursor(sqlSession, args); } else {//其他類型 Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }
這里只介紹select部分中常用返回多個實例對象的情況,也就是返回值為集合類型。
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; // 將mapper接口的參數(shù)名稱和args整成一個map結(jié)構(gòu),最后在會將值賦給sql中對應(yīng)的變量 // 在3.5版本中,默認的mapper結(jié)構(gòu)(假如沒使用@param注解或者處于jdk1.8版本中在代碼編譯時加上 -parameters 參數(shù)),結(jié)構(gòu)為 // param1 -> args[0] param2 -> args[1] // arg0 -> args[0] arg1 -> args[1] mybatis之前有些版本不是arg0 而是0 1 。。數(shù)字代替。 Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) {// 處理參數(shù)中帶有rowBounds參數(shù) RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else {// 其它情況 result = sqlSession.<E>selectList(command.getName(), param); } // issue #510 Collections & arrays support // 說明返回類型不是集合List類型,而是數(shù)組類型或其它集合類型。 if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; }
從上面知道,最終還是回到了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 標簽的解析結(jié)果
關(guān)于MappedStatement是怎么解析得來的,又是怎么存儲在Configuration中,可沿著以下路線進行查看
SqlSessionFactoryBuilder ---> build方法
XMLConfigBuilder ----> parse、parseConfiguration、mapperElement方法
XMLMapperBuilder ----> parse、parsePendingStatements、parseStatementNode
MapperBuilderAssistant ----> addMappedStatement
這里不做過多介紹,詳情見源碼
在selectList中executor的默認實現(xiàn)類是,SimpleExecutor,不過它還由Configuration類中的一個屬性決定最后的類型,
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor 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,其實這個屬性默認為true的, // 則由CachingExecutor進行包裝,也就是常說的裝飾設(shè)計模式 if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
最后回到selectList中來,由此可見,調(diào)用了CachingExecutor類中的query方法來執(zhí)行。
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 如果不為空,則啟用了二級緩存 Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
關(guān)于二級緩存,相信熟悉的都清楚,要想使用它,需要動兩個地方,
一個是mybatis的配置文件,將cacheEnabled設(shè)置為true,其實mybatis對這個屬性的默認值就是true,所以二級緩存的總開關(guān)是打開的。
第二個就是在mpper.xml文件中使用 <cache/> 或<cache-ref/>
這里對緩存不做介紹。
然后調(diào)用了BaseExecutor的query方法,這個類起的作用就是對一級緩存進行了操作,最終調(diào)用了SimpleExecutor的doQuery方法進行查詢。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,謝謝大家對億速云的支持。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。