目录:
- 第一题. MyBatis⽀持动态SQL吗?
- 第二题. 说说Mybatis的⼀级、⼆级缓存?
- 第三题. MyBatis的功能架构是什么样的?
- 第四题. 为什么Mapper接⼝不需要实现类?
- 第五题. 说说Mybatis的插件运⾏原理,如何编写⼀个插件?
第一题. MyBatis⽀持动态SQL吗?
MyBatis中有⼀些⽀持动态SQL的标签,它们的原理是使⽤OGNL从SQL参数对象中计算表达式的值,根据表达式的值动态拼接SQL,以此来完成动态SQL的功能。
- if
根据条件来组成where⼦句
<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 语句有点像
<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>可以⽤在所有的查询条件都是动态的情况
<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> 可以⽤在动态更新的时候
<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
看到名字就知道了,这个是⽤来循环的,可以对集合进⾏遍历
<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的⼀级、⼆级缓存?
- ⼀级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作⽤域为SqlSession,各个SqlSession之间的缓存相互隔离,当 Session flush 或 close 之后,该 SqlSession 中的所有 Cache 就将清空,MyBatis默认打开⼀级缓存。
- ⼆级缓存与⼀级缓存其机制相同,默认也是采⽤ PerpetualCache,HashMap 存储,不同之处在于其存储作⽤域为Mapper(Namespace),可以在多个SqlSession之间共享,并且可⾃定义存储源,如 Ehcache。默认不打开⼆级缓存,要开启⼆级缓存,使⽤⼆级缓存属性类需要实现Serializable序列化接⼝(可⽤来保存对象的状态),可在它的映射⽂件中配置。
第三题. MyBatis的功能架构是什么样的?
我们⼀般把Mybatis的功能架构分为三层:
- API接⼝层:提供给外部使⽤的接⼝API,开发⼈员通过这些本地API来操纵数据库。接⼝层⼀接收到调⽤请求就会调⽤数据处理层来完成具体的数据处理。
- 数据处理层:负责具体的SQL查找、SQL解析、SQL执⾏和执⾏结果映射处理等。它主要的⽬的是根据调⽤的请求完成⼀次数据库操作。
- 基础⽀撑层:负责最基础的功能⽀撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共⽤的东⻄,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的⽀撑。
第四题. 为什么Mapper接⼝不需要实现类?
四个字回答:动态代理,我们来看⼀下获取Mapper的过程:
获取Mapper
我们都知道定义的Mapper接⼝是没有实现类的,Mapper映射其实是通过动态代理实现的
BlogMapper mapper = session.getMapper(BlogMapper.class);
七拐⼋绕地进去看⼀下,发现获取Mapper的过程,需要先获取MapperProxyFactory——Mapper代理⼯⼚。
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代理对象)。
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⽅法。
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。
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()⽅法这⾥我们只是在⽬标对象执⾏⽬标⽅法的前后进⾏了打印;
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;
}
}
- 然后再给插件编写注解,确定要拦截的对象,要拦截的⽅法
@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配置⽂件⾥⾯配置插件
<plugins>
<plugin interceptor="xxx.MyPlugin">
<property name="dbType",value="mysql"/>
</plugin>
</plugins>
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力