zoukankan      html  css  js  c++  java
  • java【MyBatis】面试题

    一、MyBatis编程步骤。

      1.创建SqlSessionFactory对象。

      2.通过SqlSessionFactory获取SqlSession对象。

      3.通过SqlSession获得Mapper代理对象。

      4.通过Mapper代理对象,执行数据库操作。

      5.执行成功,则使用SqlSession提交事务。

      6.执行失败,则使用SqlSession回滚事务。

      7.最终,关闭回话。

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

      ${}是Proerties文件中的变量占位符,它可以用于XML标签属性值和SQL内部,属于字符串替换。例如将${driver}会被静态替换为com.mysql.jdbc.Driver:

      

    <dataSource type="UNPOOLED"> 
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    </dataSource>

    ${}也可以对传递进来的参数原样拼接在SQL中。代码如下:

    <select id="getSubject3" parameterType="Integer" resultType="Subject"> 
    SELECT * FROM subject WHERE id = ${id}
    </select>
    • 实际场景下,不推荐这么做。因为,可能有SQL注入的风险。

      #{}是SQL的参数占位符,Mybatis会将SQL中的#{}替换为?号,在SQL执行前会使用。PreparedStatement的参数设置方法,按序给SQL的?号占位符设置参数值,比如ps.setInt(0,parametervalue).所以,#{}是预编译处理,可以有效防止SQL注入提高系统安全性处理。

      另外,#{}和${}的取值方式非常方便。例如:#{item.name}的取值方法,为使用反射从参数对象中,获取item对象的name属性值,相当于param.getItem().getName()。

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

      第一种,通过在查询的SQL语句中定义字段名和别名,让字段名的别名和实体类的属性名一致。代码如下:

    <select id="selectOrder" parameterType="Integer" resultType="Order"> 
    SELECT order_id AS id, order_no AS orderno, order_price AS price FROM orders WHERE order_id = #{id}
    </select>

    这里,还有几点建议:

    • 1.数据库的关键字,统一使用大写,例如:SELECT、AS、FROM、WHERE
    • 2.每5个查询字段换一行,保持整齐。
    • 3.,的后面,和=的前后,需要有空格,更加清晰。
    • 4.SELECT、FROM、WHERE等,单独一行,高端大气。

      第二种,是第一种的特殊情况,大多数场景下,数据库字段名和实体类中的属性名,主要是前者为下划线风格,后者为驼峰风格。在这种情况下,可以直接配置如下,实现自动的下划线转驼峰的功能。

    <setting name="logImpl" value="LOG4J"/>
    <setting name="mapUnderscoreToCamelCase" value="true" />
    </settings>

    也就说,约定大于配置,非常推荐!

      第三种,通过<resultMap>来映射字段名和实体类属性名的--对应的关系,代码如下:

    <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" />
    </resultMap>
    <select id="getOrder" parameterType="Integer" resultMap="OrderResultMap">
    SELECT * FROM orders WHERE order_id = #{id}
    </select>
    • 此处SELECT * 仅仅作为示例只用,实际场景下,千万千万不要这么干。用多少字段,查询多少字段。
    • 相比第一种,第三种的重用性会一些。

    四、XML映射文件中,除了常见的select|insert|update|delete标签之外,还有那些标签?

    如下部分,可见

    • <cache/>标签,给定命名空间的缓存配置。
      • <cache-ref/>标签,其他命名空间缓存配置的引用。
    • <resultMap>标签,是最负责也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
    • <sql/>标签,引用<sql/>标签的语句。
    • <selectKey/>标签,不支持自增的主键生成策略标签。
    • <if/>
    • <choose/>、<when/>、<otherwise/>
    • <trim/>、<where/>、<set/>
    • <foreach/>
    • <bind/>

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

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

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

    Mapper接口,对应的关系如下:

    • 接口的全限名,就是映射文件中的“namespace”的值。
    • 接口的方法名,就是映射文件中MapperStatement的“id”值。
    • 接口方法内的参数,就是传递给SQL的参数。

      Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个对应的MapperStatement。举例:com.mybatis3.mappers.findStudentById,可以唯一找到“namespace”为 com.mybatis3.mappers.StudentDao 下面 "id" 为 findStudentById 的MappedStatement 。

      总结来说,在Mybatis中,每一个<select/>、<insert/>、<update/>、<delete/>标签,都会被解析为一个MapperStatement对象。

      另外,Mapper接口的实现类,通过Mybatis使用JDK Proxy自动生成其代理对象Proxy,而代理对象Proxy会拦截接口方法,从而“调用”对应的MappedStatement方法,最终执行SQL,返回执行结果,整体流程如下:

    其中,SqlSession在调用Executor之前,会获得对应的MappedStatement方法。例如:

    DefaultSqlSession#select(String statement, Object parameter, RowBounds rowBounds, 
    ResultHandler handler) 方法,代码如下:
    // DefaultSqlSession.java
    @Override
     public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { 
        try {    
                // 获得 MappedStatement 对象
                MappedStatement ms = configuration.getMappedStatement(statement);
                // 执行查询
                executor.query(ms, wrapCollection(parameter), rowBounds, handler);
            } catch (Exception e) {
     throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
    } finally {
     ErrorContext.instance().reset(); 
    }
     }

      注意:mapper接口里的方法,是不能进行重载的,因为时全限名+方法名的保存和寻找策略。所以有时,想个Mapper接口里的方法名,还是蛮闹心的。

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

      接口绑定有三种实现方式:

    • 第一种,通过XML Mapper里面写SQL来绑定。在这种情况下,要指定XML映射文件里面的“namespace”必须为接口的全路径名。
    • 第二种,通过注解绑定,就是在接口的方法上面加上@select、@update、@Insert、@Delete注解,里面包含SQL语句来绑定。
    • 第三种,是第二种的特例,也是通过注解绑定,在接口的方法上面加上@SelectProvider、@UpdateProvider、@InsertProvider、@DeleteProvider注解,通过java代码,生成对应的动态SQL。

    实际场景下,最最最推荐的第一种方式。因为SQL通过注解写在java代码中,会非常杂乱,而写在XML中,更加有整体性,并且可以更加方便的使用OGNL表达式。

    八、Mybatis的XML Mapper文件中,不同的XML映射文件,id是否可以重复?

      不同的XML Mapper文件,如果配置了“namespace”,那么id可以重复;如果没有配置“namespace”,那么id不能重复,毕竟“namespace”不是必须的,知识最佳实践而已。

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

    九、如何获取自动生成的(主)键值?

      不同的数据库,获取自动生成 的(主)键值的方式不同。

      Mysql有两种方式,但是自增主键,代码如下:

    //方式一,使用 useGeneratedKeys + keyProperty 属性 
    <insert id="insert" parameterType="Person" useGeneratedKeys="true" keyProperty="id"> 
    INSERT INTO person(name, pswd) VALUE (#{name}, #{pswd})
    </insert>
    // 方式二,使用 `<selectKey />` 标签 
    <insert id="insert" parameterType="Person" useGeneratedKeys="true" keyProperty="id"> 
    <selectKey keyProperty="id" resultType="long" order="AFTER">
     SELECT LAST_INSERT_ID() 
    </selectKey>
     INSERT INTO person(name, pswd) VALUE (#{name}, #{pswd})
     </insert>
    • 其中,方式一较为常用。
    Oracle 有两种方式,序列和触发器。因为自己不了解 Oracle ,所以问了银行的朋友,他们是使用序列。而基于序 
    列,根据 <selectKey /> 执行的时机,也有两种方式,代码如下:
    // 这个是创建表的自增序列 
    CREATE SEQUENCE student_sequence
    INCREMENT BY 1
    NOMAXVALUE NOCYCLE
    CACHE 10;
    // 方式一,使用 `<selectKey />` 标签 + BEFORE
    <insert id="add" parameterType="Student">
     <selectKey keyProperty="student_id" resultType="int" order="BEFORE"> 
    select student_sequence.nextval FROM dual 
    </selectKey> 
    INSERT INTO student(student_id, student_name, student_age) VALUES (#{student_id},#{student_name},#{student_age}) 
    </insert>
    // 方式二,使用 `<selectKey />` 标签 + AFTER
    <insert id="save" parameterType="com.threeti.to.ZoneTO" > 
    <selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER" > SELECT SEQ_ZONE.CURRVAL AS id FROM dual 
    </selectKey>
     INSERT INTO TBL_ZONE (ID, NAME ) VALUES (SEQ_ZONE.NEXTVAL, #{name,jdbcType=VARCHAR}) 
    </insert>

    他们使用第一种方式,因为使用触发器,在数据更改就会有问题:就改线上某几条数据时候,需要手动操作的时候。

    十、Mybatis执行批量插入,能返回数据库主键列表么?

    能,JDBC能做,Mybatis当然也可以做。

    十一、在Mapper中如何传递多个参数?

    第一种。使用Map集合,装在多个参数进行传递。

    // 调用方法 
    Map<String, Object> map = new HashMap(); 
    map.put("start", start);
    map.put("end", end); 
    return studentMapper.selectStudents(map); 
    
    // Mapper 接口 
    List<Student> selectStudents(Map<String, Object> map); 
    
    // Mapper XML 代码 
    <select id="selectStudents" parameterType="Map" resultType="Student"> 
    SELECT * FROM students LIMIT #{start}, #{end} 
    </select>

    第二种:保持传递多个参数,使用@Param注解。代码如下:

    // 调用方法 
    return studentMapper.selectStudents(0, 10); 
    
    // Mapper 接口 
    List<Student> selectStudents(@Param("start") Integer start, @Param("end") Integer end);
    
    // Mapper XML 代码
    <select id="selectStudents" resultType="Student"> 
    SELECT * FROM students LIMIT #{start}, #{end} 
    </select>

    第三种,保持传递多个参数,不实用@Param注解。

    // 调用方法 
    return studentMapper.selectStudents(0, 10);
    
     // Mapper 接口 
    List<Student> selectStudents(Integer start, Integer end); 
    
    // Mapper XML 代码 
    <select id="selectStudents" resultType="Student"> 
    SELECT * FROM students LIMIT #{param1}, #{param2} 
    </select>

    其中,按照参数在方法中的位置,从1开始,逐个为#{param1}、#{param2}、#{param3}不断向下。

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

    Mybaits可以映射枚举类,对应的实现类为EnumTypeHandler或者EnumOrdinalTypeHandler

    • EnumTypeHandler,基于Enum.name属性(String)。默认
      • EnumOrdinnalTypeHandler,基于Enum.ordinal属性(int)。可通过<setting name="defaultEnumTypeHandler" value="EnumOrdinalTypeHandler"/>来设置。

    当然,实际开发场景,我们很少使用Enum类型,更好的方式是常量。

    public class Dog { 
    public static final int STATUS_GOOD = 1;
     public static final int STATUS_BETTER = 2;
     public static final int STATUS_BEST = 3private int status;
    
     }

    并且,不但可以映射枚举,Mybatis可以映射任何对象到表的一列上。映射方式为自定义一个TypeHandler类,实现TypeHandler的#setParameter(...)和#getResult(...)接口方法。

    TypeHandler有两个作用:

    • 一个是,完成从javaType到jdbcType的转换。
    • 二是,完成jdbcType到javaType的转换。

    具体体现为#setParameter(...)和#getResult(...)两个方法,分别代表设置SQl问好占位符参数和获取列查询结果。

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

      Mybatis有四种Excutor执行器,分别是SimpleExcutor、ReuseExcutor、BatchExcutor、CachingExcutor。

    • SimpleExcutor:每执行一次update或者select操作,就创建一个statement对象,用完立刻关闭statement对象。
    • ReuseExcutor:执行update或者select操作,以SQL作为key查找缓存的statement对象,存在就使用,不存在就创建;用完后,不关闭statement对象,而是放置于缓存Map<String,Statement>内,供下一次使用,简而言之,就是重复使用statement对象。
    • BatchExcutor:执行update操作(没有select操作,因为JDBC批量处理不支持select操作),将所有SQL都添加到批处理中(通过addBatch方法)。等待统一执行(使用executeBatch方法)。它缓存了多个Statement对象,每个statement对象都调用addBatch方法完毕后,等待一次执行executeBatch批量处理,实际上,整个过程与JDBC批量处理是相同的。
    • CachingExecutor:在上述的三个执行器上,增加二级缓存的功能。
    通过设置<setting name="defaultExecutorType" value="">的value属性,可传入SIMPLE、REUSE、BATCH三个值,分别使用SimpleExecutor、ReuseExecutor、BatchExecutor执行器。
    通过设置<Setting nam=“cacheEnable” value=“”>的value属性为true时,创建CacheExecutor执行器。

    十四、MyBatis 如何执行批量插入?

    首先,在mapper XML编写一个简单的Insert语句,代码如下:

    <insert id="insertUser" parameterType="String"> 
    INSERT INTO users(name) VALUES (#{value})
    </insert>

    然后,在对应的mapper接口中,编写映射方法。

    public interface UserMapper {

    void insertUser(@Param("name") String name);

    }

    最后,调用该mapper接口方法,代码如下:

    private static SqlSessionFactory sqlSessionFactory;
    
     @Test
     public void testBatch() { 
    // 创建要插入的用户的名字的数组 
    List<String> names = new ArrayList<>(); 
    names.add("占小狼");
    names.add("朱小厮"); 
    names.add("徐妈");
     names.add("飞哥");
     // 获得执行器类型为 Batch 的 SqlSession 对象,并且 autoCommit = false ,禁止事务自动提交 
    try (
        SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) { 
    // 获得 Mapper 对象
     UserMapper mapper = session.getMapper(UserMapper.class);
     // 循环插入
     for (String name : names) {
     mapper.insertUser(name);
     }
    // 提交批量操作
     session.commit(); 
    }
     }
    代码比较简单,胖友仔细看看。当然,还有另一种方式,代码如下:
    INSERT INTO [表名]([列名],[列名]) 
    VALUES ([列值],[列值])), ([列值],[列值])), ([列值],[列值]));
    • 对于这种方式,需要保证单条SQL不超过语句的最大限制max_allowed_packet大小,默认为1M

    十五、介绍Mybatis的一级缓存和二级缓存的概念和实现原理?

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

      Mybatis仅支持association关联对象和collection关联集合对象的延迟加载。其中association指的是一对一,collection指的是一对多查询。

      在Mybatis配置文件中,可以配置<setting name=“lazyLoadingEnable” value=“true”>来开启延迟加载的功能。默认情况下,延迟加载的功能是关闭的。

      它的原理是,使用CGLIB或者javassist(默认)创建目标的代理对象,当调用代理对象的延迟加载属性的getting方法时,进入拦截器方法。比如调用a.getB().getName()方法,进入拦截器invoke(...)方法,发现a.getB()方法需要延迟加载时,那么就会单独发送事先保存好的关联B对象的SQL,把B查询上来,然后a.getB(b)方法,于是a对象b属性就有数据了,接着完成a.getB().getName()方法的调用,这就是延迟加载的基本原理。

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

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

    能,Mybatis不仅可以执行一对一,一对多的查询,还可以执行多对一,多对多的关联查询。

    • 多对一查询,其实就是一对一查询,只需要把selectOne(。。。)修改为selectList(。。)即可。

      https://www.cnblogs.com/qingmuchuanqi48/p/13800057.html就是案例

    • 多对多查询,其实即使一对多查询,只需要把selectOne(。。。)修改为selectList(。。。)即可
  • 相关阅读:
    MySql--密码查看或修改
    javaweb学习--Servlet开发(一)
    javaweb学习--http协议
    Vue.js学习笔记(一)
    javascript事件处理
    javascript时间的相关操作
    代理模式(Proxy)
    单例模式(Singleton)
    ArrayBlockingQueue和LinkedBlockingQueue队列
    自增(++)和自减(--)运算符
  • 原文地址:https://www.cnblogs.com/qingmuchuanqi48/p/13812573.html
Copyright © 2011-2022 走看看