每日五道java面试题之mybatis篇(六)

目录:

  • [第一题. MyBatis⽀持动态SQL吗?](#第一题. MyBatis⽀持动态SQL吗?)
  • [第二题. 说说Mybatis的⼀级、⼆级缓存?](#第二题. 说说Mybatis的⼀级、⼆级缓存?)
  • [第三题. MyBatis的功能架构是什么样的?](#第三题. MyBatis的功能架构是什么样的?)
  • [第四题. 为什么Mapper接⼝不需要实现类?](#第四题. 为什么Mapper接⼝不需要实现类?)
  • [第五题. 说说Mybatis的插件运⾏原理,如何编写⼀个插件?](#第五题. 说说Mybatis的插件运⾏原理,如何编写⼀个插件?)

第一题. MyBatis⽀持动态SQL吗?

MyBatis中有⼀些⽀持动态SQL的标签,它们的原理是使⽤OGNL从SQL参数对象中计算表达式的值,根据表达式的值动态拼接SQL,以此来完成动态SQL的功能。

  • if
    根据条件来组成where⼦句
xml 复制代码
<select id="findActiveBlogWithTitleLike"
 resultType="Blog">
SELECT * FROM BLOG
WHERE state = 'ACTIVE'
<if test="title != null">
 AND title like #{title}
</if>
</select>
  • choose (when, otherwise)
    这个和Java 中的 switch 语句有点像
xml 复制代码
<select id="findActiveBlogLike"
 resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<choose>
 <when test="title != null">
 AND title like #{title}
 </when>
 <when test="author != null and author.name != null">
 AND author_name like #{author.name}
 </when>
 <otherwise>
 AND featured = 1
 </otherwise>
</choose>
</select>
  • trim (where, set)
  • < where>可以⽤在所有的查询条件都是动态的情况
xml 复制代码
<select id="findActiveBlogLike"
 resultType="Blog">
SELECT * FROM BLOG
<where>
 <if test="state != null">
 state = #{state}
 </if>
 <if test="title != null">
 AND title like #{title}
 </if>
 <if test="author != null and author.name != null">
 AND author_name like #{author.name}
 </if>
</where>
</select>
  • < set> 可以⽤在动态更新的时候
xml 复制代码
<update id="updateAuthorIfNecessary">
 update Author
 <set>
 <if test="username != null">username=#{username},</if>
 <if test="password != null">password=#{password},</if>
 <if test="email != null">email=#{email},</if>
 <if test="bio != null">bio=#{bio}</if>
 </set>
 where id=#{id}
</update>
  • foreach
    看到名字就知道了,这个是⽤来循环的,可以对集合进⾏遍历
xml 复制代码
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
 <foreach item="item" index="index" collection="list"
 open="ID in (" separator="," close=")" nullable="true">
 #{item}
 </foreach>
</where>
</select>

第二题. 说说Mybatis的⼀级、⼆级缓存?

  1. ⼀级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作⽤域为SqlSession,各个SqlSession之间的缓存相互隔离,当 Session flush 或 close 之后,该 SqlSession 中的所有 Cache 就将清空,MyBatis默认打开⼀级缓存。
  1. ⼆级缓存与⼀级缓存其机制相同,默认也是采⽤ PerpetualCache,HashMap 存储,不同之处在于其存储作⽤域为Mapper(Namespace),可以在多个SqlSession之间共享,并且可⾃定义存储源,如 Ehcache。默认不打开⼆级缓存,要开启⼆级缓存,使⽤⼆级缓存属性类需要实现Serializable序列化接⼝(可⽤来保存对象的状态),可在它的映射⽂件中配置。

第三题. MyBatis的功能架构是什么样的?

我们⼀般把Mybatis的功能架构分为三层:

  • API接⼝层:提供给外部使⽤的接⼝API,开发⼈员通过这些本地API来操纵数据库。接⼝层⼀接收到调⽤请求就会调⽤数据处理层来完成具体的数据处理。
  • 数据处理层:负责具体的SQL查找、SQL解析、SQL执⾏和执⾏结果映射处理等。它主要的⽬的是根据调⽤的请求完成⼀次数据库操作。
  • 基础⽀撑层:负责最基础的功能⽀撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共⽤的东⻄,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的⽀撑。

第四题. 为什么Mapper接⼝不需要实现类?

四个字回答:动态代理,我们来看⼀下获取Mapper的过程:

获取Mapper

我们都知道定义的Mapper接⼝是没有实现类的,Mapper映射其实是通过动态代理实现的

java 复制代码
BlogMapper mapper = session.getMapper(BlogMapper.class);

七拐⼋绕地进去看⼀下,发现获取Mapper的过程,需要先获取MapperProxyFactory------Mapper代理⼯⼚。

java 复制代码
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 MapperProxyFactory<T> 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);
 }
 }
}

MapperProxyFactory

MapperProxyFactory的作⽤是⽣成MapperProxy(Mapper代理对象)。

java 复制代码
public class MapperProxyFactory<T> {
 private final Class<T> mapperInterface;
 ......
 protected T newInstance(MapperProxy<T> mapperProxy) {
 return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]
{this.mapperInterface}, mapperProxy);
 }
 public T newInstance(SqlSession sqlSession) {
 MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface,
this.methodCache);
 return this.newInstance(mapperProxy);
 }
}

这⾥可以看到动态代理对接⼝的绑定,它的作⽤就是⽣成动态代理对象(占位),⽽代理的⽅法被放到了MapperProxy中。

MapperProxy

MapperProxy⾥,通常会⽣成⼀个MapperMethod对象,它是通过cachedMapperMethod⽅法对其进⾏初始化的,然后执⾏excute⽅法。

java 复制代码
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 try {
 return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this,
args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
 } catch (Throwable var5) {
 throw ExceptionUtil.unwrapThrowable(var5);
 }
}

MapperMethod

MapperMethod⾥的excute⽅法,会真正去执⾏sql。这⾥⽤到了命令模式,其实绕⼀圈,最终它还是通过SqlSession的实例去运⾏对象的sql。

java 复制代码
public Object execute(SqlSession sqlSession, Object[] args) {
 Object result;
 Object param;
 ......
 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;
 ......
 }

第五题. 说说Mybatis的插件运⾏原理,如何编写⼀个插件?

插件的运⾏原理?

Mybatis会话的运⾏需要ParameterHandler、ResultSetHandler、StatementHandler、Executor这四⼤对象的配合,插件的原理就是在这四⼤对象调度的时候,插⼊⼀些我我们⾃⼰的代码。

Mybatis使⽤JDK的动态代理,为⽬标对象⽣成代理对象。它提供了⼀个⼯具类 Plugin ,实现了InvocationHandler 接⼝。

使⽤ Plugin ⽣成代理对象,代理对象在调⽤⽅法的时候,就会进⼊invoke⽅法,在invoke⽅法中,如果存在签名的拦截⽅法,插件的intercept⽅法就会在这⾥被我们调⽤,然后就返回结果。如果不存在签名⽅法,那么将直接反射调⽤我们要执⾏的⽅法。

如何编写⼀个插件?

我们⾃⼰编写MyBatis 插件,只需要实现拦截器接⼝ Interceptor (org.apache.ibatis. pluginInterceptor ) ,在实现类中对拦截对象和⽅法进⾏处理。

  • 实现Mybatis的Interceptor接⼝并重写intercept()⽅法这⾥我们只是在⽬标对象执⾏⽬标⽅法的前后进⾏了打印;
java 复制代码
public class MyInterceptor implements Interceptor {
 Properties props=null;
 @Override
 public Object intercept(Invocation invocation) throws Throwable {
 System.out.println("before......");
 //如果当前代理的是⼀个⾮代理对象,那么就会调⽤真实拦截对象的⽅法
 // 如果不是它就会调⽤下个插件代理对象的invoke⽅法
 Object obj=invocation.proceed();
 System.out.println("after......");
 return obj;
 }
}
  • 然后再给插件编写注解,确定要拦截的对象,要拦截的⽅法
java 复制代码
@Intercepts({@Signature(
 type = Executor.class, //确定要拦截的对象
 method = "update", //确定要拦截的⽅法
 args = {MappedStatement.class,Object.class} //拦截⽅法的参数
)})
public class MyInterceptor implements Interceptor {
 Properties props=null;
 @Override
 public Object intercept(Invocation invocation) throws Throwable {
 System.out.println("before......");
 //如果当前代理的是⼀个⾮代理对象,那么就会调⽤真实拦截对象的⽅法
 // 如果不是它就会调⽤下个插件代理对象的invoke⽅法
 Object obj=invocation.proceed();
 System.out.println("after......");
 return obj;
 }
}
  • 最后,再MyBatis配置⽂件⾥⾯配置插件
xml 复制代码
<plugins>
 <plugin interceptor="xxx.MyPlugin">
 <property name="dbType",value="mysql"/>
 </plugin>
</plugins>

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力

相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔4 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大9 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai9 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说9 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json