mybatis中动态sql执行原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis中动态sql执行原理相关的知识,希望对你有一定的参考价值。

参考技术A 解释器模式: 初始化过程中构建出抽象语法树,请求处理时根据参数对象解释语法树,生成sql语句。
工厂模式: 为动态标签的处理方式创建工厂类(SqlTagHandlerFactory),根据标签名称获取对应的处理方式。
策略模式: 将动态标签处理方式抽象为接口,针对不同标签有相应的实现类。解释抽象语法树时,定义统一的解释流程,再调用标签对应的处理方式完成解释中的各个子环节

Mybatis部分面试题

一、Mybatis动态sql是做什么的?都有哪些动态sql?简述一下动态sql的执行原理?

动态sql:顾名思义就是动态的根据属性值来拼接数据库执行的sql语句,也就是多次查询或变更操作,根据传入的属性值不同,动态拼接出不同的可执行sql。包含判断为空、循环等

动态sql包含:wheresetifforeachtrimwhenchoose

技术图片 

动态sql执行原理:

第一部分:在启动加载解析xml配置文件的时候进行解析,根据关键标签封装成对应的handler处理对象,封装成sqlSource对象存在mappedStatement

调用流程:

ISqlSessionFactoryBuilderbuilder对象的时候,调用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

调用查询执行到BaseExecutorquery方法时候会去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:每执行一次updateselect,就开启一个Statement对象,用完立刻关闭Statement对象。

ReuseExecutor:执行updateselect,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。

BatchExecutor:执行update(没有selectJDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

默认是SimplExcutor,需要配置在创建SqlSession对象的时候指定执行器的类型即可

四、简述下Mybatis的一级、二级缓存(分别从存储结构、范围、失效场景。三个方面来作答)?

1)一级缓存:Mybatis的一级缓存是指SqlSession级别的,作用域是SqlSessionMybatis默认开启一级缓存,在同一个SqlSession中,相同的Sql查询的时候,第一次查询的时候,就会从缓存中取,如果发现没有数据,那么就从数据库查询出来,并且缓存到HashMap中,如果下次还是相同的查询,就直接从缓存中查询,就不在去查询数据库,对应的就不在去执行SQL语句。当查询到的数据,进行增删改的操作的时候,缓存将会失效。在spring容器管理中每次查询都是创建一个新的sqlSession,所以在分布式环境中不会出现数据不一致的问题

 

2)二级缓存:二级缓存是mapper级别的缓存,多个SqlSession去操作同一个mappersql语句,多个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);
    }
}
View Code

2、在配置文件中写入plugins标签

 

    <plugins>
        <plugin interceptor="city.albert.TestInterceptor">
            <property name="name" value="name"/>
        </plugin>
    </plugins>

 

以上是关于mybatis中动态sql执行原理的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis部分面试题

一文掌握MyBatis的动态SQL使用与原理

MyBatis 动态 SQL 底层原理分析

一文掌握MyBatis的动态SQL使用与原理

Mybatis解析动态sql原理分析

MyBatis解析动态SQL原理分析