如题,MyBatis这里知识点我打算从经典面试题入手,然后整理这些面试题的答案。下面是我从网上搜集的一些面试题
什么是MyBatis
MyBatis是一款优秀的支持自定义SQL查询,存储过程和高级映射的持久层框架,笑出了几乎所有的JDBC代码和参数的手动设置以及结果集的检索。MyBatis可以使用XML或注解进行配置或映射,MyBatis通过将参数映射到配置的SQL形成最终执行的SQL语句,最后将执行的SQL的结果映射成Java对象返回。(摘自《MyBatis从入门到精通》)
MyBatis的#{}和{}的区别
#{}是需要预编译的,${}是之间将数值拼接到sql里面。#{}可以防止sql注入,提高系统安全性
MyBatis的pageHelper插件使用
pageHelper是一款好用的分页插件,可以自动对select结果进行分页查询,非复杂情况下不需要开发人员操心如何分页。这里主要介绍一下pageHelper在Spring Boot项目中常见用法
(1)引入maven依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>最新版本</version>
</dependency>
(2)pageHelper相关配置
#pagehelper
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql
(3)如何使用
使用的话,分好几种,这是从源代码的说明中摘抄的。但是我在项目中主要使用第二种方式
//第一种,RowBounds方式的调用
List<User> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));
//第二种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectIf(1);
//第三种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.offsetPage(1, 10);
List<User> list = userMapper.selectIf(1);
//第四种,参数方法调用
//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
List<User> selectByPageNumSize(
@Param("user") User user,
@Param("pageNum") int pageNum,
@Param("pageSize") int pageSize);
}
//配置supportMethodsArguments=true
//在代码中直接调用:
List<User> list = userMapper.selectByPageNumSize(user, 1, 10);
//第五种,参数对象
//如果 pageNum 和 pageSize 存在于 User 对象中,只要参数有值,也会被分页
//有如下 User 对象
public class User {
//其他fields
//下面两个参数名和 params 配置的名字一致
private Integer pageNum;
private Integer pageSize;
}
//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
List<User> selectByPageNumSize(User user);
}
//当 user 中的 pageNum!= null && pageSize!= null 时,会自动分页
List<User> list = userMapper.selectByPageNumSize(user);
//第六种,ISelect 接口方式
//jdk6,7用法,创建接口
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
@Override
public void doSelect() {
userMapper.selectGroupBy();
}
});
//jdk8 lambda用法
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(()-> userMapper.selectGroupBy());
//也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
@Override
public void doSelect() {
userMapper.selectGroupBy();
}
});
//对应的lambda用法
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> userMapper.selectGroupBy());
//count查询,返回一个查询语句的count数
long total = PageHelper.count(new ISelect() {
@Override
public void doSelect() {
userMapper.selectLike(user);
}
});
//lambda
total = PageHelper.count(()->userMapper.selectLike(user));
//分页时,实际返回的结果list类型是Page<E>,如果想取出分页信息,需要强制转换为Page<E>,
assertEquals(182, ((Page) list).getTotal());
//或者使用PageInfo接收
//获取第1页,10条内容,默认查询总数count
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectAll();
//用PageInfo对结果进行包装
PageInfo page = new PageInfo(list);
//测试PageInfo全部属性
//PageInfo包含了非常全面的分页属性
assertEquals(1, page.getPageNum());
assertEquals(10, page.getPageSize());
assertEquals(1, page.getStartRow());
assertEquals(10, page.getEndRow());
assertEquals(183, page.getTotal());
assertEquals(19, page.getPages());
assertEquals(1, page.getFirstPage());
assertEquals(8, page.getLastPage());
assertEquals(true, page.isFirstPage());
assertEquals(false, page.isLastPage());
assertEquals(false, page.isHasPreviousPage());
assertEquals(true, page.isHasNextPage());
注意:pageHelper的分页调用是对下一行代码做分页操作,所以必须紧挨着查询,并且在查询调用的前面。如果下一行代码并非查询操作,在pageHelper中创建的ThreadLocal属性无法回收,会出现内存泄漏的情况。
使用MyBatis的接口调用有哪些要求
(1)Mapper接口类的路径要和mapper.xml的namespace的mapper接口类的路径相同
(2)Mapper接口类命名方法id、参数名要和mapper.xml的方法参数相对应
(3)Mapper接口类返回值要和mapper.xml的返回值对应
MyBatis的xml对应的dao接口工作原理是什么?可以重载吗?
Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement.在Mybatis中,每一个<select>/<insert>/<update>/<delete>标签,都会被解析成一个MappedStatement.
Dao接口里面的方法不能重载。因为虽然重载的参数列表不一致,但是方法名是一样的,这样在mapper.xml中查找会冲突,因此不能重载。
MyBatis是否支持延迟加载
Mybatis支持延迟加载。
使用场景:一对一,多对一:立即加载;一对多,多对多:延迟加载。
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=tree|false.
它的原理是:使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送实现保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b树形就有值了,接着完成a.getB(),getName()方法的调用。这就是延迟加载的原理。不光是Mybatis,几乎所有的包括hibernate,支持延迟加载的原理。
Mybatis插件运行原理
Mybatis的插件利用JDK动态代理和责任链设计模式的总和运用。
Mybatis是对四大接口进行拦截,Mybatis的四大接口:
Executor(update,query,flushStatements,commit,rollback,getTransaction, close,isClosed)Mybatis的执行器,用于执行增删改查;
StatementHandle(getParameterObject,setParameters)处理SQL的参数对象;
ResultSetHandle(handleResultSets,HandleOutputParameters)处理SQL的返回结果集;
ParameterHander(prepare,parameterize,batch,update,query)拦截SQL的语法构建的处理.
Mybatis仅可以编写针对上面四种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4中接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。编写插件需要实现Mybatis的Interceptor接口并腹泻intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,还需要在配置文件中配置编写的插件。
MyBatis和hibernate的区别
(1)mybatis是半自动的ORM框架,hibernate是全自动的ORM框架。ORM是在关系型数据库和对象之间作一个映射。因此hibernate可以直接使用dao层的语句生成对数据库操作的sql,mybatis是需要在mapper.xml中编写sql。
(2)hibernate的数据库移植性好,mybatis则移植性低。hibernate是利用dao层的代码去生成对应的不同的数据库sql,因此可以适应不同种类的数据库语言。但是mybatis是需要在mapper.xml中编写sql,只能针对某种数据库语言,因此移植性步入hibernate。
(3)hibernate拥有完整的日志系统,mybatis则欠缺一些。hibernate日志系统非常健全,涉及广泛,包括:sql记录,关系异常,优化警告,缓存提示,脏数据警告等。mybatis则除了基本记录功能外,功能薄弱很多。
(4)sql优化上,mybatis比hibernate方便很多。由于mybatis的sql都是写在xml里,因此优化sql比hibernate方便很多。而hibernate的sql很多都是自动生成的,无法直接维护sql;所有hql,但是功能不如sql强大。写sql的灵活度上hibernate不如mybatis强大。
(5)缓存机制上,hibernate要比mybatis更好一些。Mybatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。而Hibernate对查询对象有着良好的管理机制,用户无需关心SQL.所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。
MyBatis的一级缓存和二级缓存
一级缓存是指sqlSession级别的缓存。一级缓存的作用域默认是一个SqlSession.Mybatis默认开启一级缓存。也就是在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中;第二次以后直接去缓存中取。当执行sql查询的过程中发生了增删改操作,Mybatis会将SqlSession的缓存清空。一级缓存的范围有SESSION和STATEMENT两种,默认是SESSION,如果不想使用一级缓存,可以把一级缓存的范围指定为STATEMENT,这样每次执行完一个Mapper中的语句后都会讲一级缓存清除。如果需要更改一级缓存的范围,可以在Mybatis的配置文件中,通过localCacheScope执行。
|
二级缓存是在多个SqlSession在同一个Mapper文件中共享的缓存,是Mapper级别的。
(1)流程:
当开启二级缓存后,在配置文件中配置<setting name="cacheEnabled" value="true"/>这行代码,Mybatis会为SqlSession对象生成Executor对象时,还会生成一个对象:CachingExecutor,我们称之为装饰者,这里用到了装饰器模式。那么CachingExecutor的作用是什么呢?就是当一个查询请求过来时,CachingExecutor会接到请求,先进行二级缓存的查询,如果没命中,就交给真正的Executor来查询,再到一级缓存中查询,如果还没命中,再到数据库中查询。然后把查询到的结果再返回CachingExecutor,它进行二级缓存,最后再返回给请求方。它是executor的装饰者,增强executor的功能,具有查询缓存的作用。当配置<setting name="cacheEnabled" value="false"/>时,请求过来时,BaseExecutor这个抽象类会接到请求,就不进行二级缓存的查询。
(2)开启:
(1)在配置文件mybatis-config.xml中配置二级缓存,默认是开启的
<setting name="cacheEnabled" value="true"/>
(2)在mapper文件中配置,开启当前文件的二级缓存
<!-- 每个Mapper文件使用一个缓存对象 -->
<cache/>
<!-- 如果是多个Mapper文件共用一个缓存对象 -->
<cache-ref />
(3)针对要查询的statement使用缓存,即在<select>节点中配置如下属性:
useCache="true"
(3)说明 (参考:https://www.cnblogs.com/51life/p/9529409.html)
- 映射语句文件中的所有 select 语句将会被缓存。
- 映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
- 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
- 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
- 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
- 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
- 二级缓存在被多个文件共同使用时可能会出现脏数据情况。
MyBatis执行批量插入,如何返回主键ID列表
这种批量插入返回主键ID的只适应于主键自增的情况:使用 useGeneratedKeys="true" keyProperty="主键属性"这两个属性配置插入sql,适用单条数据插入。
mapper.xml中sql:
<insert id="getIdsBySaveBatch" useGeneratedKeys="true" keyProperty="id">
insert into user(name,age)
values
<foreach collection="list" item="item" separator=",">
(#{item.name}, #{item.age})
</foreach>
</insert>
mapper接口
int getIdsBySaveBatch(List<User> users);
结果: