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

Posted liycode

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis工作原理(含部分源码)相关的知识,希望对你有一定的参考价值。

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工作原理(含部分源码)的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis:MyBatis核心组件介绍原理解析和源码解读

手把手教你阅读mybatis核心源码,掌握底层工作原理与设计思想

手把手教你阅读mybatis核心源码,掌握底层工作原理与设计思想

MyBatis核心源码深度剖析工作机制和实现原理

Mybatis还有这种操作?浅析为什么要看源码

MyBatis 源码分析系列文章合集