2,Mybatis面试题
1.#{ }和${ }的区别是什么?
#{ }是预编译处理,${ }是字符串替换;
对于#{},Mybatis会对sql语句进行预处理,将sql中的#{}替换为?号,然后调用PreparedStatement的set方法来赋值。当变量是数字类型时,直接替换;当变量是字符串类型时,会加上双引号。
对于${},Mybatis就是把${ }直接替换成变量的值。
使用#{}可以有效的防止sql注入,提高系统安全性。
2.Mybatis的XML映射文件中,不同的XML映射文件,id是否可以重复?
不同的XML映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;
原因就是namespace+id是作为Map的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
备注:在旧版的Mybatis中,namespace是可选的,不过新版的namespace已经是必须的了。
Map<String,MappedStatement>
3.Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
- 第一种是使用
<resultMap>
标签,逐一定义列名和对象属性名之间的映射关系。 - 第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如
T_NAME AS NAME
,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe
,Mybatis一样可以正常工作。
有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
4.使用Mybatis的mapper接口调用时有那些要求?
Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同;
Mapper接口方法的输出参数类型和mapper.xml 中定义的每个sql的resultType的类型相同;
Mapper.xml文件中的namespace即是mapper接口的类路径。
5. Mybatis相比JDBC有哪些优点?
(1)数据库链接创建,释放频繁造成系统资源浪费会影响系统性能,使用数据库可以解决
解决:在核心配置文件SqlMapConfig.xml中配置数据链接池,使用数据链接池管理数据库链接
(2)Sql写在代码中不易于维护,修改需要变动java代码
在映射文件XXXMapper.xml文件中配置sql语句与Java代码分离
(3)向Sql语句传输参数麻烦,因为Sql语句的WHERE条件不一定,可能多也可能少,占位符需要和参数一一对应
Mybatis可以自动将Java对象映射到sql语句
(4)对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历。
将数据库记录封装成pojo对象解析更加方便,Mybatis可以自动将sql执行结果映射到Java对象
数据库连接创建、释放频繁,sql语句与java代码没有分离,向sql语句传输参数麻烦,对结果集解析麻烦(这两个都可以用java对象与结果集映射解决)
6.当实体类中的属性名和表中的字段名不一样,怎么办 ?
第1种解决方案:通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
<select id="getOrder" parametertype="int" resultetype="cn.mybatis.domain.order"> select order_id id, order_no orderNo ,order_price price form orders where order_id=#{id}; </select>
第2种解决方案:通过来映射字段名和实体类属性名的一一对应的关系。
<select id="getOrder" parameterType="int" resultMap="orderResultMap"> select * from orders where order_id=#{id} </select> <resultMap id="orderResultMap" type="cn.mybatis.domain.order" > <!–用id属性来映射主键字段–> <id property="id" column="order_id"> <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–> <result property= "orderNo" column="order_no"/> <result property="price" column="order_price"/> </reslutMap>
***7. 分别介绍一下JDBC核心对象与Mybatis核心对象?
JDBC有四个核心对象
(1)DriverManager,用于注册数据库连接
(2)Connection,与数据库连接对象
(3)Statement/PrepareStatement,操作数据库SQL语句的对象
(4)ResultSet,结果集或一张虚拟表
MyBatis也有四大核心对象
(1)SqlSession
对象,该对象中包含了执行SQL
语句的所有方法,类似于JDBC
里面的Connection
。
(2)Executor
接口,它将根据SqlSession
传递的参数动态地生成需要执行的SQL
语句,同时负责查询缓存的维护。类似于JDBC
里面的Statement/PrepareStatement
。
(3)MappedStatement
对象,该对象是对映射SQL的封装,用于存储要映射的SQL
语句的id、参数等信息。
(4)ResultHandler
对象,用于对返回的结果进行处理,最终得到自己想要的数据格式或类型。可以自定义返回类型
***8.Mybatis中Dao接口的工作原理是什么?
通常一个Xml
映射文件,都会写一个Dao
接口与之对应,请问,这个Dao
接口的工作原理是什么?Dao
接口里的方法,参数不同时,方法能重载吗?
Dao
接口即Mapper
接口。接口的全限名,就是映射文件中的namespace
的值;接口的方法名,就是映射文件中Mapper
的Statement
的id
值;接口方法内的参数,就是传递给sql
的参数。
Mapper
接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key
值,可唯一定位一个MapperStatement
。在Mybatis
中,每一个 <select>
、<insert>
、<update>
、<delete>
标签,都会被解析为一个MapperStatement
对象。
举例来说:cn.mybatis.mappers.StudentDao.findStudentById
,可以唯一找到namespace
为 com.mybatis.mappers.StudentDao
下面 id
为 findStudentById
的 MapperStatement。
Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
***9.模糊查询like语句该怎么写?
第1种:在Java代码中添加sql通配符。(java代码可能指的是Service中的java代码)
String wildcardname = “%smi%”; list<name> names = mapper.selectlike(wildcardname); <select id=”selectlike”> select * from foo where bar like #{value} </select>
第2种:在sql语句中拼接通配符,会引起sql注入
String wildcardname = “smi”; list<name> names = mapper.selectlike(wildcardname); <select id=”selectlike”> select * from foo where bar like "%"#{value}"%" </select>
***10.Mybatis是如何进行分页的?分页插件的原理是什么?
-
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
-
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
11. Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
第一种是使用标签,逐一定义数据库列名和对象属性名之间的映射关系。
第二种是使用sql列的别名功能,将列的别名书写为对象属性名。
有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
***12. Mybatis动态sql有什么用?执行原理?有哪些动态sql?
Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。
Mybatis提供了9种动态sql标签:trim | where | set | foreach | if | choose | when | otherwise | bind。
***13. Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
<resultMap>
、<parameterMap>
、<sql>
、<include>
、<selectKey>
,加上动态sql
的9个标签,其中<sql>
为sql
片段标签,通过<include>
标签引入sql
片段,<selectKey>
为不支持自增的主键生成策略标签。
***14.MyBatis底层实现原理?
MyBatis是一个持久层框架,实现了ORM思想,可以将查询的结果集自动转换成Java对象,也可以将Java对象转换成一条数据插入到数据库表当中。
那么,查询结果集是如何自动转换成Java
对象的呢?实际上这里使用了反射机制,在配置文件中假设编写了一条select
语句,查询之后,列名与属性名要一一对应(不对应的可以采用给列起别名),然后每个列名前添加“set”,通过反射机制获取set方法,然后再通过反射机制的method.invoke()
来调用这个set
方法,给Java
对象的属性赋值。这样就完成了对象的封装。
另外,Java
对象是如何转换成一条记录插入到数据库的呢?假设在配置文件中编写了一条insert语句,那么这条语句需要的值从哪里来呢,在mybatis
的mapper
配置中有parameterType
属性,该属性是专门给sql
语句占位符传值的,其实这里也是使用了反射机制,其中sql
语句的占位符采用#{}
,其中大括号当中需要提供java
对象的属性名,该属性名和get
进行拼接得到get
方法名,然后通过反射机制获取该get
方法,再通过method.invoke()
来调用这个get
方法,这样就可以获取到对应的属性值,然后传入了。
其实MyBatis设计最牛的地方当然是采用JDK动态代理的方式生成DAO接口的实现类了。其中DAO接口中的每一个方法名对应sql语句的id。DAO接口中的方法不允许重载,因为id是不允许重复的。
15.什么是ORM?
- ORM(Object/Relational Mapping)即对象关系映射,是一种数据持久化技术。它在对象模型和关系型数据库直接建立起对应关系,并且提供一种机制,通过JavaBean对象去操作数据库表的数据。 MyBatis通过简单的XML或者注解的方式进行配置和原始映射,将实体类和SQL语句之间建立映射关系,是一种半自动(之所以说是半自动,因为我们要自己写SQL)的ORM实现。
***16.在开发过程中,经常遇到插入重复的现象,这种情况该如何解决呢?
插入的过程一般都是分两步的:
先判断是否存在记录,没有存在则插入否则不插入。如果存在并发操作,那么同时进行了第一步,然后大家都发现没有记录,然后都插入了数据从而造成数据的重复。解决插入重复的思路可以是这样的:
(1)判断数据库是否有数据,有的话则无所作为。没有数据的话,则进行下面第2步
(2)向redis set key,其中只有一个操作a会成功,其他并发的操作b和c会失败的
(3)上面set key 成功的操作a,开始执行插入数据操作,无论是否插入数据成功,都在最后del key。【注】插入不成功可以多尝试几次,增加成功的概率。
(4)上面set key 失败的操作b和c,sleep一下,然后再判断数据库是否有数据,有数据则无所做为,没有数据则重复上面的set key,此时是b和c在竞争,失败者则无所作为,成功者则开始插入数据,然后无论插入成功还是失败则都要del key。【注】既然是并发了,本身就是异常情况,就没有必要考虑用户体验了,就可以多sleep一会儿也无妨,不过对于单线程多事件处理的开发模式不要sleep太久。
总之,上面的过程就是:线程a 线程b 线程c,同时插入数据。如果线程a拿到锁之后,让它插入数据,它插入成功了,那么线程b 线程c啥也不用做;它插入失败了,线程b 线程c则抢锁,谁抢到了谁插入数据,不管最后是否成功,程序走到此步就可以了,已经完成了既定两个目标:执行插入,不重复插入。
***17.模糊查询like语句该怎么写?
- 第1种:在Java代码中添加sql通配符。
- 第2种:在sql语句中拼接通配符,会引起sql注入
- 有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成; 嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。
19.MyBatis实现一对多有几种方式,怎么操作的?
- 有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的collection节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。
20.sql注入:
- SQL注入,大家都不陌生,是一种常见的攻击方式。攻击者在界面的表单信息或URL上输入一些奇怪的SQL片段(例如“or ‘1’=’1’”这样的语句),有可能入侵参数检验不足的应用程序。所以,在我们的应用中需要做一些工作,来防备这样的攻击方式。在一些安全性要求很高的应用中(比如银行软件),经常使用将SQL语句全部替换为存储过程这样的方式,来防止SQL注入。这当然是一种很安全的方式,但我们平时开发中,可能不需要这种死板的方式。
21.mybatis是如何做到防止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已编译好,再次执行时无需再编译。
结论:
#{}:相当于JDBC中的PreparedStatement
${}:是输出变量的值
- 简单说,#{}是经过预编译的,是安全的;${}是未经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入。
22.Mybatis的一级缓存和二级缓存
23.Mybatis的编程步骤是怎样的?
(1)创建sqlSessionFactory
(2)通过sqlSessionFactory创建sqlSession
(3)通过SQLSession执行数据库操作
(4)调用session.commit()提交事务
(5)调用session.close()关闭会话
***24.Mybatis逻辑分页和物理分页的区别是什么?
(1)物理分页速度并不一定快于逻辑分页;逻辑分页速度上也并不一定快于物理分页
(2)物理分页总是优于逻辑分页:没有必要将属于数据库端的压力加到应用端来,就算速度上存在优势,然而其他性能上的优点足以弥补这个缺点
25.Mybatis有几种分页方式?
(1)数组分页(内存分页/逻辑分页)
(2)SQL分页(物理分页)
(3)拦截器分页(物理分页)
(4)RowBounds分页(内存分页)
原理:通过RowBounds实现分页和通过数组方式分页原理差不多,都是一次获取所有符合条件的数据,然后在内存中对大数据进行操作,实现分页效果。只是数组分页需要我们自己去实现分页逻辑,这里更加简化而已。
存在问题:一次性从数据库获取的数据可能会很多,对内存的消耗很大,可能导致性能变差,甚至引发内存溢出。
适用场景:在数据量很大的情况下,建议还是适用拦截器实现分页效果。RowBounds建议在数据量相对较小的情况下使用。
26.Mybatis的一级缓存和二级缓存(都是用的HashMap)
(1)一级缓存
存储作用域:session;默认打开一级缓存
(2)二级缓存
存储作用域:namespace;默认关闭二级缓存