美文网首页
Mybatis--常见面试题整理

Mybatis--常见面试题整理

作者: wyn_做自己 | 来源:发表于2021-11-24 17:41 被阅读0次

    今日份鸡汤:虽然青春不再,但奋斗可以不止~

    1、传统JDBC弊端?

    (1)JDBC底层没有用连接池,创建数据库需要频繁的创建和关联链接,消耗很大的资源。
    (2)写原生的JDBC代码在Java中,一旦我们要修改sql的话,Java需要整体编译,不利于系统维护。
    (3)使用PreParedStatement预编译的话,对变量进行设置123数字,这样的序号不利于维护。
    (4)返回result结果集也需要硬编码。

    2、什么是Mybatis?什么是ORM框架?

    Mybatis:
    (1) 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时 只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性 能,灵活度高。

    (2)MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数 据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

    (3)通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最 后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。(从执行 sql 到返 回 result 的过程)。

    ORM框架:
    对象关系映射(Object Relational Mapping,简称ORM),用于实现面向对象编程语言里不同类型系统的数据之间的转换。

    3、Mybatis加载mappers文件有哪几种方式?

    resource、url、class、package(优先级最高)

    4、Mybatis都有哪些Executor执行器?它们之间的区别是什么?

    SIMPLE :简单的,默认的
    REUSE:复用的
    BATCH:批量的

    (1)SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。

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

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

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

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

    5、Mybatis中如何指定使用哪一种Executor执行器?

    在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。

    6、SqlSession是否是线程安全的?

    与spring整合是线程安全的。如果是他自己本身是非线程安全的。

    7、#{}和${}的区别是什么?

    动态 sql 是 mybatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在查询之前 mybatis 会对其进行动态解析。mybatis 为我们提供了两种支持动态 sql 的语法:#{} 以及 ${}。

    (1)#{}是预编译处理,$ {}是字符串替换。

    (2)mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;mybatis在处理 { } 时,就是把{ } 替换成变量的值。

    (3)使用 #{} 可以有效的防止SQL注入,提高系统安全性。

    进一步解释一下:

    (1)一般用来当作占位符,常使用Linux脚本的同学应该对此有更深的体会吧。既然是占位符,当然就是被用来替换的。知道了这点就能很容易区分和#,从而不容易记错了。

    (2)预编译的机制。预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。在某些特殊场合下只能用{},不能用#{}。例如:在使用排序时ORDER BY{id},如果使用#{id},则会被解析成ORDER BY “id”,这显然是一种错误的写法。

    8、Xml 映射文件中,除了常用的 select | insert | updae | delete 标签之外,还有哪些标签?

    <resultMap> 、 <parameterMap> 、 <sql> 、 <include> 、 <selectKey> ,加上动态 sql 的 9个标签, trim | where | set | foreach | if | choose | when | otherwise | bind 等,其中为 sql片段标签,通过<include> 标签引入 sql 片段, <selectKey> 为不支持自增的主键生成策略标签。

    9、最佳实践中,通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应,请问,这个 Dao 接口的工作原理是什么? Dao 接口里的方法,参数不同时,方法能重载吗?

    Dao 接口即 Mapper 接口。接口的全限名,就是映射文件中的 namespace 的值;接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的参数,就是传递给 sql 的参数。

    Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MapperStatement。在 Mybatis 中,每一个<select> 、 <insert> 、 <update> 、 <delete> 标签,都会被解析为一个MapperStatement 对象。

    举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到 namespace 为 com.mybatis3.mappers.StudentDao 下面 id 为findStudentById 的 MapperStatement。

    Mapper 接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。
    Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK动态代理为 Mapper 接口生成代理对象 proxy,代理对象会拦截接口方法,转而执行 MapperStatement 所代表的 sql,然后将 sql 执行结果返回。

    10、Mybatis 是如何进行分页的?分页插件的原理是什么?

    Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

    分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。

    举例:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10

    11、Mybatis执行批量插入可以返回数据库主键列表吗?

    当然是可以的。

    我们都知道Mybatis在插入单条数据的时候有两种方式返回自增主键:

    (1)对于支持生成自增主键的数据库:增加 useGenerateKeys和keyProperty ,<insert>标签属性。

    (2)不支持生成自增主键的数据库:使用<selectKey>。

    <insert id="insertAuthor" useGeneratedKeys="true"
        keyProperty="id">
      insert into Author (username, password, email, bio) values
      <foreach item="item" collection="list" separator=",">
        (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
      </foreach>
    </insert>
    

    从官网资料可以看出Mybatis是支持批量插入时返回自增主键的。

    但是在本地测试的时候使用上述方式确实不能返回自增id,而且还报错(不认识keyProperty中指定的Id属性),然后在网上找相关资料。终于在Stackoverflow上面找到了一些信息。

    解决办法:

    (1)升级Mybatis版本到3.3.1。官方在这个版本中加入了批量新增返回主键id的功能

    (2)在Dao中不能使用@param注解。

    (3)Mapper.xml中使用list变量(parameterType="Java.util.List")接受Dao中的参数集合。

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

    Mybatis 动态 sql 可以让我们在 Xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能, Mybatis 提供了 9 种动态 sql 标签 trim | where | set | foreach | if | choose | when | otherwise | bind 。
    其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。

    12、Mybatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?

    第一种是使用 <resultMap> 标签,逐一定义列名和对象属性名之间的映射关系。第一种是使用sql 列的别名功能,将列别名书写为对象属性名,比如 T_NAME AS NAME,对象属性名一般是 name,小写,但是列名不区分大小写, Mybatis 会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成 T_NAME AS NaMe, Mybatis 一样可以正常工作。有了列名与属性名的映射关系后, Mybatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

    13、Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别。

    能,Mybatis不仅可以执行一对一、一对多的关联查询,还可以执行多对一,多对多的关 联查询,多对一查询,其实就是一对一查询,只需要把selectOne()修改为selectList()即可; 多对多查询,其实就是一对多查询,只需要把selectOne()修改为selectList()即可。

    关联对象查询,有两种实现方式,一种是单独发送一个sql去查询关联对象,赋给主对象, 然后返回主对象。另一种是使用嵌套查询,嵌套查询的含义为使用join查询,一部分列是A对 象的属性值,另外一部分列是关联对象B的属性值,好处是只发一个sql查询,就可以把主对象 和其关联对象查出来。

    那么问题来了,join查询出来100条记录,如何确定主对象是5个,而不是100个?其去重 复的原理是 resultMap标签内的 id 子标签,指定了唯一确定一条记录的id列,Mybatis根据 id 列值来完成100条记录的去重复功能, id 可以有多个,代表了联合主键的语意。

    同样主对象的关联对象,也是根据这个原理去重复的,尽管一般情况下,只有主对象会有 重复记录,关联对象一般不会重复。

    14、Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?

    Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,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, 几乎所有的包括 Hibernate, 支持延迟加载的原理都是一样的。

    15、Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?

    不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复;
    原因就是 namespace+id 是作为 Map<String, MapperStatement>的 key使用的,如果没有 namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同。

    16、Mybatis中如何执行批处理?

    使用BatchExecutor完成批处理。

    17、Mybatis是否可以映射Enum枚举类?

    Mybatis可以映射枚举类,不单可以映射枚举类,Mybatis可以映射任何对象到表的一列上。映射方式为自定义一个TypeHandler,实现TypeHandler的setParameter()和getResult()接口方法。TypeHandler有两个作用,一是完成从javaType至jdbcType的转换,二是完成jdbcType至javaType的转换,体现为setParameter()和getResult()两个方法,分别代表设置sql问号占位符参数和获取列查询结果。

    18、Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?

    虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的B标签依然可以定义在任何地方,Mybatis都可以正确识别。

    原理是,Mybatis解析A标签,发现A标签引用了B标签,但是B标签尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已经存在,A标签也就可以正常解析完成了。

    19、简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?

    Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象,其每个子元素会被解析为ParameterMapping对象。<resultMap>标签会被解析为ResultMap对象,其每个子元素会被解析为ResultMapping对象。每一个<select>、<insert>、<update>、<delete>标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。

    20、为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?

    Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。

    21、接口绑定有几种实现方式,分别是怎么实现的?

    接口绑定有两种实现方式:
    一种是通过注解绑定,就是在接口的方法上面加上@Select@Update等注解里面包含Sql语句来绑定
    另外一种就是通过xml里面写SQL来绑定,在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。

    22、Mybatis默认的一级缓存是开启的还是关闭的?

    开启的

    23、当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

    第1种: 通过在查询的sql语句中****定义字段名的别名,让字段名的别名和实体类的属性名一致

    <select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”> 
           select order_id id, order_no orderno ,order_price price form orders where order_id=#{id}; 
    </select> 
    

    第2种: 通过<resultMap>来映射字段名和实体类属性名的一一对应的关系

    <select id="getOrder" parameterType="int" resultMap="orderresultmap">
            select * from orders where order_id=#{id}
        </select>
       <resultMap type=”me.gacl.domain.order” id=”orderresultmap”> 
            <!–用id属性来映射主键字段–> 
            <id property=”id” column=”order_id”> 
            <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–> 
            <result property = “orderno” column =”order_no”/> 
            <result property=”price” column=”order_price” /> 
        </reslutMap>
    

    第3种:在MyBatis的全局配置文件中开启驼峰命名规则
    mapUnderscoreToCamelCase:true/false

    <!--是否启用下划线与驼峰式命名规则的映射(如first_name => firstName)-->
    <configuration>  
        <settings>  
            <setting name="mapUnderscoreToCamelCase" value="true" />  
        </settings>  
    </configuration>
    

    24、在mapper中如何传递多个参数?

    第一种:使用占位符的思想
    在映射文件中使用#{0},#{1}代表传递进来的第几个参数
    使用@param注解:来命名参数

    {0},#{1}方式

    //对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。
    
    <select id="selectUser"resultMap="BaseResultMap">  
        select *  fromuser_user_t   whereuser_name = #{0} anduser_area=#{1}  
    </select>  
    

    @param注解方式

    public interface usermapper { 
             user selectuser(@param(“username”) string username, 
             @param(“hashedpassword”) string hashedpassword); 
            }
     <select id=”selectuser” resulttype=”user”> 
             select id, username, hashedpassword 
             from some_table 
             where username = #{username} 
             and hashedpassword = #{hashedpassword} 
        </select>
    

    第二种:使用Map集合作为参数来装载

    try{
                //映射文件的命名空间.SQL片段的ID,就可以调用对应的映射文件中的SQL
    
    
                /**
                 * 由于我们的参数超过了两个,而方法中只有一个Object参数收集
                 * 因此我们使用Map集合来装载我们的参数
                 */
                Map<String, Object> map = new HashMap();
                map.put("start", start);
                map.put("end", end);
                return sqlSession.selectList("StudentID.pagination", map);
            }catch(Exception e){
                e.printStackTrace();
                sqlSession.rollback();
                throw e;
            }finally{
                MybatisUtil.closeSqlSession();
            }
    
        <!--分页查询-->
        <select id="pagination" parameterType="map" resultMap="studentMap">
    
            /*根据key自动找到对应Map集合的value*/
            select * from students limit #{start},#{end};
    
        </select>
    

    相关文章

      网友评论

          本文标题:Mybatis--常见面试题整理

          本文链接:https://www.haomeiwen.com/subject/gvugtrtx.html