mybatis查詢語句的背後

 轉載請註明出處。。。java

1、前言

在先了解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

 2、查詢操做

當咱們使用要使用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方法進行查詢。

相關文章
相關標籤/搜索