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工作原理(含部分源码)的更多相关文章

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

    1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap t ...

  2. 面试官:你分析过mybatis工作原理吗?

    Mybatis工作原理也是面试的一大考点,必须要对其非常清晰,这样才能怼回去.本文建立在Spring+SpringMVC+Mybatis整合的项目之上. 我将其工作原理分为六个部分: 读取核心配置文件 ...

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

    SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...

  4. 65、Spark Streaming:数据接收原理剖析与源码分析

    一.数据接收原理 二.源码分析 入口包org.apache.spark.streaming.receiver下ReceiverSupervisorImpl类的onStart()方法 ### overr ...

  5. sobel算子原理及opencv源码实现

    sobel算子原理及opencv源码实现 简要描述 sobel算子主要用于获得数字图像的一阶梯度,常见的应用和物理意义是边缘检测. 原理 算子使用两个33的矩阵(图1)算子使用两个33的矩阵(图1)去 ...

  6. MyBatis工作原理

    Mybatis工作原理: 我们的应用程序通过mybatis提供的api,增删改查方法来访问数据库,api底层调用了jdbc ,只不过mybatis对jdbc的封装是不完全封装,里面的sql语句需要我们 ...

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

    我们回忆一下<MyBatis框架的使用及源码分析(十) CacheExecutor,SimpleExecutor,BatchExecutor ,ReuseExecutor> , 这4个Ex ...

  8. MyBatis框架的使用及源码分析(九) Executor

    从<MyBatis框架的使用及源码分析(八) MapperMethod>文中我们知道执行Mapper的每一个接口方法,最后调用的是MapperMethod.execute方法.而当执行Ma ...

  9. 深入理解NIO(三)—— NIO原理及部分源码的解析

    深入理解NIO(三)—— NIO原理及部分源码的解析 欢迎回到淦™的源码看爆系列 在看完前面两个系列之后,相信大家对NIO也有了一定的理解,接下来我们就来深入源码去解读它,我这里的是OpenJDK-8 ...

  10. MyBatis、Spring、SpringMVC 源码下载地址

    MyBatis.Spring.SpringMVC 源码下载地址 github mybatis https://github.com/fengyu415/MyBatis-Learn.git spring ...

随机推荐

  1. centos6 free 和 centos 7的free 的差异与对比

    目录 一 centos6 free 常用参数和含义 centos6 free 命令示例 free 值讲解 计算公式 二 centos7 free 常用的参数 centos7 free 命令示例 计算公 ...

  2. tcpdump非常实用的抓包实例

    详细的文档见tcpdump高级过滤技巧 基本语法 ========过滤主机--------- 抓取所有经过 eth1,目的或源地址是 192.168.1.1 的网络数据# tcpdump -i eth ...

  3. springboot-7-配置druid数据源监视

    关于druid数据源的配置, 上个博客已经说过了,再说一遍吧 , 引入依赖 , 配置properties参数 , 编写servlet和filter提供页面监视 , 测试 1, 引入maven依赖 &l ...

  4. 搭建jenkins

    使用Jenkins配置Git+Maven的自动化构建 实现背景:Jenkins通过给定的代码地址URL,将代码拉取到其“宿主服务器”(就是Jenkins的安装位置),进行编译.打包和发布到容器中.在J ...

  5. 精度更高的double类型计算工具类(借助BigDecimal类型)

    /** * 提供精確的加法運算 * @param args */ public static double add(double v1, double v2) { BigDecimal b1 = ne ...

  6. ruby 数据sql操作

    ActiveRecord ActiveRecord 是 Rails 的 ORM 元件,負責與資料庫溝通,讓我們可以用物件導向的語法操作資料庫.在”打造 CRUD 應用程式”一章中提到的對應概念如下: ...

  7. chown -R 用户名:组名 ./ 及 chown用法介绍

    当我们在不通过yum(CentOS).apt-get(Ubuntu)来安装MySQL的时候,通常执行以下命令来改变目录的拥有者: [root@localhost ~]# chown -R mysql: ...

  8. Oracle相关

    where 条件中使用=进行限制时,可以返回一个记录集,即可以返回多个记录集

  9. windows10下mysql8.0.11忘记密码的解决办法

    首先输入 新开一个cmd窗口,登录mysql,刷新权限表 FLUSH PRIVILEGES; 经过我再次修改密码测试,只用下面这条语句就可以了 ALTER USER 'root'@'localhost ...

  10. 商城项目,java返回json数据,报错406

    前言: 项目结构为maven,搭建好架构,整合ssm,进行测试, 从数据库中查询数据,返回json数据,结果报错406 问题: 解决: 1,确定项目中json包是否存在(极大可能出于此) 2,处理器适 ...