面试:谈谈你对MyBatis执行过程之SQL执行过程理解,设计模式面试题java

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

经过MyBatis初始化加载Sql执行过程所需的信息后,我们就可以通过 SqlSessionFactory 对象得倒忙SqlSession ,然后执行 SQL 语句了,接下来看看Sql执行具体过程,SQL大致执行流程图如下所示:

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

接下来我们来看看每个执行链路中的具体执行过程,

SqlSession

==============

SqlSession 是 MyBatis 暴露给外部使用的统一接口层,通过 SqlSessionFactory 创建,且其包含和数据库打交道所有操作接口。

下面通过时序图描述 SqlSession 对象的创建流程:

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

在生成 SqlSession 的同时,基于 executorType 初始化好 Executor 实现类。

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 {

executor = new SimpleExecutor(this, transaction);

} if (cacheEnabled) {

executor = new CachingExecutor(executor);

} executor = (Executor) interceptorChain.pluginAll(executor); return executor;

}

最顶层的SqlSession接口已生成,那我们可以来看看sql的执行过程下一步是怎样的呢? 怎样使用代理类 MapperProxy 。

MapperProxy

===============

MapperProxy 是 Mapper 接口与SQL 语句映射的关键,通过 MapperProxy 可以让对应的 SQL 语句跟接口进行绑定的,具体流程如下:

  • MapperProxy 代理类生成流程

  • MapperProxy 代理类执行操作

MapperProxy 代理类生成流程

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

其中, MapperRegistry 是 Configuration 的一个属性,在解析配置时候会在 MapperRegistry 中缓存了 MapperProxyFactory 的 knownMappers 变量 Map 集合。

MapperRegistry 会根据 mapper 接口类型获取已缓存的 MapperProxyFactory , MapperProxyFactory 会基于 SqlSession 来生成 MapperProxy 代理对象,

public T getMapper(Class type, SqlSession sqlSession) {

MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);

if (mapperProxyFactory == null) {

throw new BindingException(“Type " + type + " is not known to the MapperRegistry.”);

} else {

try {

return mapperProxyFactory.newInstance(sqlSession);

} catch (Exception var5) {

throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);

} } }

当调用 SqlSession 接口时, MapperProxy 怎么是实现的呢? MyBatis 的 Mapper 接口 是通过动态代理实现的,调用 Mapper 接口的任何方法都会执行 MapperProxy::invoke() 方法,

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

try {

//Object类型执行

if (Object.class.equals(method.getDeclaringClass())) {

return method.invoke(this, args);

}

//接口默认方法执行

if (method.isDefault()) {

if (privateLookupInMethod == null) {

return this.invokeDefaultMethodJava8(proxy, method, args);

}

return this.invokeDefaultMethodJava9(proxy, method, args);

}

} catch (Throwable var5) {

throw ExceptionUtil.unwrapThrowable(var5);

}

MapperMethod mapperMethod = this.cachedMapperMethod(method);

return mapperMethod.execute(this.sqlSession, args);

}

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

但最终会调用倒忙mapperMethod::execute() 方法执行,主要是判断是 INSERT 、 UPDATE 、 DELETE 、 SELECT 语句去操作,其中如果是查询的话,还会判断返回值的类型。

public Object execute(SqlSession sqlSession, Object[] args) {

Object result; Object param; switch(this.command.getType()) {

case INSERT: param = this.method.convertArgsToSqlCommandParam(args);

result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));

break;

case UPDATE: param = this.method.convertArgsToSqlCommandParam(args);

result = this.rowCountResult(sqlSession.update(this.command.getName(), param));

break;

case DELETE: param = this.method.convertArgsToSqlCommandParam(args);

result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));

break;

case SELECT: if (this.method.returnsVoid() && this.method.hasResultHandler()) {

this.executeWithResultHandler(sqlSession, args);

result = null;

} else if (this.method.returnsMany()) {

result = this.executeForMany(sqlSession, args);

} else if (this.method.returnsMap()) {

result = this.executeForMap(sqlSession, args);

} else if (this.method.returnsCursor()) {

result = this.executeForCursor(sqlSession, args);

} else {

param = this.method.convertArgsToSqlCommandParam(args);

result = sqlSession.selectOne(this.command.getName(), param);

if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {

result = Optional.ofNullable(result); } } break;

case FLUSH: result = sqlSession.flushStatements(); break;

default:

throw new BindingException("Unknown execution method for: " + this.command.getName());

} if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {

throw new BindingException(“Mapper method '” + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + “).”);

} else {

return result;

} }

通过以上的分析,总结出

  • Mapper 接口实际对象为代理对象 MapperProxy ;

  • MapperProxy 继承 InvocationHandler ,实现 invoke 方法;

  • MapperProxyFactory::newInstance() 方法,基于 JDK 动态代理的方式创建了一个 MapperProxy 的代理类;

  • 最终会调用倒忙mapperMethod::execute() 方法执行,完成操作。

  • 而且更重要一点是, MyBatis 使用的动态代理和普遍动态代理有点区别,没有实现类,只有接口,MyBatis 动态代理类图结构如下所示:

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

以 SELECT 为例, 调用会 SqlSession ::selectOne() 方法。继续往下执行,会执行 Executor::query() 方法。

public List selectList(String statement, Object parameter, RowBounds rowBounds) {

List var5; try {

MappedStatement ms = this.configuration.getMappedStatement(statement);

var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

} catch (Exception var9) {

throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);

} finally {

ErrorContext.instance().reset(); } return var5;

}

执行倒忙Executor 类,那么我们来看看其究竟有什么?

E x ecutor

Executor 对象为SQL 的执行引擎,负责增删改查的具体操作,顶层接口 SqlSession 中都会有一个 Executor 对象,可以理解为 JDBC 中 Statement 的封装版。

Executor 是最顶层的是执行器,它有两个实现类,分别是 BaseExecutor 和 CachingExecutor

  • BaseExecutor 是一个抽象类,实现了大部分 Executor 接口定义的功能,降低了接口实现的难度。 BaseExecutor 基于适配器设计模式之接口适配会有三个子类,分别是 SimpleExecutor 、 ReuseExecutor 和 BatchExecutor 。

  • BatchExecutor : 批处理执行器,用于执行update(没有select,JDBC批处理不支持select将多个 SQL 一次性输出到数据库,

  • ReuseExecutor : 可重用执行器, 执行 update 或 select ,以sql作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String, Statement> 内,供下一次使用。简言之,就是重复使用 Statement 对象

  • SimpleExecutor : 是 MyBatis 中 默认 简单执行器,每执行一次 update 或 select ,就开启一个 Statement 对象,用完立刻关闭 Statement 对象

  • CachingExecutor : 缓存执行器,为 Executor 对象增加了 二级缓存 的相关功:先从缓存中查询结果,如果存在就返回之前的结果;如果不存在,再委托给 Executor delegate 去数据库中取, delegate 可以是上面任何一个执行器。

在 Mybatis 配置文件中,可以指定默认的 ExecutorType 执行器类型,也可以手动给 DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数。

看完 Exector 简介之后,继续跟踪执行流程链路分析, SqlSession 中的 JDBC 操作部分最终都会委派给 Exector 实现, Executor::query() 方法,看看在 Exector 的执行是怎样的?

面试:谈谈你对MyBatis执行过程之SQL执行过程理解

每次查询都会先经过 CachingExecutor 缓存执行器, 会先判断二级缓存中是否存在查询 SQL ,如果存在直接从二级缓存中获取,不存在即为第一次执行,会直接执行SQL 语句,并创建缓存,都是由 CachingExecutor::query() 操作完成的。

public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {

BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);

return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

}public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //获取查询语句对应的二级缓存

Cache cache = ms.getCache(); //sql查询是否存在在二级缓存中

if (cache != null) {

//根据 节点的配置,判断否需要清空二级缓存

this.flushCacheIfRequired(ms);

if (ms.isUseCache() && resultHandler == null) {

this.ensureNoOutParams(ms, boundSql);

//查询二级缓存

List list = (List)this.tcm.getObject(cache, key);

if (list == null) {

//二级缓存没用相应的结果对象,调用封装的Executor对象的 query() 方法

list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

//将查询结果保存到二级缓存中

this.tcm.putObject(cache, key, list);

} return list;

} }//没有启动二级缓存,直接调用底层 Executor 执行数据数据库查询操作

return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

}

如果在经过 CachingExecutor 缓存执行器(二级缓存)没有返回值的话,就会执行 BaseExecutor 以及其的实现类,默认为 SimpleExecutor ,首先会在一级缓存中获取查询结果,获得不到,最终会通过 SimpleExecutor:: () 去数据库中查询。

public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

ErrorContext.instance().resource(ms.getResource()).activity(“executing a query”).object(ms.getId());

if (this.closed) {

throw new ExecutorException(“Executor was closed.”);

} else {

//是否清除本地缓存

if (this.queryStack == 0 && ms.isFlushCacheRequired()) {

this.clearLocalCache();

} List list; try {

++this.queryStack;

//从一级缓存中,获取查询结果

list = resultHandler == null ? (List)this.localCache.getObject(key) : null;

//获取到结果,则进行处理

if (list != null) {

this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

} else {

//获得不到,则从数据库中查询

list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

} } finally {

–this.queryStack;

} if (this.queryStack == 0) {

//执行延迟加载

Iterator var8 = this.deferredLoads.iterator();

while(var8.hasNext()) {

BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next(); deferredLoad.load(); } this.deferredLoads.clear();

if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {

this.clearLocalCache();

} } return list;

} }

那么 SimpleExecutor::doQuery() 如何去数据库中查询获取到结果呢? 其实执行到这边mybatis的执行过程就从 Executor 转交给 StatementHandler 处理,

public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {

Statement stmt = null;

List var9; try {

Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

stmt = this.prepareStatement(handler, ms.getStatementLog());

var9 = handler.query(stmt, resultHandler); } finally {

this.closeStatement(stmt);

} return var9;

惊喜

最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)

image.png

image.png

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
tmt, resultHandler); } finally {

this.closeStatement(stmt);

} return var9;

惊喜

最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)

[外链图片转存中…(img-rhqLrovq-1713563909349)]

[外链图片转存中…(img-ZGem8845-1713563909350)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-feAE9D03-1713563909351)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值