Java 最常见的 面试题 ➕ 答案 十三(Mybatis)

十三、Mybatis

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

  1. #{}是预编译处理,$ {}是字符串替换。
  2. MyBatis在处理#{}时,会将SQL中的#{}替换为?号,使用PreparedStatement的set方法来赋值;MyBatis在处理 $ { } 时,就是把 ${ } 替换成变量的值。
  3. 使用 #{} 可以有效的防止SQL注入,提高系统安全性。

2、mybatis 有几种分页方式?

  1. LIMIT关键字:
  2. interceptor plugin实现:需要定义一个类实现Interceptor接口
  3. PageHelper实现:这种方式实现需要我们引入maven依赖;(其实PageHelper方法是使用Interceptor拦截器方式的一种三方实现,它内部帮助我们实现了Interceptor的功能。我们不用自定义MyPageInterceptor)

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

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

4、mybatis 逻辑分页和物理分页的区别是什么?

  1. 逻辑分页是先查出来,然后利用代码去取需要的部分;物理分页是利用sql自带的limit去实现的,本身查询出来的数据是就算分页好的
  2. 逻辑分页 内存开销比较大,在数据量比较小的情况下效率比物理分页高;在数据量很大的情况下,内存开销过大,容易内存溢出,不建议使用
  3. 物理分页 内存开销比较小,在数据量比较小的情况下效率比逻辑分页还是低,在数据量很大的情况下,建议使用物理分页

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

Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。

原理:使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理。当然了,不光是 Mybatis,几乎所有的包括 Hibernate,支持延迟加载的原理都是一样的。

6、说一下 mybatis 的一级缓存和二级缓存?

  • Mybatis的一级缓存:是默认开启的,它只相对于同一个SqlSession有效,所以也称之为SqlSession缓存。当参数和SQL完全相同的情况下,使用同一个SqlSession对象调用同一个Mapper方法,当第1次执行SQL语句后,MyBatis会自动将其放在缓存中,后续再次查询时,如果没有声明需要刷新,且缓存没有超时,会直接取出此前缓存的数据,而不会再次发送SQL到数据库。
  • Mybatis的二级缓存:是默认未开启的,如果希望开启,需要在配置SQL的XML文件中配置<cache>节点,由于每个XML都通过根节点的namespace属性对应一个Mapper接口,所以,二级存储也称之为namespace缓存!在使用二级存储时,查询数据的<select>节点需要配置useCache="true",并且,查询返回的结果类型必须是实现了Serializable接口的!另外,当缓存 了数据后,如果执行了当前XML中配置的增、删、改操作,会自动刷新此前的缓存数据!
  • 关于二级缓存的特点还有许多,例如Mybatis使用了LRU算法来管理缓存的数据,但是,由于Mybatis的缓存在一定程度上是不可控的,所以,在实际应用中,一般并不使用Mybatis的缓存机制来实现数据缓存,而是使用自定义的缓存机制,或第3方缓存服务器,例如Redis、MemCache等!

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

相同点:

  • Hibernate与MyBatis都可以是通过SessionFactoryBuider由XML配置文件生成SessionFactory,然后由SessionFactory 生成Session,最后由Session来开启执行事务和SQL语句。
  • 其中SessionFactoryBuider,SessionFactory,Session的生命周期都是差不多的。Hibernate和MyBatis都支持JDBC和JTA事务处理。

不同点:

  1. hibernate是全自动,而mybatis是半自动:hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。而mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。
  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。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。

总结:

  1. 两者相同点:Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现你自己的缓存或为其他第三方缓存方案,创建适配器来完全覆盖缓存行为。
  2. 两者不同点:Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存。而MyBatis在使用二级缓存时需要特别小心。如果不能完全确定数据更新操作的波及范围,避免Cache的盲目使用。否则,脏数据的出现会给系统的正常运行带来很大的隐患。

8、mybatis 有哪些执行器(Executor)?

  1. SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
  2. ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。
  3. BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
  4. CachingExecutor:CachingExecutor是一个Executor接口的装饰器,它为Executor对象增加了二级缓存的相关功能,委托的执行器对象可以是SimpleExecutor、ReuseExecutor、BatchExecutor中任一一个。执行 update 方法前判断是否清空二级缓存;执行 query 方法前先在二级缓存中查询,命中失败再通过被代理类查询。

使用场景:Executor的这些特点,都严格限制在SqlSession生命周期范围内。Mybatis的默认执行器是SimpleExecutor,需要配置在创建SqlSession对象的时候指定执行器的类型即可。ReuseExecutor和BatchExecutor适用特定场景的sql执行使用,但是必须自己维护Statement对象,执行SqlSession的flushStatements来清除缓存不推荐使用。而CachingExecutor通常来说会配合redis等第三方储存来实现分布式二级缓存。

指定Executor执行器:在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。

9、mybatis 分页插件的实现原理是什么?

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

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

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

 

1、新建类实现 Interceptor 接口,并指定想要拦截的方法签名

/**
 * MyBatis 插件
 */
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class ExamplePlugin implements Interceptor {
 
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        for (Object arg : invocation.getArgs()) {
            System.out.println("参数:" + arg);
        }
        System.out.println("方法:" + invocation.getMethod());
        System.out.println("目标对象:" + invocation.getTarget());
        Object result = invocation.proceed();
 
        //只获取第一个数据
        if (result instanceof List){
            System.out.println("原集合数据:" + result);
            System.out.println("只获取第一个对象");
            List list = (List)result;
            return Arrays.asList(list.get(0));
        }
        return result;
    }
}

2、MyBatis 配置文件中添加该插件

<plugins>
    <plugin interceptor="constxiong.plugin.ExamplePlugin">
    </plugin>
</plugins>

3、测试代码

System.out.println("------userMapper.deleteUsers()------");
//删除 user
userMapper.deleteUsers();
 
System.out.println("------userMapper.insertUser()------");
//插入 user
for (int i = 1; i <= 5; i++) {
    userMapper.insertUser(new User(i, "ConstXiong" + i));
}
 
System.out.println("------userMapper.selectUsers()------");
//查询所有 user
List<User> users = userMapper.selectUsers();
System.out.println(users);

打印结果

------userMapper.deleteUsers()------
------userMapper.insertUser()------
------userMapper.selectUsers()------
参数:org.apache.ibatis.mapping.MappedStatement@58c1c010
参数:null
参数:org.apache.ibatis.session.RowBounds@b7f23d9
参数:null
方法:public abstract java.util.List org.apache.ibatis.executor.Executor.query(org.apache.ibatis.mapping.MappedStatement,java.lang.Object,org.apache.ibatis.session.RowBounds,org.apache.ibatis.session.ResultHandler) throws java.sql.SQLException
目标对象:org.apache.ibatis.executor.CachingExecutor@61d47554
原集合数据:[User{id=1, name='ConstXiong1', mc='null'}, User{id=2, name='ConstXiong2', mc='null'}, User{id=3, name='ConstXiong3', mc='null'}, User{id=4, name='ConstXiong4', mc='null'}, User{id=5, name='ConstXiong5', mc='null'}]
只获取第一个对象
[User{id=1, name='ConstXiong1', mc='null'}]

注:以上内容仅提供参考和交流,请勿用于商业用途,如有侵权联系本人删除!

注:此博客只是为了记忆相关知识点,大部分为网络上的文章,在此向各个文章的作者表示感谢!

上一篇:ssh框架搭建的基本步骤


下一篇:来来来!java取地址栏参数