我是靠谱客的博主 复杂万宝路,最近开发中收集的这篇文章主要介绍mybaits延迟加载原理_MyBatis 延迟加载、一二级缓存、架构设计的面试题(常问,重点了解)...,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

MyBatis 的面试题整理快接近尾声了,我对着问题写了 6 个 Demo,把 MyBatis 的源码翻了几遍,收获满满。不知道你看的有没有收获?可以在文末参与讨论。Let's go on...

1、MyBatis 的 SQL 执行日志如何开启?

这是之前遗留的一个问题。

在配置文件的    标签上设置 logImpl 参数值(SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING),指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

如 MyBatis 实现的标准输出的配置

2、MyBatis 中注册 Mapper 有哪些方式?

方式一:在配置文件 mybatis-config.xml 中添加及其子标签,编写对应的 Mapper 接口与 XML

方式二、硬编码方式在 configuration 对象中注册 Mapper 接口//配置

Configuration configuration = new Configuration(environment);

//注册

configuration.addMapper(UserMapper.class);

3、MyBatis 如何支持延迟加载?现实原理是什么?

支持延迟加载的配置:

在配置文件的标签内设置参数lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态

aggressiveLazyLoading:开启时,任一方法的调用都会加载该对象的所有延迟加载属性。否则,每个延迟加载属性会按需加载

lazyLoadTriggerMethods:指定对象的哪些方法触发一次延迟加载

resultMap 中配置 或

配置与测试示例

//配置文件

//Mapper xml

select * from user where id = 1

//InfoMapper

public interface InfoMapper {

@Select("select * from info where user_id = #{userId}")

@Results(value = {@Result(column="user_id", property = "userId")})

Info selectInfoByUserId(int userId);

}

//测试代码

System.out.println("------ selectUserWithLazyInfo ------");

User user = userMapper.selectUserWithLazyInfo();

System.out.println(user);

System.out.println(user.getInfo());

//打印 User 对象里的 Info 为空,使用 getInfo 能够查询对应的值

------ selectUserWithLazyInfo ------

User{id=1, name='ConstXiong1', mc='null', info=null, articles=null}

Info{userId=1, name=大熊}

实现原理:

支持延迟加载是通过字节码增强实现的,MyBatis 3.3 及以上默认使用了 javassist,3.3 以前使用 cglib 实现。

我本地用的 MyBatis 3.5.5,使用了 javassist 增强,核心源码如下//DefaultResultSetHandler getRowValue 获取每条的查询数据,resultMap 中如果包含懒加载 rowValue 在 createResultObject 方法通过 javassist 代理增强

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {

final ResultLoaderMap lazyLoader = new ResultLoaderMap();

//对象数据,通过 javassist 代理增强

Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);

if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {

final MetaObject metaObject = configuration.newMetaObject(rowValue);

boolean foundValues = this.useConstructorMappings;

if (shouldApplyAutomaticMappings(resultMap, false)) {

foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;

}

//根据从数据库查询到的 resultSet,根据 resultMap 通过反射设置 rowValue 的值

foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;

foundValues = lazyLoader.size() > 0 || foundValues;

rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;

}

return rowValue;

}

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {

this.useConstructorMappings = false; // reset previous mapping result

final List> constructorArgTypes = new ArrayList<>();

final List constructorArgs = new ArrayList<>();

Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);

if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {

final List propertyMappings = resultMap.getPropertyResultMappings();

for (ResultMapping propertyMapping : propertyMappings) {

//如果返回对象的属性中包含懒加载,使用 javassist 代理增强,当设置属性值时被代理到 JavassistProxyFactory 的 invoke 方法

if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {

resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);

break;

}

}

}

this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result

return resultObject;

}//JavassistProxyFactory 的 invoke 方法

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

final String methodName = method.getName();

try {

synchronized (lazyLoader) {

if (WRITE_REPLACE_METHOD.equals(methodName)) {

...

} else {

if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {

if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {

lazyLoader.loadAll();

} else if (PropertyNamer.isSetter(methodName)) {

final String property = PropertyNamer.methodToProperty(methodName);

lazyLoader.remove(property);

} else if (PropertyNamer.isGetter(methodName)) {

//测试代码中 user.getInfo() 方法的调用,在此执行懒加载查询关联 SQL 设置 info 属性

final String property = PropertyNamer.methodToProperty(methodName);

if (lazyLoader.hasLoader(property)) {

lazyLoader.load(property);

}

}

}

}

}

return methodProxy.invoke(enhanced, args);

} catch (Throwable t) {

throw ExceptionUtil.unwrapThrowable(t);

}

}

完整 Demo:

https://javanav.com/val/973ded541e9244aa8b3169b9fb869d60.html

4、MyBatis 中的本地缓存和二级缓存的作用是什么?怎么实现的?

本地缓存

作用:

SqlSession 级别的缓存,默认开启,在 MyBatis 配置文件中可以修改 MyBatis 文件中 标签 localCacheScope 参数值改变缓存的作用域。statementId、boundSql.getSql() 执行 sql、查询参数、RowBounds 都相同,即认为是同一次查询,返回缓存值。

实现原理:

每个 SqlSession 对象包含一个 Executor 对象,Executor 对象中 localCache 属性使用 PerpetualCache 对象缓存查询数据;从源码中看 DefaultSqlSession 的 close、commit、rollback、insert、delete、update 相关的方法都会触发 BaseExecutor 对象清掉缓存。

二级缓存

作用:MappedStatement 级别的缓存,默认不开启,可以在 Mapper xml 中通过 标签开启 或者 MyBatis 文件中 标签设置 cacheEnabled 参数为 true 全局开启 或者 mapper xml 配置文件中的 select 节点需要加上属性 useCache,在 SqlSession 关闭或提交之后才会生效。

开启二级缓存的默认作用摘自官网映射语句文件中的所有 select 语句的结果将会被缓存。

映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。

缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。

缓存不会定时进行刷新(也就是说,没有刷新间隔)。

缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。

缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

实现原理:XMLMappedBuilder 解析 Mapper xml 中的 、 标签

通过 builderAssistant 对象 addMappedStatement 方法,设置 cache 信息到 MappedStatement 对象内

CachingExecutor 对象的 query 方法先 MappedStatement 对象中 getCache() 获取缓存 Cache 对象,如果没有查到则到 BaseExecutor 中查询,走本地缓存逻辑

5、MyBatis 的源码中的核心类有哪些?如何实现框架功能的?

Configuration: 配置类

Environment: 环境信息

SqlSessionFactoryBuilder: SqlSessionFactory 构造者类

SqlSessionFactory: SqlSession 工厂类

SqlSession: 执行 SQL 的一次会话

XMLConfigBuilder: MyBatis xml 配置文件构造者类

XMLMapperBuilder: Mapper xml 配置文件构造者类

MapperBuilderAssistant: Mapped 匹配信息构造者类,如构造添加MappedStatement

XMLStatementBuilder: Mapper xml 配置文件中 SQL 标签的构造者类,构造 MappedStatement

MappedStatement: 通过 Mapper xml 或注解,生成的 select|update|delete|insert Statement 的封装

MapperProxy: Mapper 接口的代理类

MapperMethod: Mapper 接口的方法,包含匹配的 SQL 执行种类和具体方法签名等信息

Executor: 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓存的维护

BaseExecutor: SqlSession 中的基本数据库的增删改查的执行器,涉及本地缓存与数据库查询

CachingExecutor: 带缓存的执行器,涉及二级缓存,未命中走本地缓存逻辑

ResultMap: 返回值类型匹配的类

SqlSource: 负责根据用户传递的 parameterObject,动态地生成 SQL 语句,将信息封装到 BoundSql 对象中,并返回该对象

BoundSql: 动态生成的 SQL 语句以及相应的参数信息

StatementHandler: Statement 处理接口,封装 JDBC Statement 操作

ParameterHandler: 参数处理接口,负责对用户传递的参数转换成 JDBC Statement 所需要的参数

ResultSethandler: 执行结果处理接口

TypeHandler: 返回类型处理接口

框架如何实现,这个问题的细节就特别多了,画了一张我个人理解的图

d2d397e62be102731b252b209dc0a57c.png

到此,还剩 3 道题目MyBatis 插件的运行原理是什么?如何编写一个自定义插件?

MyBatis 是如何被 Spring 集成的?

Spring 中如何配置 MyBatis?

留着下回分解...

最后

以上就是复杂万宝路为你收集整理的mybaits延迟加载原理_MyBatis 延迟加载、一二级缓存、架构设计的面试题(常问,重点了解)...的全部内容,希望文章能够帮你解决mybaits延迟加载原理_MyBatis 延迟加载、一二级缓存、架构设计的面试题(常问,重点了解)...所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(36)

评论列表共有 0 条评论

立即
投稿
返回
顶部