美文网首页
MyBatis基础面试题

MyBatis基础面试题

作者: 执着的逗比 | 来源:发表于2020-04-07 22:43 被阅读0次

1、Mybatis 中 #{}和 ${}的区别是什么?

1)#{}是预编译处理,${}是字符串替换;
2)#{}将传入的参数都当成一个字符串,会将sql中的#{}替换为?,然后调用PreparedStatement的set方法来赋值,而${}会直接替换参数;
3)#{}可以预防sql注入,${}不能防止sql注入;
4)${}方式一般用于传入数据库对象,例如传入表名;
5)一般能用#{}的就别用${}。

2、RowBounds 是一次性查询全部结果吗?为什么?

RowBounds 表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据,因为 MyBatis 是对 JDBC 的封装,在 jdbc 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询出多少条数据,假如你要查询更多数据,它会在你执行 next()的时候,去查询更多的数据。就好比你去自动取款机取 10000 元,但取款机每次最多能取 2500 元,所以你要取 4 次才能把钱取完。只是对于 JDBC 来说,当你调用 next()的时候会自动帮你完成查询工作。这样做的好处可以有效的防止内存溢出。

3、Mybatis 逻辑分页和物理分页的区别是什么?

1)逻辑分页是一次性查询很多数据,然后再在结果中检索分页的数据。这样做的弊端是需要消耗大量的内存,有内存溢出的风险,然后对数据库的压力较大。

2)物理分页是从数据库查询指定条数的数据,弥补了一次性全部查出所有数据的种种缺点:比如需要大量的内存、对数据库查询压力较大等问题。

4、说一下 Mybatis 的一级缓存和二级缓存?

1)一级缓存:基于 PerpetualCache 的 HashMap 本地缓存,它的声明周期是和 SQLSession 一致的,有多个 SQLSession 或者分布式的环境中的数据库操作,可能会出现脏数据。当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认一级缓存是开启的。

2)二级缓存:也是基于 PerpetualCache 的 HashMap 本地缓存,不同在于其存储作用域为 Mapper 级别的,如果多个SQLSession之间需要共享缓存,则需要使用到二级缓存,并且二级缓存可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态)。

开启二级缓存后的数据查询流程:二级缓存 → 一级缓存 → 数据库。
缓存更新机制:当某一个作用域(一级缓存 Session或者二级缓存 Mapper)进行了C/U/D (不知道CURD是什么含义的小伙伴请自行去面壁)操作后,默认该作用域下所有 select 中的缓存将被清除。

5、mybatis 是否支持延迟加载?延迟加载的原理是什么?

MyBatis 支持延迟加载,设置 lazyLoadingEnabled=true 即可。

延迟加载的原理的是调用的时候触发加载,而不是在初始化的时候就加载信息。比如调用 a.getB().getName(),这个时候发现 a.getB() 的值为 null,此时会单独触发事先保存好的关联 B 对象的 SQL,先查询出来 B,然后再调用 a.setB(b),而这时候再调用 a.getB().getName() 就有值了,这就是延迟加载的基本原理。

6、mybatis 和 hibernate 的区别有哪些?

1)hibernate是全自动,而mybatis是半自动
hibernate属于全自动ORM映射工具,使用hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。

2)hibernate数据库移植性远大于mybatis
hibernate通过它强大的映射结构和hql语言,大大降低了对象与数据库(Oracle、MySQL等)的耦合性,而mybatis由于需要手写sql,因此与数据库的耦合性直接取决于程序员写sql的方法,如果sql不具通用性而用了很多某数据库特性的sql语句的话,移植性也会随之降低很多,成本很高。

3)hibernate拥有完整的日志系统,mybatis则欠缺一些
hibernate日志系统非常健全,涉及广泛,包括:sql记录、关系异常、优化警告、缓存提示、脏数据警告等;而mybatis则除了基本记录功能外,功能薄弱很多。

4)mybatis相比hibernate需要关心很多细节
hibernate配置要比mybatis复杂的多,学习成本也比mybatis高。但也正因为mybatis使用简单,才导致它要比hibernate关心很多技术细节。mybatis由于不用考虑很多细节,开发模式上与传统jdbc区别很小,因此很容易上手并开发项目,但忽略细节会导致项目前期bug较多,因而开发出相对稳定的软件很慢,而开发出软件却很快。hibernate则正好与之相反。但是如果使用hibernate很熟练的话,实际上开发效率丝毫不差于甚至超越mybatis。

5)sql直接优化上,mybatis要比hibernate方便很多
由于mybatis的sql都是写在xml里,因此优化sql比hibernate方便很多。而hibernate的sql很多都是自动生成的,无法直接维护sql;虽有hql,但功能还是不及sql强大,见到报表等变态需求时,hql也歇菜,也就是说hql是有局限的;hibernate虽然也支持原生sql,但开发模式上却与orm不同,需要转换思维,因此使用上不是非常方便。总之写sql的灵活度上hibernate不及mybatis。

6)缓存机制上,hibernate要比mybatis更好一些
MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。而Hibernate对查询对象有着良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。

7、Mybatis 分页插件的实现原理是什么?

Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql语句,然后重写sql语句,根据dialect方言,添加对应的物理分页语句和物理分页参数。

举例:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10。

8、mybatis 如何编写一个自定义插件?

Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。

具体实现:实现Mybatis的Interceptor接口并重写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。

9、Mybatis 有几种分页方式?

1)数组分页

查询出全部数据,然后再list中截取需要的部分。

mybatis接口

List<Student> queryStudentsByArray();

xml配置文件

 <select id="queryStudentsByArray"  resultMap="studentmapper">

        select * from student

 </select>

service

List<Student> queryStudentsByArray(int currPage, int pageSize);

serviceImpl

 @Override

    public List<Student> queryStudentsByArray(int currPage, int pageSize) {

        //查询全部数据

        List<Student> students = studentMapper.queryStudentsByArray();

        //从第几条数据开始

        int firstIndex = (currPage - 1) * pageSize;

        //到第几条数据结束

        int lastIndex = currPage * pageSize;

        return students.subList(firstIndex, lastIndex); //直接在list中截取

    }

controller

 @ResponseBody
 @RequestMapping("/student/array/{currPage}/{pageSize}")

    public List<Student> getStudentByArray(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize) {

        List<Student> student = StuServiceIml.queryStudentsByArray(currPage, pageSize);

        return student;

    }

2)sql分页

mybatis接口

List<Student> queryStudentsBySql(Map<String,Object> data);

xml文件

<select id="queryStudentsBySql" parameterType="map" resultMap="studentmapper">

        select * from student limit #{currIndex} , #{pageSize}</select>

service

List<Student> queryStudentsBySql(int currPage, int pageSize);

serviceImpl

public List<Student> queryStudentsBySql(int currPage, int pageSize) {

        Map<String, Object> data = new HashedMap();

        data.put("currIndex", (currPage-1)*pageSize);

        data.put("pageSize", pageSize);

        return studentMapper.queryStudentsBySql(data);

    }

3)拦截器分页

创建拦截器,拦截mybatis接口方法id以ByPage结束的语句

package com.autumn.interceptor;

import org.apache.ibatis.executor.Executor;import org.apache.ibatis.executor.parameter.ParameterHandler;import org.apache.ibatis.executor.resultset.ResultSetHandler;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.plugin.*;import org.apache.ibatis.reflection.MetaObject;import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;import java.util.Map;import java.util.Properties;

/**

 * @Intercepts 说明是一个拦截器

 * @Signature 拦截器的签名

 * type 拦截的类型 四大对象之一( Executor,ResultSetHandler,ParameterHandler,StatementHandler)

 * method 拦截的方法

 * args 参数,高版本需要加个Integer.class参数,不然会报错

 */

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})public class MyPageInterceptor implements Interceptor {

    //每页显示的条目数

    private int pageSize;

    //当前现实的页数

    private int currPage;

    //数据库类型

    private String dbType;

    @Override

    public Object intercept(Invocation invocation) throws Throwable {

        //获取StatementHandler,默认是RoutingStatementHandler

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

        //获取statementHandler包装类

        MetaObject MetaObjectHandler = SystemMetaObject.forObject(statementHandler);

        //分离代理对象链

        while (MetaObjectHandler.hasGetter("h")) {

            Object obj = MetaObjectHandler.getValue("h");

            MetaObjectHandler = SystemMetaObject.forObject(obj);

        }

        while (MetaObjectHandler.hasGetter("target")) {

            Object obj = MetaObjectHandler.getValue("target");

            MetaObjectHandler = SystemMetaObject.forObject(obj);

        }

        //获取连接对象

        //Connection connection = (Connection) invocation.getArgs()[0];

        //object.getValue("delegate");  获取StatementHandler的实现类

        //获取查询接口映射的相关信息

        MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue("delegate.mappedStatement");

        String mapId = mappedStatement.getId();

        //statementHandler.getBoundSql().getParameterObject();

        //拦截以.ByPage结尾的请求,分页功能的统一实现

        if (mapId.matches(".+ByPage$")) {

            //获取进行数据库操作时管理参数的handler

            ParameterHandler parameterHandler = (ParameterHandler) MetaObjectHandler.getValue("delegate.parameterHandler");

            //获取请求时的参数

            Map<String, Object> paraObject = (Map<String, Object>) parameterHandler.getParameterObject();

            //也可以这样获取

            //paraObject = (Map<String, Object>) statementHandler.getBoundSql().getParameterObject();

            //参数名称和在service中设置到map中的名称一致

            currPage = (int) paraObject.get("currPage");

            pageSize = (int) paraObject.get("pageSize");

            String sql = (String) MetaObjectHandler.getValue("delegate.boundSql.sql");

            //也可以通过statementHandler直接获取

            //sql = statementHandler.getBoundSql().getSql();

            //构建分页功能的sql语句            String limitSql;

            sql = sql.trim();

            limitSql = sql + " limit " + (currPage - 1) * pageSize + "," + pageSize;

            //将构建完成的分页sql语句赋值个体'delegate.boundSql.sql',偷天换日

            MetaObjectHandler.setValue("delegate.boundSql.sql", limitSql);

        }

        //调用原对象的方法,进入责任链的下一级

        return invocation.proceed();

    }

    //获取代理对象    @Override

    public Object plugin(Object o) {

        //生成object对象的动态代理对象

        return Plugin.wrap(o, this);

    }

    //设置代理对象的参数    @Override

    public void setProperties(Properties properties) {

        //如果项目中分页的pageSize是统一的,也可以在这里统一配置和获取,这样就不用每次请求都传递pageSize参数了。参数是在配置拦截器时配置的。

        String limit1 = properties.getProperty("limit", "10");

        this.pageSize = Integer.valueOf(limit1);

        this.dbType = properties.getProperty("dbType", "mysql");

    }

}

配置文件SqlMapConfig.xml

<configuration>

    <plugins>

        <plugin interceptor="com.autumn.interceptor.MyPageInterceptor">

            <property name="limit" value="10"/>

            <property name="dbType" value="mysql"/>

        </plugin>

    </plugins>

</configuration>

mybatis配置

接口

List<AccountExt> getAllBookByPage(@Param("currPage")Integer pageNo,@Param("pageSize")Integer pageSize);

xml配置文件

  <sql id="getAllBooksql" >
    acc.id, acc.cateCode, cate_name, user_id,u.name as user_name, money, remark, time
  </sql>

  <select id="getAllBook" resultType="com.autumn.pojo.AccountExt" >

    select

    <include refid="getAllBooksql" />

    from account as acc

  </select>

service

    public List<AccountExt> getAllBookByPage(String pageNo,String pageSize) {

        return accountMapper.getAllBookByPage(Integer.parseInt(pageNo),Integer.parseInt(pageSize));

    }

controller

@RequestMapping("/getAllBook")

@ResponseBody

    public Page getAllBook(String pageNo,String pageSize,HttpServletRequest request,HttpServletResponse response){

        pageNo=pageNo==null?"1":pageNo;   //当前页码

        pageSize=pageSize==null?"5":pageSize;   //页面大小

        //获取当前页数据

        List<AccountExt> list = bookService.getAllBookByPage(pageNo,pageSize);

        //获取总数据大小

        int totals = bookService.getAllBook();

        //封装返回结果

        Page page = new Page();

        page.setTotal(totals+"");

        page.setRows(list);

        return page;

    }

Page实体类

package com.autumn.pojo;

import java.util.List;

/**

 * Created by Autumn on 2018/6/21.

 */public class Page {

    private String pageNo = null;

    private String pageSize = null;

    private String total = null;

    private List rows = null;

    public String getTotal() {

        return total;

    }

    public void setTotal(String total) {

        this.total = total;

    }

    public List getRows() {

        return rows;

    }

    public void setRows(List rows) {

        this.rows = rows;

    }

    public String getPageNo() {

        return pageNo;

    }

    public void setPageNo(String pageNo) {

        this.pageNo = pageNo;

    }

    public String getPageSize() {

        return pageSize;

    }

    public void setPageSize(String pageSize) {

        this.pageSize = pageSize;

    }

}

4)RowBounds分页

数据量小时,RowBounds不失为一种好办法。但是数据量大时,实现拦截器就很有必要了。

mybatis接口加入RowBounds参数

public List<UserBean> queryUsersByPage(String userName, RowBounds rowBounds);

service

    @Override

    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.SUPPORTS)

    public List<RoleBean> queryRolesByPage(String roleName, int start, int limit) {

        return roleDao.queryRolesByPage(roleName, new RowBounds(start, limit));

    }

未完待续

相关文章

网友评论

      本文标题:MyBatis基础面试题

      本文链接:https://www.haomeiwen.com/subject/gubophtx.html