zoukankan      html  css  js  c++  java
  • Mybatis面试题

    1,什么是Mybatis

    1  Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时

    只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建

    statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性

    能,灵活度高。

    2  MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数

    据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

    通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过

     java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最

    后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。(从执行 sql 到返

    回 result 的过程)。

    2,Mybatis的优点

    1、简单易学。mybatis本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar加配置几个sql映射文件,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现;

    2、灵活。mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql基本上可以实现我们不使用数据访问框架可以实现的所有功能,或许更多;

    3、解除sql与程序代码的耦合。通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性;

    4、提供映射标签,支持对象与数据库的orm字段关系映射;

    5、提供对象关系映射标签,支持对象关系组建维护;

    6、提供xml标签,支持编写动态sql。

    3,Mybatis的缺点

    SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写

    SQL 语句的功底有一定要求。

    SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

     

    4,MyBatis框架适用场合:

    (1)MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。

    (2)对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。

    5,#{}和${}的区别是什么?

    1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id".

       $将传入的数据直接显示生成在sql中。如:order by $user_id$,如果传入的值是111,那么解析成sql时的值为order by 111, 如果传入的值是id,则解析成的sql为order by id.

    2. #方式能够很大程度防止sql注入。 

        $方式无法防止Sql注入。

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

    select * from emp where empno = 7360 or 1=1  -- ${}

    select * from emp where empno = '7369'     -- #{}

    没有特殊需求情况下,都要使用#{}

    6,Mybatis执行流程

          

    1、 mybatis配置

    SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。

    mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。

    2、 通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂

    3、 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。

    4、 mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。

    5、 MappedStatement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。

    6、 MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。

    7、 MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

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

    第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。

      

    第二种是使用sql列的别名功能,将列别名书写为对象属性名,有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

    (数据库中部门对象列为dname,实体为deptName  select  deptno,dname deptName,loc from dept )

     

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

    答:Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,多对一,collection指的就是一对多,多对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

        延迟加载代码实现(一对多collection 查询部门时,查出该部门的员工):

           实体:Dept   Emp

                      需要在Dept中加入private List<Emp> empList;  和getter setter

            daoDeptDao  

                     /**

         * 查询所有部门

         * @return

         */

        List<Dept>  listAll();

    EmpDao:

       /**

         * 根据部门编号获取员工

         * @param deptNo

         * @return

         */

        List<Emp> listEmpByDeptNo(int deptNo);

            配置文件:DeptMapper.xml :

                      <!--查询所有部门-->

         <select id="listAll" resultMap="deptEmp">

              select  * from dept

         </select>

        <!--映射-->

        <resultMap id="deptEmp" type="com.aaa.mybatis.entity.Dept">

            <id property="deptNo" column="deptno"/>

            <result property="deptName" column="deptname"/>

            <result property="loc" column="loc"/>

            <collection property="empList" column="deptno" ofType="com.aaa.mybatis.entity.Emp"

            select="com.aaa.mybatis.dao.EmpDao.listEmpByDeptNo">

            </collection>

        </resultMap>

     EmpMapper.xml:

          <!--根据部门编号获取员工-->

        <select  id="listEmpByDeptNo" resultType="com.aaa.mybatis.entity.Emp">

            select empno,empname,salary from emp where deptno=#{deptNo}

        </select>

       主配置文件:mybatis-config.xml  中的 settings

                 <!--开启全局懒加载-->

            <setting name="lazyLoadingEnabled" value="true"></setting>

             测试:

                     /**

         * 测试延时加载

         */

        @Test

        public void testListAll(){

            SqlSession sqlSession = null;

            try {

                sqlSession = SqlSessionFacotryUtil.getSqlSession();

                DeptDao deptDao = sqlSession.getMapper(DeptDao.class);

                List<Dept> depts = deptDao.listAll();

                if(depts!=null&&depts.size()>0){

                    for (Dept dept : depts) {

                        dept.getEmpList();

                    }

                }

            } catch (Exception e) {

                e.printStackTrace();

            } finally {

                if(sqlSession!=null)

                    sqlSession.close();

            }

        }

    如果下面代码注释掉,开启懒加载,底层查询数据库时,只执行了部门查询,如果不开启懒加载,部门和该部门的员工都查询了

       if(depts!=null&&depts.size()>0){

                    for (Dept dept : depts) {

                        dept.getEmpList();

                    }

                }

    它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器intercept()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。(ctrl+n   ProxyFactory    实现类:JavassistProxyFactory 的invoke和CglibProxyFactory的  intercept)

    https://my.oschina.net/wenjinglian/blog/1857581?from=singlemessage

    (Javaassist 就是一个用来 处理 Java 字节码的类库。)

    当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。

    9,模糊查询like语句该怎么写?

    在Java代码中添加sql通配符。

          List<Dept> depts = deptDao.listByLike("%dev%");

           mapper中:

    <!--模糊查询 like-->

        <select id="listByLike" parameterType="string" resultType="com.aaa.mybatis.entity.Dept">

            select  deptno,dname as deptName,loc from dept  where dname like  #{param}

        </select>

     在sql语句中拼接通配符

         select  deptno,dname as deptName,loc from dept  where dname like '%${param}%'

           '%#{param}%' 写法错误的

        select  deptno,dname as deptName,loc from dept  where dname like concat('%',# {param},'%')

       oracle   : '%'||# {param}||'%'    concat(concat('%',#{param}),'%')  

       mysql:  concat('%',# {param},'%')

       mysql 或者oracle  '%${param}%'

    10,请问MyBatis中的动态SQL是什么意思?

    对于一些复杂的查询,我们可能会指定多个查询条件,但是这些条件可能存在也可能不存在,需要根据用户指定的条件动态生成SQL语句。如果不使用持久层框架我们可能需要自己拼装SQL语句,还好MyBatis提供了动态SQL的功能来解决这个问题。MyBatis中用于实现动态SQL的元素主要有:

    - if

    - choose / when / otherwise

    - trim

    - where

    - set

    - foreach

    11,请说明一下MyBatis中命名空间(namespace)的作用是什么?

    在大型项目中,可能存在大量的SQL语句,这时候为每个SQL语句起一个唯一的标识(ID)就变得并不容易了。为了解决这个问题,在MyBatis中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个SQL语句就成了定义在这个命名空间中的一个ID。只要我们能够保证每个命名空间中这个ID是唯一的,即使在不同映射文件中的语句ID相同,也不会再产生冲突了。

     12,如何执行批量插入?

        SQL 基础:

           -- 批量插入

    --  mysql

    insert into dept values (18,'dt1','1floor'),(19,'dt1','1floor'),(20,'dt1','1floor');

    -- oracle

    insert into dept

    select 18,'dt1','1floor' from dual UNION

    select 19,'dt1','1floor' from dual UNION

    select 20,'dt1','1floor' from dual

    dao:

    /**

         * 批量插入

         * @param depts

         * @return

         */

        int  batchAdd(List<Dept> depts);

     mapper:

            <!--批量插入-->

        <insert id="batchAdd">

             insert into dept values

            <foreach collection="list" item="dept" index="i"   separator=",">

                (null,#{dept.deptName},#{dept.loc})

            </foreach>

        </insert>

    测试:

           List<Dept> deptList = new ArrayList<Dept>(5);

                Dept dept = new Dept();

                dept.setDeptName("team33");

                dept.setLoc("33floor");

                deptList.add(dept);

                Dept dept1 = new Dept();

                dept1.setDeptName("team44");

                dept1.setLoc("44floor");

                deptList.add(dept1);

                Dept dept2 = new Dept();

                dept2.setDeptName("team55");

                dept2.setLoc("55floor");

                deptList.add(dept2);

                Dept dept3 = new Dept();

                dept3.setDeptName("team66");

                dept3.setLoc("66floor");

                deptList.add(dept3);

                Dept dept4 = new Dept();

                dept4.setDeptName("team77");

                dept4.setLoc("77floor");

                deptList.add(dept4);

                int i = deptDao.batchAdd(deptList);

                sqlSession.commit();

     

    13,如何获取自动生成的(主)键值?

      MySQL:Mapper文件insert语句设置    useGeneratedKeys="true" keyProperty="id"

         <!--添加部门并返回自增ID-->

        <insert id="add" useGeneratedKeys="true" keyProperty="deptNo">

            insert into dept values(null,#{deptName},#{loc})

        </insert>

         自增生成之后的值赋值实体中的属性deptNo

    Oracle:Mapper文件insert语句增加

    <selectKey keyProperty="id" order="BEFORE" resultType="Integer">

        select xxx_SEQ.nextval from dual

    </selectKey>

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

         hql:" from Emp" Emp 实体类

                    session.save(emp);

                   session.saveOrUpdate(emp);

                  session.update(emp);

                  session.deleteByke(empno);

                   session.delete(emp); 

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

    15,在mapper中如何传递多个参数?

    (1)第一种:#{0}或者是#{arg0} 看是否是3.4.2及之前#{0} 或者之后#{arg0}

          dao:

              /**

         * 添加,传递多个参数

         * @param dname

         * @param loc

         * @return

         */

        int addA(String dname,String loc);

    mapper:

           <!-- 添加,多个参数  3.4.2版本之前#{0}   之后#{arg0}    https://www.cnblogs.com/zhangmingcheng/p/9922236.html-->

        <insert id="addA">

            insert into dept values(null,#{arg0},#{arg1})

        </insert>

    (2)第二种: 使用 @param 注解:

      dao:

           /**

         * 添加,@Param传递多个参数

         * @param dname

         * @param loc

         * @return

         */

        int addB(@Pa <!--添加,@Param传递多个参数-->

    mapper:

        <insert id="addB">

            insert into dept values(null,#{deptName},#{loc})

        </insert>ram("deptName") String dname,@Param("loc") String loc);

     

    (3)第三种:多个参数封装成map或者是实体

              dao方法:

       /**

         * 添加

         * @param dept

         * @return

         */

        int add(Dept dept);

        mapper:

       <!--添加部门并返回自增ID-->

        <insert id="add" useGeneratedKeys="true" keyProperty="deptNo">

            insert into dept values(null,#{deptName},#{loc})

        </insert>

      

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

    不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;

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

    但是,在以前的Mybatis版本的namespace是可选的,不过新版本的namespace已经是必须的了。

     一对一、一对多的关联查询 ? 

          参考以前上课时的例子(参考刚才讲的懒加载的例子也行)

     

    16, Mybatis的一级、二级缓存

           参考项目(mybatis_interview_cache)

    1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

    2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置<cache/> ;

    3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新

     17,使用MyBatis的mapper接口调用时有哪些要求?

    ①  Mapper接口方法名和mapper.xml中定义的每个sql的id相同;

    ②  Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同(配置文件中可以省略,如果写,必须相同);

    ③  Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同(通过resultMap也可以,更新方法没有返回值配置的);

    ④  Mapper.xml文件中的namespace即是mapper接口的类路径。

     

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

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

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

    Dao 接口,就是人们常说的 Mapper 接口,接口的全限名,就是映射文件中的 namespace 的值,接口的方法名,就是映射文件中 MappedStatement 的 id 值,接口方法内的参数,就是传递给 sql 的参数。Mapper 接口是没有 实 现 类 的 , 当 调 用 接 口 方 法 时 , 接 口 全 限 名 + 方 法 名 拼 接 字 符 串 作 为 key 值 , 可 唯 一 定 位 一 个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到

    namespace 为 com.mybatis3.mappers.StudentDao 下 面 id = findStudentById 的

    MappedStatement。在 Mybatis 中,每一个<select>、<insert>、<update>、<delete>标签,都会被解

    析为一个 MappedStatement 对象。

    Dao 接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。

    Dao 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理 proxy 对象,代理对象 proxy 会拦截接口方法,转而执行MappedStatement 所代表的 sql,然后将 sql 执行结果返回。

        DeptDao deptDao = sqlSession.getMapper(DeptDao.class);

    https://www.cnblogs.com/hopeofthevillage/p/11384848.html

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

    答:Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

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

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

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

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

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

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

             // sqlSessionFactory.openSession(ExecutorType.SIMPLE);

             //  sqlSessionFactory.openSession(ExecutorType.REUSE);

             // sqlSessionFactory.openSession(ExecutorType.BATCH);

     

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

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

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

    mybatis分页插件(非官网)

    1,添加jar包

            <!-- 分页插件pagehelper -->        <dependency>            <groupId>com.github.pagehelper</groupId>            <artifactId>pagehelper</artifactId>            <version>5.0.0</version>        </dependency>        <dependency>            <groupId>com.github.pagehelper</groupId>            <artifactId>pagehelper-spring-boot-autoconfigure</artifactId>            <version>1.2.3</version>        </dependency>        <dependency>            <groupId>com.github.pagehelper</groupId>            <artifactId>pagehelper-spring-boot-starter</artifactId>            <version>1.2.3</version>        </dependency>      <!-- 分页插件pagehelper -->

    2,添加springboot配置

                  https://pagehelper.github.io/

    https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md

     #分页插件

    #helperDialect属性来指定分页插件使用哪种方言

    pagehelper.helper-dialect=oracle

    #当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。

    pagehelper.reasonable=true

    #支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。

    pagehelper.support-methods-arguments=true

    #增加了该参数来配置参数映射,用于从对象中根据属性名取值

    pagehelper.params=count=countSql

    3,具体使用

           与以前controller区别

      //设置当前第几页和每页显示数量    PageHelper.startPage(Integer.valueOf(map.get("pageNo")+""),Integer.valueOf(map.get("pageSize")+""));

            //用PageInfo对结果进行包装

            PageInfo<Map> pageInfo =new PageInfo<Map>(newsService.getList());

    /**

         * 分页部门查询

         * @param map

         * @return

         */

        @ResponseBody

        @RequestMapping("page")

        public Object page(@RequestParam Map map){

            int pageNo = Integer.valueOf(map.get("pageNo")+"");

            int pageSize = Integer.valueOf(map.get("pageSize")+"");

            //初始化配置

            PageHelper.offsetPage(pageNo,pageSize);

            PageInfo<Map> pageInfo = new PageInfo<Map>(deptService.getList());

           //如果使用easyui可以这样封装,其他框架,自己根据pageInfo解析

            Map tmap  = new HashMap();

            tmap.put("total",pageInfo.getTotal());

            tmap.put("rows",pageInfo.getList());

            return tmap;

        }

  • 相关阅读:
    LeetCode周赛#209
    CodeBlocks相关配置
    LeetCode双周赛#36
    LCCUP 2020 秋季编程大赛 补题
    while和do while区别
    完全背包--piggy-bank
    数组定义在哪里???重要!!!
    01背包---点菜问题
    java源程序命名规则
    hdu1159-----最长公共子序列LCS
  • 原文地址:https://www.cnblogs.com/czs528/p/13527483.html
Copyright © 2011-2022 走看看