MyBatis面试分析

一.   resultType(属性)和resultMap(标签引用)的区别?
         collection和association的区别?
         Statement和PreparedStatement的区别?
         #{}和${}的区别是什么?

    resultType不支持自定义返回结果,会将查询到的结果通过到type中java对象的同名的属性,对象中的属性名必须和数据库的字段一致。
   resultMap支持自定义返回结果,提前配置类和表,列和类中的属性之间的对应关系,赋值给map中列对应引用的属性名

    collection和association区别在于他们是外层查询和内嵌对象之间的关联场景
    collection是处理一对多的关系,外层查询中某列作为外键关联内嵌对象集合的某列
    association是处理一对一或者多对一的关系,外层查询结果集的某列关联内嵌对象的某列

public class A{
    private B b1;
    private List<B> b2;
}
在映射b1属性时用association标签, 映射b2时用collection标签,分别是一对一,一对多的关系

    Statement是将完整的sql发送给数据库,并且生成执行计划,没有cache,对于一条数据的执行力会大于PreparedStatement,但是会有sql注入的问题。
    PreparedStatement是Statement的子类,会将带占位符的sql预编译成有或者无参的存储过程,只预编译一次,生成一次执行计划,采用了cache机制,对于批量操作来说因为只预编译了一次,每次用到直接从cache里面取出sql并且传入变量,所以批量性能和对数据库内存的开销远远小于Statement,并且没有sql注入。

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

二.  MyBatis 里面的几个核心对象及生命周期?

public class TestMybatis {

    public void testMapper() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        try {
            BlogMapper mapper = session.getMapper(BlogMapper.class);
            Blog blog = mapper.selectBlogById(1);
            System.out.println(blog);
        } finally {
            session.close();
        }
    }
}

1)  SqlSessionFactoryBuiler
  首先是 SqlSessionFactoryBuiler。它是用来构建 SqlSessionFactory 的,而 SqlSessionFactory 只需要一个,所以只要构建了这一个 SqlSessionFactory,它的使命 就完成了,也就没有存在的意义了。所以它的生命周期只存在于方法的局部。
2)  SqlSessionFactory
  SqlSessionFactory 是用来创建 SqlSession 的,每次应用程序访问数据库,都需要 创建一个会话。因为我们一直有创建会话的需要,所以 SqlSessionFactory 应该存在于 应用的整个生命周期中(作用域是应用作用域)。创建 SqlSession 只需要一个实例来做 这件事就行了,否则会产生很多的混乱,和浪费资源。所以我们要采用单例模式。
3)  SqlSession
  SqlSession 是一个会话,因为它不是线程安全的,不能在线程间共享。所以我们在 请求开始的时候创建一个 SqlSession 对象,在请求结束或者说方法执行完毕的时候要及 时关闭它(一次请求或者操作中)。
4)  Mapper
   Mapper(实际上是一个代理对象)是从 SqlSession 中获取的。
   BlogMapper mapper = session.getMapper(BlogMapper.class);
它的作用是发送 SQL 来操作数据库的数据。它应该在一个 SqlSession 事务方法之 内。

三.  MyBatis cache缓存

1.  缓存体系结构

  MyBatis 跟缓存相关的类都在 cache 包里面,其中有一个 Cache 接口,只有一个默 认的实现类 PerpetualCache,它是用 HashMap 实现的。
  除此之外,还有很多的装饰器,通过这些装饰器可以额外实现很多的功能:回收策略、日志记录、定时刷新等等。
但是无论怎么装饰,经过多少层装饰,最后使用的还是基本的实现类(默认 PerpetualCache)。

   所有的缓存实现类总体上可分为三类:基本缓存、淘汰算法缓存、装饰器缓存。

缓存实现类

描述

作用

装饰条件

基本缓存

缓存基本实现类

默认是 PerpetualCache,也可以自定义比如 RedisCache、EhCache 等,具备基本功能的缓存类

LruCache

LRU 策略的缓存

当缓存到达上限时候,删除最近最少使用的缓存 (Least Recently Use)

eviction="LRU"(默 认)

FifoCache

FIFO 策略的缓存

当缓存到达上限时候,删除最先入队的缓存
eviction="FIFO"
SoftCache
WeakCache

带清理策略的缓存

通过 JVM 的软引用和弱引用来实现缓存,当 JVM 内存不足时,会自动清理掉这些缓存,基于 SoftReference 和 WeakReference

eviction="SOFT"
eviction="WEAK"
LoggingCache

带日志功能的缓存

比如:输出缓存命中率

基本

SynchronizedCache

同步缓存

基于 synchronized 关键字实现,解决并发问题

基本

BlockingCache

阻塞缓存

通过在 get/put 方式中加锁,保证只有一个线程操 作缓存,基于 Java 重入锁实现

blocking=true
SerializedCache

支持序列化的缓存

将对象序列化以后存到缓存中,取出时反序列化

readOnly=false(默 认)

ScheduledCache

定时调度的缓存

在进行 get/put/remove/getSize 等操作前,判断 缓存时间是否超过了设置的最长缓存时间(默认是 一小时),如果是则清空缓存--即每隔一段时间清 空一次缓存

flushInterval 不为 空

TransactionalCache

事务缓存

在二级缓存中使用,可一次存入多个缓存,移除多 个缓存

在 TransactionalCach eManager 中用 Map 维护对应关系

2.  一级缓存

1)介绍

  一级缓存也叫本地缓存,MyBatis 的一级缓存是在会话(SqlSession)层面进行缓 存的。MyBatis 的一级缓存是默认开启的,不需要任何的配置。

2)存放位置


  在同一个会话里面共享一级缓存,这个对象肯定是在 SqlSession 里面创建的,作为SqlSession的一个属性。DefaultSqlSession 里面只有两个属性,Configuration 是全局的,
所以缓存只可能放在 Executor里面维护——SimpleExecutor/ReuseExecutor/BatchExecutor的父类BaseExecutor的构造函数中持有了PerpetualCache。 

3)缓存的作用范围

在同一个 session 中共享,不同 session 不能共享

演示:一级缓存需要先关闭二级缓存, localCacheScope 设置为 SESSION
//  1.同一个session
BlogMapper mapper = session.getMapper(BlogMapper.class);
System.out.println(mapper.selectBlog(1));
System.out.println(mapper.selectBlog(1));
//  2.创建另一个session
SqlSession session1 = sqlSessionFactory.openSession();
BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
System.out.println(mapper.selectBlog(1));
 //判断是否命中缓存:如果再次发送 SQL 到数据库执行,说明没有命中缓存;如果直 接打印对象,说明是从内存缓存中取到了结果。

但sqlSession执行commit,即增删改操作时会清空一级缓存,这么做的目的是避免脏读。

4)源码解读

 

3.  二级缓存以及为什么引入二级缓存?

1)引入二级原因

  • 二级缓存的作用范围更大,是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,可以共用二级缓存。
  • 实际开发中,MyBatis通常和Spring进行整合开发。Spring将事务放到Service中管理,对于每一个service中的sqlsession是不同的,这是通过mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer创建sqlsession自动注入到service中的。 每次查询之后都要进行关闭sqlSession,关闭之后数据被清空。所以spring整合之后,如果没有事务,一级缓存是没有意义的。

2)二级缓存的开启及使用

   1.  首先在核心配置文件(mybatis-config.xml)中配置

<!-- 全局配置参数,需要时再设置 -->
<settings>
  <!-- 开启二级缓存  默认值为true -->
  <setting name="cacheEnabled" value="true"/>
</settings>

    2.  然后在具体的sql文件(mapper.xml)中配置

<mapper namespace="cn.zqr.mybatis.mapper.UserMapper">
<!-- 开启本mapper namespace下的二级缓存 -->
<cache></cache>

      关于cache标签配置:

<!-- 声明这个 namespace 使用二级缓存 -->
<cache	<!-缓存实现类,需要实现 Cache 接口,默认是 PerpetualCache-->
	 type="org.apache.ibatis.cache.impl.PerpetualCache"
	<!—最多缓存对象个数,默认 1024-->
	 size="1024"
	<!—回收策略-->
	 eviction="LRU"
	<!—自动刷新时间 ms,未配置时只有调用时刷新-->
	 flushInterval="120000"
 	<!—默认是 false(安全),改为 true 可读写时,对象必须支持序列化 -->
	 readOnly="false“ />

 

属性含义取值

type

缓存实现类

需要实现 Cache 接口,默认是 PerpetualCache

size

最多缓存对象个数

默认 1024

eviction

回收策略(缓存淘汰算法)

LRU – 最近最少使用的:移除最长时间不被使用的对象(默认)。

FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

flushInterval

定时自动清空缓存间隔

自动刷新时间,单位 ms,未配置时只有调用时刷新

readOnly

是否只读

true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象 不能被修改。这提供了很重要的性能优势。 false:读写缓存;会返回缓存对象的拷贝(通过序列化),不会共享。这 会慢一些,但是安全,因此默认是 false。

改为 false 可读写时,对象必须支持序列化。

blocking

是否使用可重入锁实现
缓存的并发控制

true,会使用 BlockingCache 对 Cache 进行装饰 默认 false

 

    3.  pass:灵活配置

    1⃣️如果针对每次查询都需要最新的数据sql,可设置成useCache=false,禁用二级缓存。

<select id="findArtPageview" parameterType="com.Article" resultType="int" useCache="false">
  select pageview from article where aid=#{aid}
</select>

    2⃣️ 一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。但是如    果你不想刷新缓存只需要修改flushCache=false。

<update id="pageviewAdd" parameterType="com.Goods" flushCache="false">
  update goods set pageview=#{pageview} where gid=#{gid}
</update>

3)注意事项(即为什么二级缓存默认是关闭的?)

  • 因为所有的增删改都会刷新二级缓存,导致二级缓存失效,所以适合在查询为主 的应用中使用,比如历史交易、历史订单的查询。否则缓存就失去了意义。
  • 推荐在一个Mapper里面只操作单表的情况使用。如果多个namespace 中有针对于同一个表的操作,比如User表,如果在一个namespace中刷新了缓存,Role表的namespace中和user关联的sql没有刷新,就会出现读到脏数据的情况。
  • 跨namespace 的缓存共享的问题,可以使用<cache-ref>来解决:

        <cache-ref namespace="com.gupaoedu.crud.dao.DepartmentMapper“ />
        两个命名空间的操作使用的是同一个Cache,在关联的表比较少,或者按照业务可以对表进行分组的时候可以使用。但是在这种情况下,多个Mapper的操作都会引起缓存刷新,缓存的意义已经不大了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值