mybatis中动态sql执行原理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis中动态sql执行原理相关的知识,希望对你有一定的参考价值。
参考技术A 解释器模式: 初始化过程中构建出抽象语法树,请求处理时根据参数对象解释语法树,生成sql语句。工厂模式: 为动态标签的处理方式创建工厂类(SqlTagHandlerFactory),根据标签名称获取对应的处理方式。
策略模式: 将动态标签处理方式抽象为接口,针对不同标签有相应的实现类。解释抽象语法树时,定义统一的解释流程,再调用标签对应的处理方式完成解释中的各个子环节
Mybatis部分面试题
一、Mybatis动态sql是做什么的?都有哪些动态sql?简述一下动态sql的执行原理?
动态sql:顾名思义就是动态的根据属性值来拼接数据库执行的sql语句,也就是多次查询或变更操作,根据传入的属性值不同,动态拼接出不同的可执行sql。包含判断为空、循环等
动态sql包含:where、set、if、foreach、trim、when、choose
动态sql执行原理:
第一部分:在启动加载解析xml配置文件的时候进行解析,根据关键标签封装成对应的handler处理对象,封装成sqlSource对象存在mappedStatement。
调用流程:
I、SqlSessionFactoryBuilder对builder对象的时候,调用XMLConfigBuilder解析sqlMapConfig.xml配置文件,在解析过程中使用到了私有的mapperElement(XNode parent)方法
II、上面方法中通过构建XMLMapperBuilder,获取到所有的配置mapper配置,
在调用private void configurationElement(XNode context)方法进行解析mapper.xml,通过void buildStatementFromContext(List<XNode> list, String requiredDatabaseId)方法解析mapper.xml内的每一个标签
III、循环中构建XMLStatementBuilder对象,调用parseStatementNode()方法来封装mappedStatment对象,
IIII、在过程中需要构建sqlSource对象,通过XMLLanguageDriver对象进行处理,在XMLLanguageDriver中构建解析动态标签对象XMLScriptBuilder
第二部分:在执行过程中获取sqlSource中获取bondSql对象时,执行相应的标签handler
调用查询执行到BaseExecutor的query方法时候会去getBoundSql并且将参数传进去,
在sqlSource接口DynamicSqlSource实现类中,调用getBoundSql方法执行过程共创建DynamicContext对象进行判定解析封装成SqlSource对象返回。
构建出来的sqlSource如下图:
二、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
mybatis支持延迟加载,主要包括association一对一关联对象和collection一对多关联对象。在Mybatis配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
三、Mybatis都有哪些Executor执行器?它们之间的区别是什么?
SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。
BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
默认是SimplExcutor,需要配置在创建SqlSession对象的时候指定执行器的类型即可
四、简述下Mybatis的一级、二级缓存(分别从存储结构、范围、失效场景。三个方面来作答)?
1)一级缓存:Mybatis的一级缓存是指SqlSession级别的,作用域是SqlSession,Mybatis默认开启一级缓存,在同一个SqlSession中,相同的Sql查询的时候,第一次查询的时候,就会从缓存中取,如果发现没有数据,那么就从数据库查询出来,并且缓存到HashMap中,如果下次还是相同的查询,就直接从缓存中查询,就不在去查询数据库,对应的就不在去执行SQL语句。当查询到的数据,进行增删改的操作的时候,缓存将会失效。在spring容器管理中每次查询都是创建一个新的sqlSession,所以在分布式环境中不会出现数据不一致的问题
2)二级缓存:二级缓存是mapper级别的缓存,多个SqlSession去操作同一个mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession。第一次调用mapper下的sql 的时候去查询信息,查询到的信息会存放到该mapper对应的二级缓存区域,第二次调用namespace下的mapper映射文件中,相同的SQL去查询,回去对应的二级缓存内取结果,使用值需要开启cache标签,在select上添加useCache属性为true,在更新和删除时候需要手动开启flushCache刷新缓存。
五、简述Mybatis的插件运行原理,以及如何编写一个插件?
编写插件
1、创建插件类实现interceptor接口并且使用注解标注拦截对象与方法
package city.albert; /** * @author niunafei * @function * @email niunafei0315@163.com * @date 2020/6/11 6:08 PM */ import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import java.lang.reflect.Method; import java.util.Properties; /** * 注解声明mybatis当前插件拦截哪个对象的哪个方法 * <p> * type表示要拦截的目标对象 Executor.class StatementHandler.class ParameterHandler.class ResultSetHandler.class * method表示要拦截的方法, * args表示要拦截方法的参数 * * @author niuanfei */ @Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class TestInterceptor implements Interceptor { /** * 拦截目标对象的目标方法执行 * * @param invocation * @return * @throws Throwable */ @Override public Object intercept(Invocation invocation) throws Throwable { //被代理对象 Object target = invocation.getTarget(); //代理方法 Method method = invocation.getMethod(); //方法参数 Object[] args = invocation.getArgs(); // do something ...... 方法拦截前执行代码块 //执行原来方法 Object result = invocation.proceed(); // do something .......方法拦截后执行代码块 return result; } /** * 包装目标对象:为目标对象创建代理对象 * * @param target * @return */ @Override public Object plugin(Object target) { System.out.println("MySecondPlugin为目标对象" + target + "创建代理对象"); //this表示当前拦截器,target表示目标对象,wrap方法利用mybatis封装的方法为目标对象创建代理对象(没有拦截的对象会直接返回,不会创建代理对象) Object wrap = Plugin.wrap(target, this); return wrap; } /** * 设置插件在配置文件中配置的参数值 * * @param properties */ @Override public void setProperties(Properties properties) { System.out.println(properties); } }
2、在配置文件中写入plugins标签
<plugins> <plugin interceptor="city.albert.TestInterceptor"> <property name="name" value="name"/> </plugin> </plugins>
以上是关于mybatis中动态sql执行原理的主要内容,如果未能解决你的问题,请参考以下文章