前言
接上一篇文章 MyBatis面试题总结-上 继续介绍 MyBatis常见面试题。
本文主要从面试的角度介绍 MyBatis 相关知识,希望能给大家带来帮助,同时也是为了自己学习的过程做总结。
一、什么是Mybatis的接口绑定?有哪些实现方式?
接口绑定就是在Mybatis中任意定义接口,然后把接口里面的方法和SQL语句绑定。
方式一:通过xml里面写SQL来绑定,就是在接口的方法上面加上@Select、@Update等注解,里面包含Sql语句来绑定。
方式二:通过xml里面写SQL来绑定。
二、使用MyBatis的mapper接口调用时有哪些要求?
- Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同;
- Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
- Mapper.xml文件中的namespace即是mapper接口的类路径。
三、MyBatis实现一对一,一对多有几种方式?
有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的association,collection节点配置一对一,一对多的类就可以完成
嵌套查询是先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,也是通过配置association,collection,但另外一个表的查询通过select节点配置。
四、Mybatis动态sql有什么用?执行原理?有哪些动态 sql?
Mybatis动态sq可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值完成逻辑判断并动态拼接sql的功能。
Mybatis提供了9种动态sql标签 :trim | where | set | foreach | if | choose| when | otherwise | bind。
五、有了解过MyBatis源码吗,请说说它的工作流程?
- 加载配置文件
- 生成SqlSessionFactory工厂
- 使用SqlSessionFactory工厂创建SqlSession对象
- 使用SqlSession创建Dao接口的代理对象
- 使用代理对象执行方法
- 关闭SqlSession,释放资源
六、Mybatis 是如何做到防止SQL注入的
- SQL注入,大家都不陌生,是一种常见的攻击方式。攻击者在界面的表单信息或URL上输入一些奇怪的SQL片段(例如“or ‘1’=’1’”这样的语句),有可能入侵参数检验不足的应用程序。所以,在我们的应用中需要做一些工作,来防备这样的攻击方式。在一些安全性要求很高的应用中(比如银行软件),经常使用将SQL语句全部替换为存储过程这样的方式,来防止SQL注入。这当然是一种很安全的方式,但我们平时开发中,可能不需要这种死板的方式。
- MyBatis框架作为一款半自动化的持久层框架,其SQL语句都要我们自己手动编写,这个时候当然需要防止SQL注入。其实,MyBatis的SQL是一个具有“输入+输出”的功能,类似于函数的结构,参考上面的两个例子。其中,parameterType 表示了输入的参数类型,resultType 表示了输出的参数类型。回应上文,如果我们想防止SQL注入,理所当然地要在输入参数上下功夫。上面代码中使用#的即输入参数在SQL中拼接的部分,传入参数后,打印出执行的SQL语句,会看到SQL是这样的:
select id, username, password from user where username=? and password=?;
- 不管输入什么参数,打印出的SQL都是这样的。这是因为MyBatis启用了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题。
底层实现原理
MyBatis是如何做到SQL预编译的呢?其实在框架底层,是JDBC中的PreparedStatement类在起作用,PreparedStatement是我们很熟悉的Statement的子类,它的对象包含了编译好的SQL语句。这种“准备好”的方式不仅能提高安全性,而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。
Mybatis是如何进行分页的?分页插件的原理是什么?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
select * from student,拦截sql后重写为:select t.* from (select * from student) t limit 0, 10;
MyBatis 的分页功能是基于内存的分页,即先查询出所有记录,再按起始位置和页面容量取出结果。
七、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection值得就是一对多查询。
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
八、MyBatis的一级缓存、二级缓存
缓存:缓存其实就是存储在内存中的临时数据,这里的数据量会比较小,一般来说,服务器的内存也是有限的,不可能将所有的数据都放到服务器的内存里面,所以, 只会把关键数据放到缓存中,缓存因为速度快,使用方便而出名。
MyBatis的缓存分为2类,分别是一级缓存和二级缓存。
一级缓存:一级缓存在没有配置的默认情况下是默认开启的,一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库(前提是多次查询中间没有执行 insert 、update或delete 语句,如果执行了这三种操作,那么MyBatis为了数据的一致性,将会缓存刷新,后面的查询操作会从数据库查询,并且将数据重新保存在缓存中)。
二级缓存:二级缓存是默认关闭的,它指的是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其二级缓存。即使开启了二级缓存,不同的sqlsession之间的缓存数据也不是想互访就能互访的,必须等到sqlsession关闭了以后,才会把其一级缓存中的数据写入二级缓存。二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。 也就是要求实现Serializable接口,配置方法很简单有两种方式,只需要在映射XML文件配置就可以开启缓存了。
方式一:单个mapper配置,在需要开启二级缓存的xxxMapper.xml文件中加入以下配置即可开启。
方式二:所有的mapper都开启二级缓存,在mybatis.xml中加入以下配置即可
当二级缓存开启后,同一个命名空间(namespace) 所有的操作语句,都影响着一个共同的 cache,也就是二级缓存被多个 SqlSession 共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
一级缓存与二级缓存的不同之处:
- Mybatis的一级缓存默认开启,而二级缓存默认关闭。
- Mybatis的一级缓存指的是Mybaits中SqlSession对象的缓存,而二级缓存指的是SqlSessionFactory对象的缓存。一个SqlSessionFactory对象包括多个SqlSession对象。
- SqlSession对象中存放的是返回数据的对象,而SqlSessionFactory对象中存放的是数据,不是对象。
- Mybatis和Spring整合的时候,一级缓存与事务有关,而二级缓存与事务无关。
总结
- MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
- MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
- 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。