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默认是不开启二级缓存的。

未完,待续...

原文地址:https://www.cnblogs.com/liycode/p/9357624.html

时间: 2024-08-25 05:01:23

Mybatis工作原理(含部分源码)的相关文章

ThreadLocal 工作原理、部分源码分析

1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocalMap 静态内部类维护了一个Entry 数组 private Entry[] table; 查看Entry 源码,它维护了两个属性,ThreadLocal 对象 与一个Object static class Entry extends W

你分析过mybatis工作原理吗?

Mybatis工作原理也是面试的一大考点,必须要对其非常清晰,这样才能怼回去.本文建立在Spring+SpringMVC+Mybatis整合的项目之上. 我将其工作原理分为六个部分: 读取核心配置文件并返回InputStream流对象. 根据InputStream流对象解析出Configuration对象,然后创建SqlSessionFactory工厂对象 根据一系列属性从SqlSessionFactory工厂中创建SqlSession 从SqlSession中调用Executor执行数据库操作

SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转

SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-convert.html 目录 前言 现象 源码分析 实例讲解 关于配置 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-in

MyBatis工作原理

Mybatis工作原理: 我们的应用程序通过mybatis提供的api,增删改查方法来访问数据库,api底层调用了jdbc ,只不过mybatis对jdbc的封装是不完全封装,里面的sql语句需要我们自己来写,sql语句写在映射文件mapper.xml中的,而映射文件是注册在主配置文件mybatis.xml中的,主配置文件是通过api加载进来的 ,// 1.加载主配置文件 InputStream inputStream = Resources.getResourceAsStream("mybat

【从零之六&amp;完结】android口语对话系统(含全部源码)

做了一个半月终于完成了,下面这个就是我参考Olympus/RavenClaw系统编写的对话管理系统,目前实现了一个简单的航班查询,部分截图 目前能实现的功能: 1.航班查询,具体的航班信息是我自己编的,下一步可以写个爬虫把真实的数据爬下来.编的数据放到了res/raw/flightinfo.txt中.航班查询就设定了三个要素,时间,起始地和目的地,想更全面的话可以增加任务树的agent即可. 2.在MainActivity中可以语音打开已安装的应用程序,这在前一篇博文也介绍过了. 3.别的功能就

MyBatis框架的使用及源码分析(十一) StatementHandler

我们回忆一下<MyBatis框架的使用及源码分析(十) CacheExecutor,SimpleExecutor,BatchExecutor ,ReuseExecutor> , 这4个Excecutor执行sql操作的最终都调用了StatementHandler 来执行,我们拿SimpleExecutor来看: public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement st

Spark2.1内部原理剖析与源码阅读、程序设计与企业级应用案例视频教程

38套大数据,云计算,架构,数据分析师,Hadoop,Spark,Storm,Kafka,人工智能,机器学习,深度学习,项目实战视频教程 视频课程包含: 38套大数据和人工智能精品高级课包含:大数据,云计算,架构,数据挖掘实战,实时推荐系统实战,电视收视率项目实战,实时流统计项目实战,离线电商分析项目实战,Spark大型项目实战用户分析,智能客户系统项目实战,Linux基础,Hadoop,Spark,Storm,Docker,Mapreduce,Kafka,Flume,OpenStack,Hiv

Java中HashMap底层实现原理(JDK1.8)源码分析

这几天学习了HashMap的底层实现,但是发现好几个版本的,代码不一,而且看了Android包的HashMap和JDK中的HashMap的也不是一样,原来他们没有指定JDK版本,很多文章都是旧版本JDK1.6.JDK1.7的.现在我来分析一哈最新的JDK1.8的HashMap及性能优化. 在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效

Springmvc+mybatis+shiro+lucene+rest+bootstrap源码下载

>>>源码地址下载>>> 1. 使用阿里巴巴Druid连接池(高效.功能强大.可扩展性好的数据库连接池.监控数据库访问性能.支持Common-Logging.Log4j和JdkLog,监控数据库访问)2. 提供高并发JMS消息处理机制3. 所有功能模块化.所有模块服务化.所有服务原子化的方式,提供可拓展的服务模型,使程序稳定运行,永不宕机4. 提供Wink Rest.Webservice服务,故可作为独立服务平台部署 框架整合: Springmvc + Mybatis

SpringMVC关于json、xml自动转换的原理研究[附带源码分析]

本文讨论SpringMVC关于json.xml自动转换的原理. 实现这个功能只需要三个配置 1.springmvc配置文件 dispatcher-servlet.xml中的关键配置如下 <mvc:resources location="/static/" mapping="/static/**"/> <!-- 配置包扫描器 --> <context:component-scan base-package="com.winner