Mybatis知识点(面试题)整理

it2025-02-10  9

如题,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执行。

<setting name="localCacheScope" value="STATEMENT"/>

 二级缓存是在多个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);

结果:

 

 

最新回复(0)