面试题:MyBatis二级缓存[通俗易懂]

2022-09-29 11:32:47 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

一:一级缓存和二级缓存简介

①、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。

②、二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

写这篇文章的初衷:MyBatis二级缓存在实际工作中一般都不会使用,但是就有有些面试官一直问这个问题,所以为了这道面试题,这里专门探讨一下。

二:一级缓存

MyBatis一级缓存也称为查询缓存,是在SqlSession中保存了一个HashMap, key为SQL语句,value为查询出的结果,当一个查询操作会判断查询的语句是否在HashMap中存在,如果存在则直接取出缓存的查询结果,如果不存在就继续查询数据库然后将结果缓存起来。一级缓存又被称为 SqlSession 级别的缓存,会话缓存。一级缓存减少与数据库的交互次数从而降低数据库的压力,进而提高响应速度。MyBatis一级缓存默认是自动开启的,一级缓存是针对于单个SqlSession的,不同的SqlSession之间缓存数据HashMap是无法相互影响的。当insert、update、delete操作时都会清除一级缓存,导致一级缓存失效。

代码语言:javascript复制
public class SqlSession {
      private Map<Object, Object> cache = new HashMap<Object, Object>();
}
代码语言:javascript复制
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
代码语言:javascript复制
## 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

## Mybatis 配置
mybatis.typeAliasesPackage=com.example.mybatis.entity
mybatis.mapperLocations=classpath:mapper/*.xml
mybatis.configuration.map-underscore-to-camel-case=true

# 打印mybatis中的sql语句
logging.level.com.example.mybatis.mapper=debug
代码语言:javascript复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String username;
    private Date createTime;
}
代码语言:javascript复制
public interface UserMapper {
    User getUser(@Param("id") Long id);
}
代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.mybatis.mapper.UserMapper">

    <select id="getUser" resultType="com.example.mybatis.entity.User">
        SELECT * FROM user where id = #{id}
    </select>
</mapper>
代码语言:javascript复制
@SpringBootTest
class SpringbootMybatisApplicationTests {


    @Autowired
    private SqlSessionFactory sessionFactory;

    @Test
    void testMyBatis() {
        SqlSession sqlSession = sessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        // 查询两次完全相同的SQL
        User user1 = userMapper.getUser(1L);
        System.out.println(user1);

        // 走一级缓存,不查询数据库
        User user11 = userMapper.getUser(1L);
        System.out.println(user11);

        // 清除一级缓存
        sqlSession.clearCache();
    }
}

插入会使一级缓存失效

代码语言:javascript复制
@SpringBootTest
class SpringbootMybatisApplicationTests {


    @Autowired
    private SqlSessionFactory sessionFactory;

    @Test
    void testMyBatis() {
        SqlSession sqlSession = sessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        User user1 = userMapper.getUser(1L);
        System.out.println(user1);

        // 插入会使一级缓存失效
        userMapper.insertUser(new User(5L, "monday", new Date()));

        User user11 = userMapper.getUser(1L);
        System.out.println(user11);
    }
}

更新会清除一级缓存,会使一级缓存失效

代码语言:javascript复制
@SpringBootTest
class SpringbootMybatisApplicationTests {


    @Autowired
    private SqlSessionFactory sessionFactory;

    @Test
    void testMyBatis() {
        SqlSession sqlSession = sessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        User user1 = userMapper.getUser(1L);
        System.out.println(user1);

          // 更新会使一级缓存失效
        userMapper.updateUser(new User(5L, "monday", new Date()));

        User user11 = userMapper.getUser(1L);
        System.out.println(user11);
    }
}

三:二级缓存

二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于 sqlSession 的,而 二级缓存是基于 mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。二级缓存底层也是使用的HashMap来保存结果的。

代码语言:javascript复制
# 开启MyBatis二级缓存
mybatis.configuration.cache-enabled=true

在Mapper.xml中配置cache标签,表示开启当前Mapper对应的二级缓存。也可以通过type属性指定缓存实现类,如果不配置mybatis默认使用PerpetualCache作为缓存实现类,也可以自定义缓存实现类。

代码语言:javascript复制
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"></cache>
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.mybatis.mapper.UserMapper">
    <!-- 开启二级缓存 -->
    <cache></cache>

    <select id="getUser" resultType="com.example.mybatis.entity.User">
        SELECT * FROM user where id = #{id}
    </select>

</mapper>

实体类需要实现Serializable接口。

代码语言:javascript复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private Long id;
    private String username;
    private Date createTime;
}
代码语言:javascript复制
@SpringBootTest
class SpringbootMybatisApplicationTests {


    @Autowired
    private SqlSessionFactory sessionFactory;

    @Test
    void testMyBatisCache() {
        SqlSession sqlSession1 = sessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);

        // 第一次查询,发出sql语句,并将查询的结果放入到二级缓存中
        User user1 = userMapper1.getUser(1L);
        System.out.println(user1);
        sqlSession1.close();

        // 使用sqlSession2查询,没有发送sql语句
        SqlSession sqlSession2 = sessionFactory.openSession();
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = userMapper2.getUser(1L);
        System.out.println(user2);
        sqlSession2.close();
    }
}

useCache表示是否使用二级缓存,默认是true使用二级缓存,可以配置成false禁止二级缓存,每次都查询数据库。在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。flushCache=”true” 属性表示即刷新缓存,如果改成false则不会刷新(我这里的mybatis版本测试的flushCache默认值并不是true而是false,必须显式配置该参数为true才会刷新缓存)

代码语言:javascript复制
<select id="getUser" useCache="false" flushCache="true" resultType="com.example.mybatis.entity.User">
    SELECT * FROM user where id = #{id}
</select>
代码语言:javascript复制
@SpringBootTest
class SpringbootMybatisApplicationTests {


    @Autowired
    private SqlSessionFactory sessionFactory;

    @Test
    void testMyBatisCache() {
        // 第一次查询写入到二级缓存
        SqlSession sqlSession1 = sessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        User user1 = userMapper1.getUser(5L);
        System.out.println(user1);
        sqlSession1.close();

        // 中间更新,缓存被清空
        SqlSession sqlSession2 = sessionFactory.openSession();
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        userMapper2.updateUser(new User(5L, "abc", new Date()));
        sqlSession2.close();

        // 还会继续发SQL查询, 注意必须显式配置flushCache="true" 否则数据不对
        SqlSession sqlSession3 = sessionFactory.openSession();
        UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
        User user3 = userMapper3.getUser(5L);
        System.out.println(user3);
        sqlSession3.close();
    }
}

四:分布式缓存

PerpetualCache只能在单台服务器上有效,如果在多态服务器上就没有效果了,此时我们可以使用分布式缓存来实现在所有服务器上二级缓存都生效,常用的分布式缓存有redis、ehcache、memcache等。

那么究竟应该不应该使用二级缓存呢?先来看一下二级缓存的注意事项:

  1. 缓存是以namespace为单位的,不同namespace下的操作互不影响。
  2. insert,update,delete操作会清空所在namespace下的全部缓存, 不能做到部分删除。
  3. 通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace。

二级缓存默认是单击模式的,如果要支持分布式还要配置redis,而且二级缓存不支持单条数据的更新一旦发生insert/update/delete操作会一次性清空缓存,还不如直接使用redis作为缓存服务器来的简单,所以二级缓存并不推荐使用

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/193035.html原文链接:https://javaforall.cn

0 人点赞