1. 程式人生 > >Mybatis工作原理(含部分源碼)

Mybatis工作原理(含部分源碼)

context off params 判斷 new trace app name res

MyBatis的初始化

1、讀取配置文件,形成InputStream

String resource = "mybatis.xml";

// 加載mybatis的配置文件(它也加載關聯的映射文件)
InputStream inputStream = null;
try {
    inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
    e.printStackTrace();
}

2、解析XML配置文件,創建SqlSessionFacotry

sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// SqlSessionFactoryBuilder類
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        return build(parser.parse()); // 開始進行解析了 :)
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            inputStream.close();
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}

根據Configuration對象來創建SqlSession

MyBatis的SQL查詢流程

創建SqlSession

sqlSession = sessionFactory.openSession();

User user = sqlSession.selectOne("com.luo.dao.UserDao.getUserById", 1);

// DefaultSqlSession類
public <T> T selectOne(String statement, Object parameter) {
    
    // 使用的selectList
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
        return list.get(0);
    } else if (list.size() > 1) {
        // 多於1個結果時拋出異常
        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
        return null; // list.size() == 0
    }
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 根據mapper.xml文件中的某個SQL語句創建MappedStatement
        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();
    }
}

執行器在query()方法中,先查詢緩存判斷是否命中,命中則直接返回,否則從數據庫中查詢。

// CachingExecutor類
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 將參數與mapper中的sql合並
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 創建緩存的key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

// BaseExecutor類,創建緩存對象
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId()); // mapper文件中的id
    cacheKey.update(rowBounds.getOffset()); // 分頁偏移
    cacheKey.update(rowBounds.getLimit()); // 每頁的大小
    cacheKey.update(boundSql.getSql()); // sql語句
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // 模仿 DefaultParameterHandler 邏輯,記錄每個參數
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId()); // 每個SqlSessionFacotry的id
    }
    return cacheKey;
}

// CachingExecutor類
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, parameterObject, boundSql);
            @SuppressWarnings("unchecked")
            
            /**
             * PerpetualCache是默認二級緩存實現類
             * Map<Object, Object> cache = new HashMap<Object, Object>(); map的key就是CacheKey key
             * CacheKey中有個hashcode = multiplier * hashcode + 每個update(Object object)object的hashCode()
             * update()方法會向updateList添加元素
             * CacheKey重寫的equals()方法中先判斷hashcode是否相等
             * 然後用updateList每個對象的equals()判斷
             * 這兩個條件都滿足就說明緩存命中,cache.get(key)也就有值
             */
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
                // 二級緩存中沒有數據
                list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 底層調用queryFromDatabase
                tcm.putObject(cache, key, list); // 將結果放入二級緩存
            }
            return list;
        }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 底層調用queryFromDatabase
}

// BaseExecutor類,從數據庫查詢
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // 先往二級緩存中插入一個占位枚舉值
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        localCache.removeObject(key);
    }

    // 往二級緩存中寫入查詢結果
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

一級緩存和二級緩存

一級緩存和二級緩存的命中判斷依據是一樣的。

一級緩存是SqlSession級別的緩存,不可關閉。同一個SqlSession對象對象執行2遍相同的SQL查詢,第二遍查詢直接返回緩存結果。

二級緩存是mapper級別的緩存。不同的SqlSession對象執行兩次相同的SQL語句,第二次查詢直接返回二級緩存中的結果。MyBatis默認是不開啟二級緩存的。

未完,待續...

Mybatis工作原理(含部分源碼)