Mybatis面试题准备+源码分析

写在前面:

检验知识是否掌握、了解程度,一个好的办法,通过面试题来自我判断。面试题是考官考量你知识是否准备充分有没有深入了解其中的原理能否匹配工作上的要求。从侧面来说,刷题也是一种进步、学习的方法。平时积累了一些有用质量比较高的面试题,现在拿出来趁热打铁一定能大力出奇迹。Let’s Go Go Go。

源码分析:

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

面试宝典:

简单谈谈你对Mybatis的理解?

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(PlainOld Java Objects,普通老式 Java 对象)为数据库中的记录。

MyBatis接口绑定的优点是什么?

接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定,我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法,可以有更加灵活的选择和设置。

避免省去了传统Dao开发接口实现类方法中存在大量模板方法,(调用sqlsession方法时将statement的id硬编码了)Ps:查询学号为001的学生下次换个学号需要改动代码里的学号。

实现MyBatis接口绑定分别有哪几种方式?

①通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;

mapper代理方法(程序员只需要mapper接口)

xml里面写SQL来绑定,在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。注意4大规范

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

(2)Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;

(3)Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;

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

MyBatis如何实现一对一关联关系?

使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。其实Mybatis会在幕后自动创建一个 ResultMap,再基于属性名来映射列到 JavaBean 的属性上。

MyBatis如何实现一对多关联关系?

mybatis中使用resultMap完成高级输出结果映射。如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系

说说MyBatis动态SQL的具体使用步骤?

if

choose (when, otherwise)

trim (where, set)

foreach

if


choose (when, otherwise)

MyBatis 提供了 choose 元素,它有点像Java 中的 switch

语句。

提供了“title”就按“title”查找,提供了“author”就按“author”

查找的情形,若两者都没有提供,就返回所有符合条件的

BLOG。

Where

若语句的开头为“AND”或“OR”,where 元素也会将它们去

除。(若只有一个满足 则把满足地那一个最前面的符号去

)

Trim

和where 元素等价。

//prefixOverrides=”AND”  
去除多余sql前面的AND
//suffixOverrides=”,”  
去除sql语句结尾多余的逗号.
//prefix="("   前缀的内容
//suffix=")"  后缀的内容
<trim prefix="WHERE" 
             prefixOverrides="AND">
   <if test="state != null">
     AND state = #{state}
   </if>
   <if test="title != null">
     AND title like #{title}
   </if>
   <if test="author != null and 
              author.name != null">
     AND author_name like #{author.name}
   </if>
</trim>  

Set

MyBatisHibernate的区别是什么?

hibernate:是一个标准ORM框架(对象关系映射)。入门门槛较高的,不需要程序写sql,sql语句自动生成了。对sql语句进行优化、修改比较困难的。全自动

 

mybatis:专注是sql本身需要程序员自己编写sql语句,sql修改、优化比较方便。mybatis是一个不完全的ORM框架,虽然程序员自己写sql,mybatis 也可以实现映射(输入映射、输出映射)。半自动

MyBatis如何实现模糊查询?

where bar like "%"#{value}"%"

where `name` like concat('%',#{pointRuleListDto.name}, '%')                            

Mybatis#{}${}的区别是什么?

https://blog.51cto.com/11140372/2363585

#{}表示一个占位符号,#{}接收输入参数,有效防止sql注入。会自动对传入的数据加一个双引号

${}表示一个拼接符号会引用sql注入

${param}传递的参数会被当成sql语句中的一部分,比如传递表名,字段名。

例子:(传入值为id)

order by ${param}

则解析成的sql为:

order by id

例子:(传入值为id)

会对自动传入的数据加一个双引号。

select * from table where name =#{param}

select * from table where name = "id"

某个网站的登录验证的SQL查询代码为:

strSQL ="SELECT * FROM users WHERE (name = ' " + userName + " ') and (pw = ' "+ passWord +" ');"

恶意填入

userName = "1' OR'1'='1"

与passWord = "1' OR'1'='1"

SELECT *FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1');

相当于SELECT * FROM users;在后台帐号验证的时候巧妙地绕过了检验,达到无账号密码,亦可登录网站。

Mybatis有几种分页方式?

分页方式:逻辑分页和物理分页。

逻辑分页: 使用MyBatis 自带的 RowBounds进行分页,

它是一次性查询很多数据,然后在数据中再进行检索。

物理分页: 自己手写 SQL 分页或使用分页插件 PageHelp

er,去数据库查询指定条数的分页数据的形式。

1.数组分页 subList(a,b)

先查询出全部数据,再在list中截取需要的部分。

java中list中的subList(a,b)[a,b]左闭右开

接口

List<Student>queryStudentsByArray(int currPage, 

                                                        int pageSize);

实现接口       currPage当前页数 pageSize每页有几条

 @Override

   public List<Student> queryStudentsByArray(int currPage, intpageSize) {

       //查询全部数据

       List<Student> students = 

             studentMapper.queryStudentsByArray();

       //从第几条数据开始

       int firstIndex = (currPage - 1) * pageSize;

       //到第几条数据结束

       int lastIndex = currPage * pageSize;

       //直接在list中截取

       return students.subList(firstIndex,lastIndex); 

}

2.sql分页

select* from 表名 limt a,b;

a为查询结果的索引值(默认从0开始)

b为查询结果返回的数量

limit(0,2)从起始行开始查2条

limit(1)返回1条 从1开始

 

接口

List<Student> queryStudentsBySql(intcurrPage, int pageSize);

实现类

public List<Student>queryStudentsBySql(int currPage, 

                                                              int pageSize) {

       Map<String, Object> data = new HashedMap();

       data.put("currIndex",(currPage-1)*pageSize);

       data.put("pageSize", pageSize);

       return studentMapper.queryStudentsBySql(data);

}

Xml文件

select * from student limit #{currIndex} ,#{pageSize}

3.interceptor拦截器分页

相当于sql分页,只不过在Service中取消了计算当前页。

Ps:都交给Pager这个类去实现就好。

在拦截器中有过设置了,xml里也仅仅查询所有的就好了

不需要#{page},#{size},拦截器会自动给加上。

链接参考

https://blog.csdn.net/feinifi/article/details/88769101

需要定义一个类实现Interceptor接口

/**
 * 利用MyBatis拦截器进行分页
 * @Intercepts 说明是一个拦截器
 * @Signature 拦截器的签名
 * type 拦截的类型 四大对象之一
 ( Executor,
 ResultSetHandler,
 ParameterHandler,
 StatementHandler)
 * method 拦截的方法
 * args 参数,高版本需要加个
 Integer.class参数,不然会报错
 */
@Intercepts({@Signature(type = 
StatementHandler.class, 
method = "prepare", args = {
Connection.class, Integer.class})})


public class MyPageInterceptor 
implements Interceptor {
  
  private int page;
  private int size;
  @SuppressWarnings("unused")
  private String dbType;
 
  @SuppressWarnings("unchecked")
  @Override
  //进行拦截的时候要执行的方法
  public Object intercept(Invocation invocation) throws Throwable {
    System.out.println("plugin is running...");
    StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
    MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
    while(metaObject.hasGetter("h")){
      Object object = metaObject.getValue("h");
      metaObject = SystemMetaObject.forObject(object);
    }
    while(metaObject.hasGetter("target")){
      Object object = metaObject.getValue("target");
      metaObject = SystemMetaObject.forObject(object);
    }
    MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
    String mapId = mappedStatement.getId();
    if(mapId.matches(".+ByPager$")){
      ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
      Map<String, Object> params = (Map<String, Object>)parameterHandler.getParameterObject();
      page = (int)params.get("page");
      size = (int)params.get("size");
      String sql = (String) metaObject.getValue("delegate.boundSql.sql");
      sql += " limit "+(page-1)*size +","+size;
      metaObject.setValue("delegate.boundSql.sql", sql);
    }
    return invocation.proceed();
  }
 
  //生成Object对象的动态代理对象
  @Override
  //插件用于封装目标对象的
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
 
  @Override
  //可以配置自定义相关属性,即:接口实现对象的参数配置;
  public void setProperties(Properties properties) {
    String limit = properties.getProperty("limit","10");
    this.page = Integer.parseInt(limit);
    this.dbType = properties.getProperty("dbType", "mysql");
  }
 
}

我们之前在service的findByPager方法里面,为了给limit

传入两个参数,其中page做了计算在拦截器里,这里使用

拦截器的方式就无需计算了:

实际上是以一种拦截器的方式在程序执行findByPager方法

的时候对语句会增加limitpage,size的拼接,还是和第二种

sql分页实现思路一样,所以这里需要对UserMapper.xml配置

文件中的findByPager这个查询对应的语句中的limit #{page},

#{size}这部分去掉,变为如下的样子:

4.PageHelper代替拦截器

引入maven依赖
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>4.2.1</version>
 </dependency>

spring.xml配置文件做一下修改:

<bean id="pageInterceptor" class="com.github.pagehelper.PageHelper">
  <property name="properties">
    <props>
      <prop key="helperDialect">mysql</prop>
      <prop key="reasonable">true</prop>
      <prop key="supportMethodsArguments">true</prop>
      <prop key="params">count=countSql</prop>
    </props>
  </property>
 </bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="mapperLocations"  value="classpath:com/xxx/mybatis/dao/*Mapper.xml"/>
  <property name="plugins" ref="pageInterceptor"></property>
</bean>

service层的方法,做一些修改:

public Pager<User> findByPager(int page,int size){
  Pager<User> pager = new Pager<User>();
  Page<User> res = PageHelper.startPage(page,size);
  userDao.findAll();
  pager.setRows(res.getResult());
  pager.setTotal(res.getTotal());
  return pager;
}

其实PageHelper方法也是使用Interceptor拦截器方式的一种

三方实现,它内部帮助我们实现了Interceptor的功能。所以

我们不用自定义MyPageInterceptor这个类实际上也是

在运行查询方法的时候,进行拦截,然后设置分页参数。

以PageHelper.startPage(page,size)这一句需要显示调用,

然后再执行userDao.findAll(),在查询所有用户信息的时候,

会进行一个分页参数设置,让返回的结果只是分页的结果,

而不是全部集合。PageHelper:在调用查询方法之前调用。

PageHelper只对紧跟着的第一个SQL语句起作用

5.RowBounds

在mapper.java中的方法中传入RowBounds对象。

不需要在 sql 语句中写 limit,mybatis 会自动拼接sql ,

添加 limit。

定义接口

List<Book>  selectBookByName(Map<String, Object> 

map, RowBounds rowBounds);


mappep.xml里面正常配置,不用对rowBounds任何操作。

mybatis的拦截器自动操作rowBounds进行分页。

RowBounds是一次性查询全部结果吗?为什么?

RowBounds 表面是在“所有”数据中检索数据,其实并非

是一次性查询出所有数据,因为 MyBatis 是对 jdbc 的封

装,在 jdbc 驱动中有一个 Fetch Size 的配置,它规定了

每次最多从数据库查询多少条数据,假如你要查询更多数

据,它会在你执行 next()的时候,去查询更多的数据

Mybatis逻辑分页和物理分页的区别是什么?

逻辑分页是一次性查询很多数据,然后再在结果中检索分页的数据。这样做弊端是需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。

物理分页是从数据库查询指定条数的数据,弥补了一次性全部查出的所有数据的种种缺点。

Mybatis是否支持延迟加载?延迟加载的原理是什么?

MyBatis 支持延迟加载,设置 lazyLoadingEnabled=true即可。

延迟加载的原理是调用的时候触发加载而不是在初始化的时候就加载信

比如调用 a. getB(). getName(),这个时候发现 a. getB() 的值为 null,此时会单独触发事先保存好的关联 B 对象的 SQL,先查询出来B,然后再调用 a. setB(b),而这时候再调用 a.getB(). getName() 就有值了,这就是延迟加载的基本原理。

说一下Mybatis的一级缓存和二级缓存?

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。

要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行。<cache/>

有哪些执行器(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批处理相同。会重用预处理语句,并执行批量更新。

Mybatis分页插件的实现原理是什么?

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

Mybatis如何编写一个自定义插件?

MyBatis 自定义插件针对 MyBatis 四大对象(Executor、

StatementHandler、ParameterHandler、

ResultSetHandler)进行拦截:

o  Executor:拦截内部执行器,它负责调用 State

mentHandler 操作数据库,并把结果集通过 Resu

ltSetHandler 进行自动映射,另外它还处理了二级

缓存的操作;

o  StatementHandler:拦截 SQL 语法构建的处理,

它是 MyBatis 直接和数据库执行 SQL 脚本的对象,

另外它也实现了 MyBatis 的一级缓存;

o  ParameterHandler:拦截参数的处理;

o  ResultSetHandler:拦截结果集的处理。

MyBatis 插件要实现Interceptor接口,接口包含的方法,如下。

public interface Interceptor {   
  //要进行拦截的时候要执行的方法。
Object intercept(Invocation invocation) throws Throwable;    
   
 //封装目标对象
Object plugin(Object target);   
 
 //配置自定义相关属性(接口实现对象的参数配置)
void setProperties(Properties properties);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值