MyBatis核心流程
摘要
mybatis的核心流程,主要是对于主线的一个探索。目的是对于整个mybatis流程有个初步的印象
核心流程
核心流程搞懂:主线,涉及的模块不深究。再去基础支持层,再回来核心。
/**
* MyBatis API 的使用
* MyBatis 在启动的时候会做哪些操作?
* 1.加载全局配置文件
* 2.加载映射文件
* 3.加载的内容存储在了那个Java对象中? Configuration
* @throws Exception
*/
@Test
public void test1() throws Exception{
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSession中提供的 API方法来操作数据库
List<User> list = sqlSession.selectList("mapper.UserMapper.selectUserList");
for (User item: list
) {
System.out.println(item.toString());
}
// 5.关闭会话
sqlSession.close(); // 关闭session 清空一级缓存
}
mybatis在启动的时候做什么操作?
- 加载全局配置文件
- 加载映射文件
- 加载的内容存在什么java对象中 ---Configuration
下面的题目分别对应着代码中的四步来进行(第五步是关闭)
获取配置文件
// 1.使用的时候获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
这一步调用了io包下的Resources类的静态方法getResourceAsStream,里面调用了一个重载的getResourceAsStream方法,两个参数是类加载器和路径,其中类加载器为空。
/**
* io.Resources
* Returns a resource on the classpath as a Stream object
*
* @param loader The classloader used to fetch the resource
* @param resource The resource to find
* @return The resource
* @throws java.io.IOException If the resource cannot be found or read
*/
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
而后还是重载嵌套,其中的getClassLoaders加载了五种加载器,封装到一个类加载器数组中
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
return getResourceAsStream(resource, getClassLoaders(classLoader));
}
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}
而后通过classLoaderWrapper中的getResourceAsStream生成字节输入流。
/**
* io.ClassLoaderWrapper
* Try to get a resource from a group of classloaders
*
* @param resource - the resource to get
* @param classLoader - the classloaders to examine
* @return the resource or null
*/
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) { // 不为空就加载
// try to find the resource as passed
InputStream returnValue = cl.getResourceAsStream(resource);
// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue; // 猜测是变量放到里面节省内存空间?
}
}
}
return null;
}
这样就拿到了字节流。
加载解析配置文件
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
首先是SqlSessionFactory,可以看到他是一个接口,里面有若干的方法,比如selectOne等。而他的实现有两个,分别是 DefaultSqlSessionFactory
和 SqlSessionManager
。而我们没有直接通过DefaultSqlSessionFactory直接获取,而是通过Builder(建造者)模式进行获取。
为什么通过建造者获取呢?
1. SqlSessionFactory的目的是生产SqlSession的,当我们系统启动之后我们要获取工厂对象的时候,SqlSessionFactory工厂对象应该是单例。
2. 全局配置文件和映射文件也只需用在系统启动的时候完成加载操作。
由于1、2点,我们可以直接在工厂中直接完成配置文件的加载解析和工厂的创建。
但是这样会导致我们的职责不单一,所以用建造者模式来进行拆分。构建出这个复杂的对象。
SqlSessionFactoryBuilder的build方法重载了build方法,变成三个变量InputStream,String类型的环境,Properties三个变量,后两个为空。具体的解析给到了XMLConfigBuilder里。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 用于解析 mybatis-config.xml,同时创建了 Configuration 对象 >>
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析XML,最终返回一个 DefaultSqlSessionFactory >>
// 全局配置文件中的信息都被封装到了Configuration对象中
// 映射文件中的配置信息同样的也被封装到了Configuration对象中
// 一个具体的CRUD标签的信息被封装到MappedStatment对象中
return build(parser.parse());// 调用的下面的build
} 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中的。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
然后通过XMLConfigBuilder把字节流转为了XMLConfigBuilder对象(暂时不看),只需要知道他继承自BaseBuilder,在里面新建了一个Configuration实例,Configuration构造函数对配置进行了初始化,而后调用其中的parse方法,进行加载解析。保证只加载一遍,而后解析。
public Configuration parse() {
// 保证加载解析只能做一次
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// XPathParser,dom 和 SAX 都有用到 >>
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
再往后就是parseConfiguration,里面的参数XNode就是对应着我们的mapper-config.xml的内容,在里面的dtd文件中可以看到他们规定规定内容。
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 对于全局配置文件各种标签的解析
propertiesElement(root.evalNode("properties"));
// 解析 settings 标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 读取文件
loadCustomVfs(settings);
// 日志设置
loadCustomLogImpl(settings);
// 类型别名
typeAliasesElement(root.evalNode("typeAliases"));
// 插件
pluginElement(root.evalNode("plugins"));
// 用于创建对象
objectFactoryElement(root.evalNode("objectFactory"));
// 用于对对象进行加工
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 反射工具箱
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// settings 子标签赋值,默认值就是在这里提供的 >>
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 创建了数据源 >>
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析引用的Mapper映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
以propertiesElement举例,可以看到他的作用是把提取到的信息放到Configuration中的variables中去。
private void propertiesElement(XNode context) throws Exception {
if (context != null) { // 意味着这个属性可以没有
// 创建了一个 Properties 对象,后面可以用到
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
// System.out.println(resource+": adsf");
// System.out.println(url +": adsf");
if (resource != null && url != null) {
// url 和 resource 不能同时存在
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
// 加载resource或者url属性中指定的 properties 文件
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
// 和 Configuration中的 variables 属性合并
defaults.putAll(vars);
}
// for (Map.Entry item: defaults.entrySet()
// ) {
// System.out.println(item.getKey()+":"+item.getValue());
// }
// 更新对应的属性信息
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
这样我们就知道了我们解析的配置文件都存到什么地方(Configuration),怎么存的(Mapper)了。
打开session
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
SqlSessionFactory是一个接口,他的实现类是DefaultSqlSessionFactory。里面实现openSession的方法如下:
@Override
public SqlSession openSession() { // 执行器才是真和数据库操作,干活的getDefaultExecutorType
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
openSessionFromDataSource代码如下,通过事务控制,创建了一个执行器和一个DefaultSqlSession类的实例。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根据事务工厂和默认的执行器类型,创建执行器 >>
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
执行器executor才是真正的完成数据库的操作。点进去newExecutor,里面负责配置执行器的类型,和二级缓存还有插件。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
// 默认 SimpleExecutor
executor = new SimpleExecutor(this, transaction);
}
// 二级缓存开关,settings 中的 cacheEnabled 默认是 true
// 映射文件中的<cache>标签只是创建了Cache对象,
// cacheEnabled才会真正对Excutor缓存的增强。
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 植入插件的逻辑,至此,四大对象已经全部拦截完毕
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
执行sqlSession中的方法
// 4.通过SqlSession中提供的 API方法来操作数据库
List<User> list = sqlSession.selectList("mapper.UserMapper.selectUserList");
这里指定了全路径,然后就属于sqlSession的方法就执行完成了,程序把具体的执行过程交给了执行器executor进行。
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// statement就是我们的全路径名?
MappedStatement ms = configuration.getMappedStatement(statement);
// 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰
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();
}
}
执行器是一个接口有两个实现方法,其中第一个是BaseExecutor,另一个是CachingExecutor,先看CachingExecutor,因为整体流程是有cache就用cache,没有就是base。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取SQL
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建CacheKey:什么样的SQL是同一条SQL? >>
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
query具体的执行是看到有缓存就用二级缓存,如果有二级缓存,就直接把缓存中的数据加载进来。如果没有的话,就执行BaseExecutor中的query,也就是查询数据库。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
// cache 对象是在哪里创建的? XMLMapperBuilder类 xmlconfigurationElement()
// 由 <cache> 标签决定
if (cache != null) {
// flushCache="true" 清空一级二级缓存 >>
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 获取二级缓存
// 缓存通过 TransactionalCacheManager、TransactionalCache 管理
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 写入二级缓存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 走到 SimpleExecutor | ReuseExecutor | BatchExecutor
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
一级缓存查询的逻辑:先查询一级缓存,如果一级缓存中没有结果(list为空)就进行真正的数据库查询
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 异常体系之 ErrorContext
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// flushCache="true"时,即使是查询,也清空一级缓存
clearLocalCache();
}
List<E> list;
try {
// 防止递归查询重复处理缓存 queryStack代表深度
queryStack++;
// 查询一级缓存
// ResultHandler 和 ResultSetHandler的区别 localCache.getObject(key)是一级缓存中存在的数据
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 一级缓存也没有的话,真正的查询流程
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
为什么先二级缓存再一级缓存而不是先一级缓存?
二级缓存是进程级别的,一级缓存是会话级别的(session),生命周期长短是不一样的,二级缓存只要服务没重启就在,一级缓存一次会话结束就无了。二级缓存命中的概率是99%,一级缓存命中可能只有1%概率。
MyBatis核心流程的更多相关文章
- 深入详解Mybatis的架构原理与6大核心流程
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,目前在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架. 如果你想要进入一线大厂,能够熟练使用 MyBatis 开发已经 ...
- springmvc 运行原理 Spring ioc的实现原理 Mybatis工作流程 spring AOP实现原理
SpringMVC的工作原理图: SpringMVC流程 . 用户发送请求至前端控制器DispatcherServlet. . DispatcherServlet收到请求调用HandlerMappin ...
- MyBatis 核心配置综述之Executor
目录 MyBatis四大组件之 Executor执行器 Executor的继承结构 Executor创建过程以及源码分析 Executor接口的主要方法 Executor 的现实抽象 上一篇我们对Sq ...
- MyBatis 核心配置综述之StatementHandler
目录 MyBatis 核心配置综述之StatementHandler MyBatis 四大组件之StatementHandler StatementHandler 的基本构成 StatementHan ...
- MyBatis 核心配置综述之 ParameterHandler
目录 ParameterHandler 简介 ParameterHandler 创建 ParameterHandler 中的参数从何而来 ParameterHandler 解析 MyBatis 四大核 ...
- Mybatis工作流程及其原理与解析
Mybatis简介: MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBat ...
- MyBatis运行流程及入门第一个程序
1. mybatis是什么? MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并 ...
- MyBatis——MyBatis开发流程
创建项目(IDEA中) 在IDEA中创建 MyBatis项目,详细流程如下: 这里有一点需要注意,我们创建的是Maven项目,如果大家以前没有配置过Maven环境的话,在创建完项目之后,需要配置一下M ...
- Mybatis基础:Mybatis映射配置文件,Mybatis核心配置文件,Mybatis传统方式开发
一.Mybatis快速入门 1.1 框架介绍 框架是一款半成品软件,我们可以基于这个半成品软件继续开发,来完成我们个性化的需求! 框架:大工具,我们利用工具,可以快速开发项目 (mybatis也是一个 ...
- paip.刮刮卡砸金蛋抽奖概率算法跟核心流程.
paip.刮刮卡砸金蛋抽奖概率算法跟核心流程. #---抽奖算法需要满足的需求如下: 1 #---抽奖核心流程 1 #---问题???更好的算法 2 #---实际使用的扩展抽奖算法(带奖品送完判断和每 ...
随机推荐
- TCP的Keep-Alive机制:链接存在但是没有数据传输,内核怎么处理
服务端/客户端会定期发送探测报文来检测客户端的存活状态. 由三个内核参数控制: 首次发送探测报文时间:net.ipv4.tcp_keepalive_time有报文传输时重置 探测报文的发送间隔:net ...
- 部署 rsyslog 日志服务
ubuntu 服务端 + Centos 客户端 参考文档: ubuntu 20.04 搭建 rsyslog 服务器 CentOS7下搭建Rsyslog Server记录远程主机系统日志
- 从module_init看内核模块
开篇 module_init是linux内核提供的一个宏, 可以用来在编写内核模块时注册一个初始化函数, 当模块被加载的时候, 内核负责执行这个初始化函数. 在编写设备驱动程序时, 使用这个宏看起来理 ...
- Xshell远程连接虚拟机及连接故障排查
用Xshell 远程连接虚拟机 如果按前面博客装好虚拟机,会发现刚装好的虚拟机直接连Xshell连不上,宿主机也ping不通虚拟机,这就需要修改VMware的默认网络配置 修改步骤: 1.在VMwar ...
- 树莓派烧录系统并在无外接屏幕的情况下连接VNC
上个月老板给了块树莓派3B,开心坏了,在咸鱼上掏了很多零件,花了一段时间做出了一个二驱动的智能小车,但是觉得小车太小,就在又在咸鱼上掏了个四区的地盘,但是在拆卸的过程中,发现树莓派WIFI没有了, ...
- 使用LabVIEW 实现物体识别、图像分割、文字识别、人脸识别等深度视觉
前言 哈喽,各位朋友们,这里是virobotics(仪酷智能),这两天有朋友私信问之前给大家介绍的工具包都可以实现什么功能,最新的一些模型能否使用工具包加载,今天就给大家介绍一下博主目前使用工具包已经 ...
- 手把手教你使用人工智能生成游戏 3D 素材
引言 生成式 AI 已成为游戏开发中艺术工作流的重要组成部分.然而,正如我在 之前的文章 中描述的,从文本到 3D 的实用性仍落后于 2D.不过,这种情况正在改变.本文我们将重新审视 3D 素材生成的 ...
- 面霸的自我修养:synchronized专题
王有志,一个分享硬核Java技术的互金摸鱼侠 加入Java人的提桶跑路群:共同富裕的Java人 今天是<面霸的自我修养>的第3弹,内容是Java并发编程中至关重要的关键字synchroni ...
- QA|重写了元素定位后报错xx object has no attribute 'find_element'|网页计算器自动化测试实战
代码如下: 1 # basepage.py 2 3 from selenium import webdriver 4 5 6 class BasePage(): 7 """ ...
- Unity 游戏开发、01 基础知识大全、简单功能脚本实现
2.3 窗口布局 Unity默认窗口布局 Hierarchy 层级窗口 Scene 场景窗口,3D视图窗口 Game 游戏播放窗口 Inspector 检查器窗口,属性窗口 Project 项目窗口 ...