每日五道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>

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

相关推荐
java排坑日记1 小时前
poi-tl+kkviewfile实现生成pdf业务报告
java·pdf·word
V+zmm101342 小时前
校园约拍微信小程序设计与实现ssm+论文源码调试讲解
java·微信小程序·小程序·毕业设计·ssm
_周游2 小时前
【C语言】_指针与数组
c语言·开发语言
猿来入此小猿2 小时前
基于SpringBoot小说平台系统功能实现四
java·spring boot·毕业设计·毕业源码·在线小说阅读·在线小说平台·免费学习:猿来入此
SyntaxSage3 小时前
Scala语言的数据库交互
开发语言·后端·golang
疯狂小料3 小时前
Python3刷算法来呀,贪心系列题单
开发语言·python·算法
Cosmoshhhyyy3 小时前
LeetCode:2274. 不含特殊楼层的最大连续楼层数(排序 Java)
java·算法·leetcode
码力全開3 小时前
C 语言奇幻之旅 - 第14篇:C 语言高级主题
服务器·c语言·开发语言·人工智能·算法
lsx2024063 小时前
PHP Array:精通数组操作
开发语言
Dolphin_Home3 小时前
Spring Boot 多环境配置与切换
java·spring boot·后端