写在前面:鸽了两天的面试题终于出来了,mybatis的面试题也不少,考研名师唐迟曾说过:学习的进步不再于你学了多少,而在于你做了多少的总结。
最后再次感谢ThinkWon大神的总结。
原文链接:https://blog.csdn.net/ThinkWon/article/details/101292950
文章目录
- 1. MyBatis简介
- 1.1 MyBatis是什么?
- 1.2 ORM是什么
- 1.3 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
- 1.4 传统JDBC开发存在的问题
- 1.5 JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?
- 1.6 Mybatis优缺点
- 1.7 MyBatis框架适用场景
- Hibernate 和 MyBatis 的区别
- 2. MyBatis的解析和运行原理
- 2.1 MyBatis编程步骤是什么样的?
- 2.2 请说说MyBatis的工作原理
- 2.3 MyBatis的功能架构是怎样的
- 2.4 MyBatis的框架架构设计是怎么样的
- 2.5 为什么需要预编译
- 2.6 Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
- 3. 映射器
- 3.1 #{}和${}的区别
- 3.2 模糊查询like语句该怎么写
- 3.3 在mapper中如何传递多个参数
- 3.4 Mybatis如何执行批量操作
- 3.5 如何获取生成的主键
- 3.6 当实体类中的属性名和表中的字段名不一样 ,Mybatis是如何将sql执行结果封装为目标对象并返回的?
- 3.7 Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
- 3.8 什么是MyBatis的接口绑定?有哪些实现方式?
- 3.9 使用MyBatis的mapper接口调用时有哪些要求?
- 3.10 Mapper 编写有哪几种方式?
- 3.11 Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
- 3.12 Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?
- 4. 高级查询
- 4.1 MyBatis实现一对一,一对多有几种方式,怎么操作的?
- 5. 动态SQL
- 5.1 Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?
- 6. 缓存
- 6.1 简述以下Mybatis的一级、二级缓存
MyBatis是一款优秀的持久层框架,使用Java编写,它封装了JDBC的很多细节,使开发者能够只关注于SQL语句本身,无需关注注册驱动,创建连接等繁琐功能,它使用了ORM的思想实现了结果集的封装。
Object Relational Mapping对象关系映射,是一种解决关系型数据库数据和Java对象(POJO)的映射关系。简单的说就是实体类对象和数据库数据的各种对应关系,如对象的属性字段对应数据库表的字段名称。
因为MyBatis查询关联对象或关联集合时,需要手动来编写SQL,所以时半自动化ORM映射工具。
而Hibernate框架是属于全自动化的ORM映射关系,查询关联对象或关联集合时不需要手写SQL,可以根据对象模型直接获取,所以是属于全自动的。
针对以上的传统JDBC开发存在的问题,MyBatis开发出了解决方案。
优点
1. 基于SQL语句的编程,相当的灵活,不会对应用程序或者数据库现有设计造成任何影响,SQL语句编写在XML中,解除SQL与程序代码的耦合,提供XML标签,便于统一管理,支持编写动态SQL,支持可重用。
2. 与传统的JDBC相比,消除了大量的冗余代码,不需要手动开关数据库连接。
3. 很好的可以与各种数据库兼容MySQL,Oracle..
4. 提供映射标签,支持对象与数据库的ORM字段关系映射,提供对象关系映射标签,支持对象关系组件的维护。
5. 能够和spring很好的集成。
缺点
1.SQL语句编写工作量很大,尤其在与字段多,管理表多的情况下,需要开发人员具备一定的SQL功底。
2.SQL语句依赖于数据库,导致数据库移植性差,不能随意换数据库。
相同点
都是对JDBC的封装,都是持久层框架,都用于dao层的开发
不同点
1.MyBatis是一个半自动化的ORM映射框架,配置Java对象和SQL语句的执行结果对应关系,多表关联配置简单。
2.Hibernate 是一个全自动化的ORM映射框架,配置Java对象与数据库表的对应关系,多表关联配置复杂
1.Hibernate 提供数据库的无关性,即可以切换任意的数据库,即移植性好,但是消耗性能,如果项目需要支持多种数据库,代码开发量少,但是SQL的优化却很复杂。
2. MyBatis需要手动编写SQL,支持动态SQL,处理表生成表名等操作,代码量相对大一点,不支持数据库无关性,即移植性差,但是SQL优化容易。
1. Hibernate 属于重量级的框架,学习成本高,适用于需求相对稳定的中小型项目,如:办公系统。
2. MyBatis 属于轻量级框架,学习成本低,适用于需求变化频繁的大型互联网项目,如互联网大型电商系统。
小结
从上图可以发现,MyBatis的初始化从加载主配置文件开始,生成Configuration类
预编译定义:
SQL预编译是指数据库驱动在发送SQL语句和参数之前对SQL语句进行编译,这样在DBMS执行SQL时进不需要进行编译了。
为什么要预编译?
JDBC通过preparedStatement对象来抽取预编译的SQL语句,使用预编译可以优化SQL的执行。预编译之后的SQL语句可以直接执行,DBMS不需要再次编译。提高了执行SQL的性能。同时预编译对象可以重复使用,例如:把一个SQL预编译后产生的preparedStatement存储起来,下一次如果再次使用同一个SQL,可以直接使用这个缓存的PreparedStatement对象。再则就是防止SQL注入的问题,MyBatis默认情况下,将所有的SQL语句,进行预编译。
深入理解:https://blog.csdn.net/JAYU_37/article/details/105444736
延迟加载的基本原理:
使用CGLIB创建目标的代理对象,当调用方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器发现a.getB()是null
值,就会单独事先保存好的关联B对象的sql,把B查询上来,a.setB(b),之后b就有值了,接着完成a.getB().getName()方法的调用,这就是延迟加载的基本原理。
MyBatis是仅支持association关联对象和collection关联集合对象的延迟加载,association指的是一对一,collection指的是一对多的查询。开启MyBaits延迟加载需要在主配置文件中设置标签< lazyloadingEnabled = true | false >
参考:https://www.cnblogs.com/mww-NOTCOPY/p/11777187.html
写法一: ‘ %${param}%’ :可能会引起SQL注入问题,不推荐使用。
写法二:"%"#{parm}"%"
注意
:这种写法极易出错,因为%后面的引号必须是”,因为#{param}解析完之后会给自身套上一个‘’,例如:
SELECT * FROM USER WHERE NAME LIKE "%"'li'"%"; #可以查询到,查询的是li
SELECT * FROM USER WHERE NAME LIKE "%''li''%"; #不可以查询到任何结果,查询的是''li''
写法三:concat(’%’,#{param},’%’);使用concat函数,推荐使用。
写法四:使用bind标签
<select id="listUserLikeUsername" resultType="com.jourwon.pojo.User">
<bind name="pattern" value="'%' + username + '%'" />
select id,sex,age,username,password from person where username LIKE #{pattern}
</select>
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where
user_name= #{0} and dept_id=#{1};
</select>
#{}里面的数字代表传参的顺序,但是此方法并不建议使用,原因是sql语句表达不直观,一旦出错很难发现错误。
public User selectUser(@Param("userName")String name, @Param("deptId")int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where
user_name= #{username} and dept_id=#{deptId};
</select>
#{}里面的名称是注解@Param()对应value名称,使用于参数不多的情况下,此方法推荐使用。
3. Map传参法
public User selectUser(Map<String,Object>params);
<select id="selectUser" resultMap="UserResultMap" parameterType="java.util.Map">
select * from user
where
user_name= #{username} and dept_id=#{deptId};
</select>
#{}里面的名称对应的是Map的key值,此方法使用与传递多个参数的情况下。
4. JavaBean传参法
public User selectUser(User user);
<select id="selectUser" resultMap="UserResultMap" parameterType="com.jourwon.pojo.User">
select * from user
where
user_name= #{username} and dept_id=#{deptId};
</select>
#{}里面的名称传的是User类对应的属性名称扩展不易,代码可读性强,业务逻辑处理方便,推荐使用。
使用foreach标签
foreach主要用于构建in条件中,它可以在SQL语句中迭代一个集合,foreach的标签属性有item,index,collection,open,separator,close。
注意:使用foreach最关键也是最容易出错的地方应该就是在collection属性了,该属性必须是指定的,但是在不用情况下,该属性的值是不一样的。主要有三种情况:
具体用法:
<!-- int add(@Param("emps") List<Employee> emps)
MySQL下的批量保存,可以使用foreach遍历,mysql支持values(),(),...-->
<insert id="add" paramType="java.util.List">
insert into emp(ename,gender,email,did)values
<foreach collection="emps" item="emp" seperator=",">
(#{emp.ename},#{emp.gender},#{emp.email},#{emp.dept.id})
</foreach>
</insert>
对于主键自增长的MySQL
<insert id="insertUser" useGeneratedKeys="true" keyProperty="userId" >
insert into user(
user_name, user_password, create_time)
values(#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
</insert>
可以发现上述的parameterType 不写,因为idea的mybatis插件可以自动识别出来传入的参数类型,如果要想访问主键,那么parameterType的类型应当是Map或Java实体,获取主键值可以通过key或者getXXX方法
假设实体类的属性名:
private Integer uid;
private String uname;
private String ugrader;
数据库表user字段名:
int(10) id;
varchar(32) name;
varchar(32) grader;
方案一:
取别名,例如
<select id="getUser" paramterType="int" resultType="com.liuzeyu.User">
select id uid,name uname,ugrader where id = #{uid}
</select>
方案二:
resultMap标签
<resultMap id="userMap" type="com.liuzeyu.User">
<id property = "uid" column="id"/>
<result property = "uname" column = "name"/>
<result property = "ugrader" column="grander"/>
</resultMap>
<select id="getUser" paramterType="int" resultMap="userMap">
select id uid,name uname,ugrader where id = #{uid}
</select>
不同的接口映射文件中如果配置了namespace则id可以重复,否则不能。
原因:
原因就是namespace+id是作为Map<String, MappedStatement>的key使用的,
如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。
有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
所谓的接口绑定,就是在mybatis中定义任意接口,然后把接口的方法和SQL语句绑定,我们直接调用接口方法就可以了,比前的使用sqlSession提供的方法更加的灵活。
方法1:使用注解绑定,就是在接口上使用@Select,@Delete,@Update…里面编写SQL语句
方法二:使用xml来绑定,在这种情况下,xml的接口映射配置文件的namespace属性必须配置接口的全限定类名。
当SQL语句比较简单时使用注解绑定,反之使用xml绑定
总共有三种方案。
mapper.xml文件编写:
mapper.xml中的namespace必须添加mapper接口的全限定类名
mapper接口方法名必须和mapper.xml定义的执行sql语句id名称一致
如果mapper.xml和mapper接口的名称保持一致则不用在mybatis主配置文件进行配置mapper.xml的具体位置,如下
mappers>
<mapper resource="mapper.xml 文件的地址" />
<mapper resource="mapper.xml 文件的地址" />
</mappers>
定义mapper接口
注意mapper.xml的文件名必须和mapper接口名必须保持一致并且放在同一目录下。
配置mapper扫描
<bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="mapper接口权限地类名"/>
<property name="SqlSessionFactoryBeanName" value="sessionFactory"/>
</bean>
使用扫描器后可以从spring容器中获取mapper的实现类对象。
使用的是resultType
还有很多标签,例如:
<resultMap>:结果集映射
<parameterMap>:参数映射
<sql>:sql片段标签
<include>:引入sql片段标签
<selectKey>:为不自增的主键添加策略标签
.....
A标签引用了B标签,虽然B标签在A标签后面,但是还是可以被正确引用。
原因:
因为配置文件加载到A标签后,发现了引入的B标签内容还未被解析到,此时A标签就置为未解析状态
继续解析以下的标签,包含B标签,知道解析完B标签,待所有的标签都解析完之后,MyBatis就将
哪些未被解析的内容再次解析,所以A标签也可以被正常解析。
使用联合查询和嵌套查询
MyBatis动态SQL可以让我们在XML配置文件中,以标签形式编写动态SQL,完成逻辑判断和SQL拼接,MyBatis提供了9中动态sql标签,分别是
trim | where | set | foreach | if | choose | when | otherwise | bind
其执行原理是,使用OGNL从SQL参数对象中计算表达式的值,根据表达式的值动态拼接SQL,以此来完成动态SQL的功能。
一级缓存:
一级缓存SqlSession对象范围的缓存,当我们执行查询之后,查询结果会同时存入SqlSession为我们提供的一块区域中,该区域的结构是Map,当我们再次查询到同样的数据时mybatis会先从sqlSession中获取。当调用SqlSession增删改,commit(),close()时就会清空一级缓存。
二级缓存:
二级缓存的机制和一级相同,它是指Mybatis的SqlSessionFactory对象缓存,在同一个SqlSessionFactory创建的SqlSession对象内共享缓存。默认不开启二级缓存,若要开启,存储的类必须实现序列化Serializable接口,作用是用来保存对象状态,并且在映射配置文件中开启< cache/>
注意:缓存默认更新机制,当某一作用域(SqlSession/SqlSessionFactory)进行了G/U/D(增删改)后,默认作用域下的select中的缓存将被清空。
写在最后:
3.10处遇到一个问题:如果mapper.xml和mapper接口的名称保持一致则不用在mybatis主配置文件进行配置mapper.xml的具体位置,但是在我项目中如果不配置于数据库交互则会报错?所以3.10的说法是否正确有待考察…
<mappers>
<mapper resource="mapper.xml 文件的地址" />
<mapper resource="mapper.xml 文件的地址" />
</mappers>